@mytec: iter1.5.1 ready for testing

This commit is contained in:
2026-01-31 02:13:28 +02:00
parent 7595ba430d
commit 358846fe20
5 changed files with 69 additions and 31 deletions

View File

@@ -65,6 +65,20 @@ export default function App() {
const [presets, setPresets] = useState<Record<string, Preset>>({});
const [showAdvanced, setShowAdvanced] = useState(false);
// Elapsed time counter during calculation
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
if (!isCalculating) {
setElapsed(0);
return;
}
const start = Date.now();
const interval = setInterval(() => {
setElapsed(Math.floor((Date.now() - start) / 1000));
}, 1000);
return () => clearInterval(interval);
}, [isCalculating]);
// Load presets on mount
useEffect(() => {
api.getPresets().then(setPresets).catch((err) => {
@@ -415,7 +429,7 @@ export default function App() {
>
<span className="flex items-center gap-1">
<span className="animate-spin inline-block w-3 h-3 border-2 border-white border-t-transparent rounded-full" />
Cancel
Cancel{elapsed > 0 ? ` (${elapsed}s)` : '...'}
</span>
</Button>
) : (

View File

@@ -52,9 +52,11 @@ export default function CoverageBoundary({
const paths: L.LatLngExpression[][] = [];
for (const sitePoints of bySite.values()) {
const path = computeConcaveHull(sitePoints, resolution);
if (path.length >= 3) {
paths.push(path);
const sitePaths = computeConcaveHulls(sitePoints, resolution);
for (const path of sitePaths) {
if (path.length >= 3) {
paths.push(path);
}
}
}
@@ -103,15 +105,16 @@ export default function CoverageBoundary({
// ---------------------------------------------------------------------------
/**
* Compute a concave hull boundary for one site's coverage points.
* Compute concave hull boundary path(s) for a set of coverage points.
*
* maxEdge = resolution * 3 (in km) gives good detail without over-fitting.
* Returns multiple paths if hull is a MultiPolygon (disjoint coverage areas).
* Falls back to empty if hull computation fails (e.g., collinear points).
*/
function computeConcaveHull(
function computeConcaveHulls(
pts: CoveragePoint[],
resolutionM: number
): L.LatLngExpression[] {
): L.LatLngExpression[][] {
if (pts.length < 3) return [];
// Convert to GeoJSON FeatureCollection of Points
@@ -124,15 +127,28 @@ function computeConcaveHull(
try {
const hull = concave(fc, { maxEdge, units: 'kilometers' });
if (!hull || hull.geometry.type !== 'Polygon') {
return [];
if (!hull) return [];
// Handle both Polygon and MultiPolygon results
if (hull.geometry.type === 'Polygon') {
const coords = hull.geometry.coordinates[0];
return [
coords.map(
([lon, lat]: number[]) => [lat, lon] as L.LatLngExpression
),
];
}
// GeoJSON coordinates are [lon, lat]; Leaflet needs [lat, lon]
const coords = hull.geometry.coordinates[0];
return coords.map(
([lon, lat]: number[]) => [lat, lon] as L.LatLngExpression
);
if (hull.geometry.type === 'MultiPolygon') {
// Return all polygons as separate boundary paths
return hull.geometry.coordinates.map((poly) =>
poly[0].map(
([lon, lat]: number[]) => [lat, lon] as L.LatLngExpression
)
);
}
return [];
} catch (error) {
logger.error('Coverage hull computation error:', error);
return [];