From 4d52aae85b84e0601b24bd998d24884c20e1cf7f Mon Sep 17 00:00:00 2001 From: Jake Kasper Date: Tue, 2 Sep 2025 12:27:39 -0500 Subject: [PATCH] mowing --- frontend/src/components/Maps/PropertyMap.js | 117 ++++++++++++++++-- .../components/Mowing/MowingExecutionModal.js | 3 +- 2 files changed, 106 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Maps/PropertyMap.js b/frontend/src/components/Maps/PropertyMap.js index 6711346..081df01 100644 --- a/frontend/src/components/Maps/PropertyMap.js +++ b/frontend/src/components/Maps/PropertyMap.js @@ -112,7 +112,8 @@ const PropertyMap = ({ mode = "view", // "view", "edit", "execution" gpsTrack = [], currentLocation = null, - showTrackPoints = true + showTrackPoints = true, + direction = null // optional mowing direction: N_S, E_W, NE_SW, NW_SE, CIRCULAR }) => { // Debug logging @@ -413,7 +414,7 @@ const PropertyMap = ({ {/* Existing sections */} {sections.map((section) => { // Handle both string and object polygon data - let polygonData = section.polygonData; + let polygonData = section.polygonData || section.polygon_data; if (typeof polygonData === 'string') { try { polygonData = JSON.parse(polygonData); @@ -452,9 +453,9 @@ const PropertyMap = ({ ); })} - {/* GPS Tracking Elements */} - {(mode === "execution" || mode === "view") && ( - <> + {/* GPS Tracking Elements */} + {(mode === "execution" || mode === "view") && ( + <> {/* GPS Track Polyline */} {gpsTrack.length > 1 && ( - )} - - )} + {currentLocation && mode === "execution" && ( + + )} + + {/* Mowing direction guide */} + {direction && sections && sections.length > 0 && (() => { + // Compute bounding box from all section polygons + let minLat = 90, maxLat = -90, minLng = 180, maxLng = -180; + sections.forEach(section => { + let polygonData = section.polygonData || section.polygon_data; + if (typeof polygonData === 'string') { + try { polygonData = JSON.parse(polygonData); } catch { return; } + } + if (!polygonData?.coordinates?.[0]) return; + polygonData.coordinates[0].forEach(([lat, lng]) => { + if (lat < minLat) minLat = lat; + if (lat > maxLat) maxLat = lat; + if (lng < minLng) minLng = lng; + if (lng > maxLng) maxLng = lng; + }); + }); + if (!(minLat < maxLat && minLng < maxLng)) return null; + const midLat = (minLat + maxLat) / 2; + const midLng = (minLng + maxLng) / 2; + + // Build a main guideline polyline based on direction + let mainLine = []; + switch ((direction || '').toUpperCase()) { + case 'N_S': + mainLine = [[maxLat, midLng], [minLat, midLng]]; // North to South + break; + case 'E_W': + mainLine = [[midLat, minLng], [midLat, maxLng]]; // East to West + break; + case 'NE_SW': + mainLine = [[maxLat, maxLng], [minLat, minLng]]; + break; + case 'NW_SE': + mainLine = [[maxLat, minLng], [minLat, maxLng]]; + break; + case 'CIRCULAR': { + // Approximate circle around center with radius based on bbox + const latRadius = (maxLat - minLat) / 3; + const lngRadius = (maxLng - minLng) / 3; + const points = Array.from({ length: 72 }).map((_, i) => { + const t = (i / 72) * 2 * Math.PI; + return [midLat + latRadius * Math.sin(t), midLng + lngRadius * Math.cos(t)]; + }); + return ( + <> + +
Direction: Circular
+ + ); + } + default: + break; + } + if (mainLine.length === 0) return null; + + // Add a couple of parallel guide lines to improve visibility + const offsetLat = (maxLat - minLat) * 0.02; // ~2% of bbox + const offsetLng = (maxLng - minLng) * 0.02; + const parallelLines = []; + if (direction === 'N_S') { + parallelLines.push(mainLine); + parallelLines.push([[maxLat, midLng - offsetLng], [minLat, midLng - offsetLng]]); + parallelLines.push([[maxLat, midLng + offsetLng], [minLat, midLng + offsetLng]]); + } else if (direction === 'E_W') { + parallelLines.push(mainLine); + parallelLines.push([[midLat - offsetLat, minLng], [midLat - offsetLat, maxLng]]); + parallelLines.push([[midLat + offsetLat, minLng], [midLat + offsetLat, maxLng]]); + } else if (direction === 'NE_SW' || direction === 'NW_SE') { + parallelLines.push(mainLine); + } + + const directionLabelMap = { + 'N_S': 'North to South', + 'E_W': 'East to West', + 'NE_SW': 'NE to SW', + 'NW_SE': 'NW to SE', + 'CIRCULAR': 'Circular' + }; + + return ( + <> + {parallelLines.map((line, idx) => ( + + ))} +
Direction: {directionLabelMap[(direction || '').toUpperCase()] || direction}
+ + ); + })()} + + )} {/* Current polygon being drawn */} {currentPolygon.length > 0 && ( diff --git a/frontend/src/components/Mowing/MowingExecutionModal.js b/frontend/src/components/Mowing/MowingExecutionModal.js index 6366e2c..4d12314 100644 --- a/frontend/src/components/Mowing/MowingExecutionModal.js +++ b/frontend/src/components/Mowing/MowingExecutionModal.js @@ -126,7 +126,7 @@ const MowingExecutionModal = ({ plan, onClose, onComplete }) => {
Mower: {plan.equipment_name}
Cut Height: {plan.cut_height_inches}"
-
+
{ currentLocation={currentLocation} center={center || [39.8283, -98.5795]} zoom={center ? 16 : 15} + direction={plan.direction} className="h-80 w-full" />