asdfasfd
This commit is contained in:
@@ -350,6 +350,7 @@ const PropertyDetail = () => {
|
||||
const [isTracing, setIsTracing] = useState(false);
|
||||
const [gpsWatchId, setGpsWatchId] = useState(null);
|
||||
const [gpsTracePoints, setGpsTracePoints] = useState([]);
|
||||
const [gpsSamples, setGpsSamples] = useState([]); // recent raw samples for smoothing
|
||||
const [gpsDistance, setGpsDistance] = useState(0);
|
||||
const [gpsAccuracy, setGpsAccuracy] = useState(null);
|
||||
const [isSnapPreview, setIsSnapPreview] = useState(false);
|
||||
@@ -476,10 +477,39 @@ const PropertyDetail = () => {
|
||||
};
|
||||
|
||||
// GPS filtering and snapping (defaults)
|
||||
const ACCURACY_MAX_METERS = 15; // Ignore points with accuracy worse than this
|
||||
const MIN_MOVE_METERS = 2; // Ignore points if user hasn't moved this far from last point
|
||||
const ACCURACY_MAX_METERS = 12; // Ignore points with accuracy worse than this
|
||||
const MIN_MOVE_METERS = 2.5; // Ignore points if user hasn't moved this far from last point
|
||||
const TURN_MIN_DEG = 12; // Minimum heading change to record when small movement
|
||||
const SNAP_METERS = 5; // Snap to starting point when within this distance
|
||||
|
||||
// Compute bearing between two lat/lng points (degrees)
|
||||
const bearingDeg = (a, b) => {
|
||||
const toRad = (d) => (d * Math.PI) / 180;
|
||||
const toDeg = (r) => (r * 180) / Math.PI;
|
||||
const lat1 = toRad(a[0]);
|
||||
const lat2 = toRad(b[0]);
|
||||
const dLng = toRad(b[1] - a[1]);
|
||||
const y = Math.sin(dLng) * Math.cos(lat2);
|
||||
const x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLng);
|
||||
const brg = (toDeg(Math.atan2(y, x)) + 360) % 360;
|
||||
return brg;
|
||||
};
|
||||
|
||||
// Maintain a short buffer of samples; return averaged lat/lng of best by accuracy
|
||||
const addSampleAndSmooth = (lat, lng, accuracy) => {
|
||||
const now = Date.now();
|
||||
setGpsSamples((prev) => [...prev, { lat, lng, accuracy: accuracy ?? 999, t: now }].slice(-12));
|
||||
const pool = [...gpsSamples, { lat, lng, accuracy: accuracy ?? 999, t: now }].slice(-12);
|
||||
const top = pool
|
||||
.filter((s) => s.accuracy != null)
|
||||
.sort((a, b) => a.accuracy - b.accuracy)
|
||||
.slice(0, Math.min(5, pool.length));
|
||||
if (top.length === 0) return [lat, lng];
|
||||
const avgLat = top.reduce((s, p) => s + p.lat, 0) / top.length;
|
||||
const avgLng = top.reduce((s, p) => s + p.lng, 0) / top.length;
|
||||
return [avgLat, avgLng];
|
||||
};
|
||||
|
||||
const acceptAndNormalizePoint = (lat, lng, accuracy, currentPoints) => {
|
||||
if (accuracy != null && accuracy > ACCURACY_MAX_METERS) {
|
||||
toast("GPS accuracy too low (" + Math.round(accuracy) + "m). Waiting for better fix…");
|
||||
@@ -490,10 +520,18 @@ const PropertyDetail = () => {
|
||||
const [llat, llng] = currentPoints[currentPoints.length - 1];
|
||||
const moved = haversine(llat, llng, lat, lng);
|
||||
if (moved < MIN_MOVE_METERS) {
|
||||
// Too close to last point
|
||||
// Allow small move only if heading changed sufficiently vs previous segment
|
||||
if (currentPoints.length >= 2) {
|
||||
const prevHead = bearingDeg(currentPoints[currentPoints.length - 2], [llat, llng]);
|
||||
const candHead = bearingDeg([llat, llng], [lat, lng]);
|
||||
let diff = Math.abs(prevHead - candHead);
|
||||
if (diff > 180) diff = 360 - diff;
|
||||
if (diff < TURN_MIN_DEG) return null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Snap to starting point if near
|
||||
if (currentPoints.length >= 2) {
|
||||
const [slat, slng] = currentPoints[0];
|
||||
@@ -512,11 +550,12 @@ const PropertyDetail = () => {
|
||||
const { latitude, longitude, accuracy } = pos.coords;
|
||||
setGpsAccuracy(accuracy || null);
|
||||
setGpsTracePoints(prev => {
|
||||
const normalized = acceptAndNormalizePoint(latitude, longitude, accuracy, prev);
|
||||
const [sLat, sLng] = addSampleAndSmooth(latitude, longitude, accuracy);
|
||||
const normalized = acceptAndNormalizePoint(sLat, sLng, accuracy, prev);
|
||||
// For preview: check proximity to start even if skipping
|
||||
if (prev.length >= 2) {
|
||||
const [slat, slng] = prev[0];
|
||||
const dStart = haversine(slat, slng, latitude, longitude);
|
||||
const dStart = haversine(slat, slng, sLat, sLng);
|
||||
setIsSnapPreview(dStart <= SNAP_METERS);
|
||||
} else { setIsSnapPreview(false); }
|
||||
if (!normalized) return prev; // skip
|
||||
@@ -567,11 +606,22 @@ const PropertyDetail = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Ensure closed ring and recompute area
|
||||
let coords = pendingSection.coordinates || [];
|
||||
if (coords.length >= 3) {
|
||||
const [f0, f1] = coords[0] || [];
|
||||
const [l0, l1] = coords[coords.length - 1] || [];
|
||||
if (f0 !== l0 || f1 !== l1) coords = [...coords, coords[0]];
|
||||
}
|
||||
if (coords.length < 4) { toast.error('Polygon invalid: need at least 3 unique points'); return; }
|
||||
const recomputedArea = calculateAreaInSqFt(coords);
|
||||
if (!recomputedArea || recomputedArea <= 0) { toast.error('Polygon area is zero — adjust points'); return; }
|
||||
|
||||
const sectionData = {
|
||||
name: sectionName,
|
||||
area: pendingSection.area,
|
||||
area: recomputedArea,
|
||||
polygonData: {
|
||||
coordinates: [pendingSection.coordinates],
|
||||
coordinates: [coords],
|
||||
color: pendingSection.color
|
||||
},
|
||||
grassType: sectionGrassTypes.join(', '),
|
||||
@@ -611,7 +661,7 @@ const PropertyDetail = () => {
|
||||
setCurrentColor(SECTION_COLORS[nextIndex]);
|
||||
} catch (error) {
|
||||
console.error('Failed to save section:', error);
|
||||
toast.error('Failed to save section. Please try again.');
|
||||
toast.error(error?.response?.data?.message || error?.message || 'Failed to save section. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -939,10 +989,11 @@ const PropertyDetail = () => {
|
||||
const { latitude, longitude, accuracy } = pos.coords;
|
||||
setGpsAccuracy(accuracy || null);
|
||||
setGpsTracePoints(prev => {
|
||||
const normalized = acceptAndNormalizePoint(latitude, longitude, accuracy, prev);
|
||||
const [sLat, sLng] = addSampleAndSmooth(latitude, longitude, accuracy);
|
||||
const normalized = acceptAndNormalizePoint(sLat, sLng, accuracy, prev);
|
||||
if (prev.length >= 2) {
|
||||
const [slat, slng] = prev[0];
|
||||
const dStart = haversine(slat, slng, latitude, longitude);
|
||||
const dStart = haversine(slat, slng, sLat, sLng);
|
||||
setIsSnapPreview(dStart <= SNAP_METERS);
|
||||
} else { setIsSnapPreview(false); }
|
||||
if (!normalized) return prev;
|
||||
@@ -1226,6 +1277,55 @@ const PropertyDetail = () => {
|
||||
/>
|
||||
<span>{pendingSection?.area.toLocaleString()} sq ft</span>
|
||||
</div>
|
||||
{/* Nudge controls */}
|
||||
<div>
|
||||
<label className="label">Adjust Position (1 ft)</label>
|
||||
<div className="grid grid-cols-3 gap-2 w-40">
|
||||
<div></div>
|
||||
<button className="btn-secondary" onClick={() => {
|
||||
setPendingSection(prev => {
|
||||
if (!prev) return prev;
|
||||
const lat = prev.coordinates[0][0];
|
||||
const metersPerDegLat = 111320;
|
||||
const dLat = (1 * 0.3048) / metersPerDegLat; // 1 ft north
|
||||
const coords = prev.coordinates.map(([la, ln]) => [la + dLat, ln]);
|
||||
return { ...prev, coordinates };
|
||||
});
|
||||
}}>▲</button>
|
||||
<div></div>
|
||||
<button className="btn-secondary" onClick={() => {
|
||||
setPendingSection(prev => {
|
||||
if (!prev) return prev;
|
||||
const lat = prev.coordinates[0][0];
|
||||
const metersPerDegLng = 111320 * Math.cos(lat * Math.PI / 180);
|
||||
const dLng = (1 * 0.3048) / metersPerDegLng; // 1 ft west
|
||||
const coords = prev.coordinates.map(([la, ln]) => [la, ln - dLng]);
|
||||
return { ...prev, coordinates };
|
||||
});
|
||||
}}>◀</button>
|
||||
<div></div>
|
||||
<button className="btn-secondary" onClick={() => {
|
||||
setPendingSection(prev => {
|
||||
if (!prev) return prev;
|
||||
const lat = prev.coordinates[0][0];
|
||||
const metersPerDegLng = 111320 * Math.cos(lat * Math.PI / 180);
|
||||
const dLng = (1 * 0.3048) / metersPerDegLng; // 1 ft east
|
||||
const coords = prev.coordinates.map(([la, ln]) => [la, ln + dLng]);
|
||||
return { ...prev, coordinates };
|
||||
});
|
||||
}}>▶</button>
|
||||
<div></div>
|
||||
<button className="btn-secondary" onClick={() => {
|
||||
setPendingSection(prev => {
|
||||
if (!prev) return prev;
|
||||
const dLat = (1 * 0.3048) / 111320; // 1 ft south
|
||||
const coords = prev.coordinates.map(([la, ln]) => [la - dLat, ln]);
|
||||
return { ...prev, coordinates };
|
||||
});
|
||||
}}>▼</button>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="label">Grass Types</label>
|
||||
<TagInput
|
||||
|
||||
Reference in New Issue
Block a user