|
1 | 1 | import * as turf from '@turf/turf' |
2 | | -import type { Feature, Polygon } from 'geojson' |
| 2 | +import type { Feature, Point, Polygon, Position } from 'geojson' |
3 | 3 | import * as L from 'leaflet' |
4 | 4 |
|
5 | 5 | import { bearingBetween, calculateHaversineDistance, deltaBearing } from '@/libs/mission/general-estimates' |
@@ -381,7 +381,7 @@ export const generateSurveyPath = ( |
381 | 381 |
|
382 | 382 | if (continuousPath.length > 0 && turnaroundDistance === 0) { |
383 | 383 | const lastPoint = continuousPath[continuousPath.length - 1] |
384 | | - const edgePath = moveAlongEdge(poly, lastPoint, linePoints[0], diagonal) |
| 384 | + const edgePath = moveAlongEdge(poly, lastPoint, linePoints[0]) |
385 | 385 | continuousPath.push(...edgePath) |
386 | 386 | } |
387 | 387 |
|
@@ -421,50 +421,65 @@ export const generateSurveyPath = ( |
421 | 421 | } |
422 | 422 |
|
423 | 423 | /** |
424 | | - * Moves along the edge of a polygon from start to end point. |
425 | | - * @param {Feature<Polygon>} polygon - The polygon to move along. |
| 424 | + * Finds the index of the polygon ring edge (the segment from coords[i] to coords[i + 1]) that a point lies on. |
| 425 | + * @param {Feature<Point>} point - The point to locate. |
| 426 | + * @param {Position[]} coords - The polygon ring coordinates (closed, first equals last). |
| 427 | + * @returns {number} The edge index, or -1 if the point does not lie on any edge. |
| 428 | + */ |
| 429 | +const findRingEdgeIndex = (point: Feature<Point>, coords: Position[]): number => { |
| 430 | + for (let i = 0; i < coords.length - 1; i++) { |
| 431 | + // Small epsilon so floating-point drift on clipped boundary points still matches their edge. |
| 432 | + if (turf.booleanPointOnLine(point, turf.lineString([coords[i], coords[i + 1]]), { epsilon: 1e-6 })) return i |
| 433 | + } |
| 434 | + return -1 |
| 435 | +} |
| 436 | + |
| 437 | +/** |
| 438 | + * Total length of the path start -> vertices -> end, in kilometers. |
426 | 439 | * @param {L.LatLng} start - The starting point. |
| 440 | + * @param {Position[]} vertices - The intermediate ring vertices. |
427 | 441 | * @param {L.LatLng} end - The ending point. |
428 | | - * @param {number} maxDistance - The maximum distance to move. |
429 | | - * @returns {L.LatLng[]} The path along the edge. |
| 442 | + * @returns {number} The path length, in kilometers. |
430 | 443 | */ |
431 | | -export const moveAlongEdge = ( |
432 | | - polygon: Feature<Polygon>, |
433 | | - start: L.LatLng, |
434 | | - end: L.LatLng, |
435 | | - maxDistance: number |
436 | | -): L.LatLng[] => { |
| 444 | +const pathLengthThroughVertices = (start: L.LatLng, vertices: Position[], end: L.LatLng): number => |
| 445 | + turf.length(turf.lineString([[start.lng, start.lat], ...vertices, [end.lng, end.lat]])) |
| 446 | + |
| 447 | +/** |
| 448 | + * Finds the shortest path hugging a polygon's boundary between two points that lie on that boundary, so |
| 449 | + * consecutive survey transects are connected without overshooting into a far corner. |
| 450 | + * @param {Feature<Polygon>} polygon - The polygon to move along. |
| 451 | + * @param {L.LatLng} start - The starting point, expected to lie on the polygon boundary. |
| 452 | + * @param {L.LatLng} end - The ending point, expected to lie on the polygon boundary. |
| 453 | + * @returns {L.LatLng[]} The intermediate ring vertices between start and end, in the shorter direction. Empty |
| 454 | + * when both points lie on the same edge, so the caller connects them with a direct segment. |
| 455 | + */ |
| 456 | +const moveAlongEdge = (polygon: Feature<Polygon>, start: L.LatLng, end: L.LatLng): L.LatLng[] => { |
437 | 457 | const coords = polygon.geometry.coordinates[0] |
438 | | - const path: L.LatLng[] = [] |
439 | | - let remainingDistance = maxDistance |
440 | | - let currentPoint = turf.point([start.lng, start.lat]) |
441 | | - |
442 | | - for (let i = 0; i < coords.length; i++) { |
443 | | - const nextPoint = turf.point(coords[(i + 1) % coords.length]) |
444 | | - const edgeLine = turf.lineString([coords[i], coords[(i + 1) % coords.length]]) |
445 | | - |
446 | | - if (turf.booleanPointOnLine(currentPoint, edgeLine)) { |
447 | | - while (remainingDistance > 0) { |
448 | | - const distance = turf.distance(currentPoint, nextPoint) |
449 | | - if (distance <= remainingDistance) { |
450 | | - path.push(L.latLng(nextPoint.geometry.coordinates[1], nextPoint.geometry.coordinates[0])) |
451 | | - remainingDistance -= distance |
452 | | - currentPoint = nextPoint |
453 | | - break |
454 | | - } else { |
455 | | - const move = turf.along(edgeLine, remainingDistance, { units: 'kilometers' }) |
456 | | - path.push(L.latLng(move.geometry.coordinates[1], move.geometry.coordinates[0])) |
457 | | - break |
458 | | - } |
459 | | - } |
460 | | - } |
| 458 | + const startEdge = findRingEdgeIndex(turf.point([start.lng, start.lat]), coords) |
| 459 | + const endEdge = findRingEdgeIndex(turf.point([end.lng, end.lat]), coords) |
461 | 460 |
|
462 | | - if (turf.booleanPointOnLine(turf.point([end.lng, end.lat]), edgeLine)) { |
463 | | - break |
464 | | - } |
| 461 | + if (startEdge === -1 || endEdge === -1 || startEdge === endEdge) return [] |
| 462 | + |
| 463 | + const vertexCount = coords.length - 1 // Ring is closed, so the last coordinate duplicates the first. |
| 464 | + |
| 465 | + const forwardVertices: Position[] = [] |
| 466 | + for (let i = (startEdge + 1) % vertexCount; ; i = (i + 1) % vertexCount) { |
| 467 | + forwardVertices.push(coords[i]) |
| 468 | + if (i === endEdge) break |
465 | 469 | } |
466 | 470 |
|
467 | | - return path |
| 471 | + const backwardVertices: Position[] = [] |
| 472 | + for (let i = startEdge; ; i = (i - 1 + vertexCount) % vertexCount) { |
| 473 | + backwardVertices.push(coords[i]) |
| 474 | + if (i === (endEdge + 1) % vertexCount) break |
| 475 | + } |
| 476 | + |
| 477 | + const shorterVertices = |
| 478 | + pathLengthThroughVertices(start, forwardVertices, end) <= pathLengthThroughVertices(start, backwardVertices, end) |
| 479 | + ? forwardVertices |
| 480 | + : backwardVertices |
| 481 | + |
| 482 | + return shorterVertices.map((c) => L.latLng(c[1], c[0])) |
468 | 483 | } |
469 | 484 |
|
470 | 485 | /** |
|
0 commit comments