This commit is contained in:
Jake Kasper
2025-09-02 08:05:11 -05:00
parent fcf5d3571c
commit 34baedfc46

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import * as turf from '@turf/turf';
import { applicationsAPI } from '../../services/api';
import PropertyMap from '../Maps/PropertyMap';
import { XMarkIcon, ClockIcon, MapPinIcon, WrenchScrewdriverIcon, BeakerIcon } from '@heroicons/react/24/outline';
@@ -44,21 +45,67 @@ const ApplicationViewModal = ({ application, propertyDetails, onClose }) => {
const calculateCoverage = (application, log) => {
if (!log?.gpsTrack?.points || log.gpsTrack.points.length < 2) return 0;
const totalDistanceFeet = distanceFeet(log);
const plannedArea = application.totalSectionArea || 0;
if (totalDistanceFeet === 0 || plannedArea === 0) return 0;
// Determine equipment width (feet)
let widthFeet = 4;
const equipmentFromPlan = planDetails?.equipment || {};
if (typeof equipmentFromPlan.spreadWidth === 'number' && equipmentFromPlan.spreadWidth > 0) {
widthFeet = equipmentFromPlan.spreadWidth;
} else if (typeof equipmentFromPlan.sprayWidthFeet === 'number' && equipmentFromPlan.sprayWidthFeet > 0) {
widthFeet = equipmentFromPlan.sprayWidthFeet;
} else {
const equipmentName = application.equipmentName?.toLowerCase() || '';
if (equipmentName.includes('spreader')) widthFeet = 12;
else if (equipmentName.includes('sprayer')) widthFeet = 20;
else if (equipmentName.includes('mower')) widthFeet = 6;
}
// Estimate equipment width based on type (feet)
let equipmentWidth = 4;
const equipmentName = application.equipmentName?.toLowerCase() || '';
if (equipmentName.includes('spreader')) equipmentWidth = 12;
else if (equipmentName.includes('sprayer')) equipmentWidth = 20;
else if (equipmentName.includes('mower')) equipmentWidth = 6;
const plannedAreaSqFt = application.totalSectionArea || 0;
if (plannedAreaSqFt <= 0) return 0;
// Distance (ft) * Width (ft) = Area (sq ft)
const theoreticalCoverageArea = totalDistanceFeet * equipmentWidth;
const coveragePercentage = Math.min((theoreticalCoverageArea / plannedArea) * 100, 100);
return Math.round(coveragePercentage);
// Build union polygon of all planned sections
try {
const polygons = (sections || []).map((section) => {
let poly = section.polygonData;
if (typeof poly === 'string') {
try { poly = JSON.parse(poly); } catch { poly = null; }
}
if (!poly?.coordinates?.[0]) return null;
// Convert [lat,lng] -> [lng,lat]
const coords = poly.coordinates[0].map(([lat, lng]) => [lng, lat]);
return turf.polygon([coords]);
}).filter(Boolean);
if (polygons.length === 0) {
// Fallback: distance * width heuristic
const totalDistanceFeet = distanceFeet(log);
if (totalDistanceFeet === 0) return 0;
const areaFt2 = totalDistanceFeet * widthFeet;
return Math.min(Math.round((areaFt2 / plannedAreaSqFt) * 100), 100);
}
const plannedUnion = polygons.reduce((acc, cur) => acc ? turf.union(acc, cur) : cur, null);
if (!plannedUnion) return 0;
// Buffer the GPS line by half the width
const lineCoords = log.gpsTrack.points.map((p) => [p.lng, p.lat]);
const line = turf.lineString(lineCoords);
const bufferKm = (widthFeet / 2) * 0.3048 / 1000; // feet -> meters -> km
const swath = turf.buffer(line, bufferKm, { units: 'kilometers' });
if (!swath) return 0;
const overlap = turf.intersect(swath, plannedUnion);
if (!overlap) return 0;
const overlapSqMeters = turf.area(overlap);
const plannedSqMeters = plannedAreaSqFt * 0.092903;
const pct = (overlapSqMeters / plannedSqMeters) * 100;
return Math.min(Math.round(pct), 100);
} catch (e) {
console.warn('Coverage calc fallback due to error:', e);
const totalDistanceFeet = distanceFeet(log);
const areaFt2 = totalDistanceFeet * widthFeet;
return Math.min(Math.round((areaFt2 / plannedAreaSqFt) * 100), 100);
}
};
useEffect(() => {