planefill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +207 -0
- package/dist/index.cjs +588 -0
- package/dist/index.cjs.map +7 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.mjs +556 -0
- package/dist/index.mjs.map +7 -0
- package/package.json +53 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.js", "../src/planner.js", "../src/strategies/boustrophedon.js", "../src/utils/coords.js", "../src/utils/geometry.js", "../src/strategies/outerIn.js", "../src/strategies/innerOut.js", "../src/strategies/hilbert.js", "../src/strategies/gridSpiral.js", "../src/utils/simplify.js", "../src/multi.js"],
|
|
4
|
+
"sourcesContent": ["export { planPath, STRATEGIES } from './planner.js';\nexport { planMultiPath, dividePolygon, DIVISION_STRATEGIES } from './multi.js';\n", "import * as turf from '@turf/turf';\nimport boustrophedon from './strategies/boustrophedon.js';\nimport outerIn from './strategies/outerIn.js';\nimport innerOut from './strategies/innerOut.js';\nimport hilbert from './strategies/hilbert.js';\nimport gridSpiral from './strategies/gridSpiral.js';\nimport { simplifyPath } from './utils/simplify.js';\n\n/**\n * @typedef {'boustrophedon'|'diagonal'|'outer-in'|'inner-out'|'hilbert'|'grid-spiral'} Strategy\n */\n\n/**\n * Map of strategy name \u2192 implementation function.\n * Each function receives (Feature<Polygon>, { spacing, angle }) and returns\n * Feature<LineString>.\n *\n * @type {Record<Strategy, (polygon: import('@turf/turf').Feature<import('@turf/turf').Polygon>, opts: { spacing: number, angle: number }) => import('@turf/turf').Feature<import('@turf/turf').LineString>>}\n */\nexport const STRATEGIES = {\n boustrophedon: (polygon, opts) => boustrophedon(polygon, opts),\n diagonal: (polygon, opts) => boustrophedon(polygon, { ...opts, angle: 45 }),\n 'outer-in': (polygon, opts) => outerIn(polygon, opts),\n 'inner-out': (polygon, opts) => innerOut(polygon, opts),\n hilbert: (polygon, opts) => hilbert(polygon, opts),\n 'grid-spiral': (polygon, opts) => gridSpiral(polygon, opts),\n};\n\n/**\n * Plan a coverage path over a polygon using the specified strategy.\n *\n * @param {object} geojson\n * GeoJSON Feature<Polygon>, Feature<MultiPolygon>, raw Polygon geometry,\n * or raw MultiPolygon geometry.\n *\n * @param {{ strategy?: Strategy, spacing: number, angle?: number }} options\n * @param {Strategy} [options.strategy='boustrophedon'] - coverage algorithm\n * @param {number} options.spacing - row / ring separation in metres (required, > 0)\n * @param {number} [options.angle=0] - sweep rotation in degrees CW from East\n * (only used by 'boustrophedon' and 'diagonal')\n *\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n * A GeoJSON Feature<LineString> with `properties.strategy` and `properties.spacing`.\n *\n * @throws {Error} if spacing is missing / non-positive\n * @throws {Error} if strategy is unknown\n * @throws {Error} if geometry type is not Polygon or MultiPolygon\n */\nexport function planPath(geojson, options = {}) {\n const { strategy = 'boustrophedon', spacing, angle = 0 } = options;\n\n if (!spacing || spacing <= 0) {\n throw new Error('options.spacing must be a positive number (metres)');\n }\n if (!(strategy in STRATEGIES)) {\n throw new Error(\n `Unknown strategy \"${strategy}\". Valid strategies: ${Object.keys(STRATEGIES).join(', ')}`\n );\n }\n\n const polygon = normalizeToPolygon(geojson);\n\n const stratFn = STRATEGIES[strategy];\n let result;\n try {\n result = stratFn(polygon, { spacing, angle });\n } catch (err) {\n // Re-throw with strategy context\n throw new Error(`Strategy \"${strategy}\" failed: ${err.message}`);\n }\n\n // Guard against degenerate output (< 2 coords)\n if (!result || result.geometry.coordinates.length < 2) {\n const c = turf.centroid(polygon).geometry.coordinates;\n return turf.lineString([c, c], { strategy, spacing, degenerate: true });\n }\n\n // Simplify: remove waypoints that deviate less than 12% of spacing from the\n // straight line between their neighbours (Ramer-Douglas-Peucker).\n const simplified = simplifyPath(result.geometry.coordinates, spacing * 0.12);\n result = turf.lineString(\n simplified.length >= 2 ? simplified : result.geometry.coordinates,\n result.properties\n );\n\n // Attach metadata\n result.properties = { ...result.properties, strategy, spacing };\n return result;\n}\n\n// \u2500\u2500 Internal helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction normalizeToPolygon(geojson) {\n if (!geojson) throw new Error('geojson is required');\n\n // Unwrap bare geometries into Features\n const feature =\n geojson.type === 'Feature'\n ? geojson\n : { type: 'Feature', geometry: geojson, properties: {} };\n\n const geom = feature.geometry;\n if (!geom) throw new Error('GeoJSON feature has no geometry');\n\n if (geom.type === 'Polygon') return feature;\n\n if (geom.type === 'MultiPolygon') {\n // Use the polygon with the greatest area\n let best = null;\n let bestArea = -Infinity;\n for (const coords of geom.coordinates) {\n const poly = turf.polygon(coords);\n const a = turf.area(poly);\n if (a > bestArea) { bestArea = a; best = poly; }\n }\n if (!best) throw new Error('MultiPolygon contains no valid polygons');\n return best;\n }\n\n throw new Error(\n `Unsupported geometry type \"${geom.type}\". planPath accepts Polygon or MultiPolygon.`\n );\n}\n", "import * as turf from '@turf/turf';\nimport { metersToDegreesLat } from '../utils/coords.js';\nimport { clipScanLine } from '../utils/geometry.js';\n\n/**\n * Boustrophedon (lawnmower / back-and-forth) coverage path.\n *\n * The polygon is rotated by `-angle`, swept with horizontal scan lines at\n * `spacing` intervals, then the resulting path is rotated back by `+angle`.\n * This allows any sweep direction.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {{ spacing: number, angle?: number }} options\n * spacing \u2013 row separation in metres\n * angle \u2013 sweep direction in degrees clockwise from North (default 0 = E\u2013W rows)\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport default function boustrophedon(polygon, { spacing, angle = 0 }) {\n const centroidPt = turf.centroid(polygon);\n\n // Rotate the polygon to align rows with the x-axis\n const rotated = angle !== 0\n ? turf.transformRotate(polygon, -angle, { pivot: centroidPt })\n : polygon;\n\n const [minX, minY, maxX, maxY] = turf.bbox(rotated);\n const spacingDeg = metersToDegreesLat(spacing);\n\n // Build y-values for scan lines, starting half a spacing from the bottom\n const ys = [];\n for (let y = minY + spacingDeg / 2; y < maxY + spacingDeg / 2; y += spacingDeg) {\n ys.push(y);\n }\n\n if (ys.length === 0) {\n // Polygon is smaller than one spacing \u2014 return a degenerate path\n const c = centroidPt.geometry.coordinates;\n return turf.lineString([c, c]);\n }\n\n const coords = [];\n let leftToRight = true;\n\n for (const y of ys) {\n const segments = clipScanLine(rotated, y, minX, maxX);\n if (segments.length === 0) continue;\n\n if (leftToRight) {\n // Sweep left \u2192 right; segments are already sorted by x\n for (const [x0, x1] of segments) {\n coords.push([x0, y]);\n coords.push([x1, y]);\n }\n } else {\n // Sweep right \u2192 left; reverse segment order and each individual segment\n for (const [x0, x1] of [...segments].reverse()) {\n coords.push([x1, y]);\n coords.push([x0, y]);\n }\n }\n\n leftToRight = !leftToRight;\n }\n\n if (coords.length < 2) {\n const c = centroidPt.geometry.coordinates;\n return turf.lineString([c, c]);\n }\n\n // Rotate path back to the original orientation\n const path = turf.lineString(coords);\n return angle !== 0\n ? turf.transformRotate(path, angle, { pivot: centroidPt })\n : path;\n}\n", "/**\n * Convert metres to approximate degrees of latitude.\n * 1 degree latitude \u2248 111 km everywhere on Earth.\n * @param {number} meters\n * @returns {number} degrees\n */\nexport function metersToDegreesLat(meters) {\n return meters / 111000;\n}\n\n/**\n * Convert metres to approximate degrees of longitude at a given latitude.\n * @param {number} meters\n * @param {number} latDeg - latitude in decimal degrees\n * @returns {number} degrees\n */\nexport function metersToDegreesLng(meters, latDeg) {\n const cosLat = Math.cos((latDeg * Math.PI) / 180);\n if (Math.abs(cosLat) < 1e-10) return 0; // at poles\n return meters / (111000 * cosLat);\n}\n", "import * as turf from '@turf/turf';\n\n/**\n * Clip a horizontal scan line at latitude `y` against `polygon`.\n * Returns an array of [x_start, x_end] pairs (sorted left\u2192right) for\n * segments that lie INSIDE the polygon. Uses the even-odd rule so it\n * handles concave polygons and polygons with holes.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {number} y - latitude of scan line\n * @param {number} xMin - left bound of polygon bbox\n * @param {number} xMax - right bound of polygon bbox\n * @returns {Array<[number, number]>} sorted x-pairs for inside segments\n */\nexport function clipScanLine(polygon, y, xMin, xMax) {\n // Tiny epsilon keeps the scan line off polygon vertices, preventing\n // degenerate double-intersections at vertex crossings.\n const EPS = 1e-10;\n const scanLine = turf.lineString([\n [xMin - 1, y + EPS],\n [xMax + 1, y + EPS],\n ]);\n\n const hits = turf.lineIntersect(scanLine, polygon);\n if (!hits || hits.features.length === 0) return [];\n\n // Collect x values of all intersection points and sort left\u2192right\n let xs = hits.features\n .map(f => f.geometry.coordinates[0])\n .sort((a, b) => a - b);\n\n // Deduplicate (handles vertex-touching cases)\n xs = xs.filter((x, i) => i === 0 || Math.abs(x - xs[i - 1]) > 1e-8);\n\n // Even-odd rule: pairs (0,1), (2,3), \u2026 are inside segments.\n // An odd total means a degenerate touch \u2014 drop the unpaired point.\n if (xs.length % 2 !== 0) xs.pop();\n\n const segments = [];\n for (let i = 0; i + 1 < xs.length; i += 2) {\n segments.push([xs[i], xs[i + 1]]);\n }\n return segments;\n}\n\n/**\n * Find the index of the point in `coords` (array of [lng, lat]) that is\n * closest (squared Euclidean distance) to `target`.\n *\n * @param {number[][]} coords\n * @param {number[]} target - [lng, lat]\n * @returns {number} index\n */\nexport function findClosestIndex(coords, target) {\n let bestIdx = 0;\n let bestDist = Infinity;\n for (let i = 0; i < coords.length; i++) {\n const dx = coords[i][0] - target[0];\n const dy = coords[i][1] - target[1];\n const d = dx * dx + dy * dy;\n if (d < bestDist) { bestDist = d; bestIdx = i; }\n }\n return bestIdx;\n}\n\n/**\n * Re-order a closed ring (first === last allowed) so it starts at `startIndex`.\n * Returns an open ring (no closing duplicate).\n *\n * @param {number[][]} coords\n * @param {number} startIndex\n * @returns {number[][]}\n */\nexport function reorderRing(coords, startIndex) {\n // Strip the closing duplicate if present\n const isClosed =\n coords[0][0] === coords[coords.length - 1][0] &&\n coords[0][1] === coords[coords.length - 1][1];\n const ring = isClosed ? coords.slice(0, -1) : coords.slice();\n return [...ring.slice(startIndex), ...ring.slice(0, startIndex)];\n}\n\n/**\n * From a Feature that may be Polygon or MultiPolygon, return the polygon\n * with the largest area. Returns null if the feature is invalid.\n *\n * @param {import('@turf/turf').Feature} feature\n * @returns {import('@turf/turf').Feature<import('@turf/turf').Polygon> | null}\n */\nexport function getLargestPolygon(feature) {\n if (!feature) return null;\n const geom = feature.geometry ?? feature;\n if (!geom) return null;\n\n if (geom.type === 'Polygon') {\n return { type: 'Feature', geometry: geom, properties: {} };\n }\n\n if (geom.type === 'MultiPolygon') {\n let best = null;\n let bestArea = -Infinity;\n for (const coords of geom.coordinates) {\n const poly = turf.polygon(coords);\n const a = turf.area(poly);\n if (a > bestArea) { bestArea = a; best = poly; }\n }\n return best;\n }\n\n return null;\n}\n", "import * as turf from '@turf/turf';\nimport { findClosestIndex, reorderRing, getLargestPolygon } from '../utils/geometry.js';\n\n/**\n * Outer-in coverage: trace concentric rings from the polygon boundary inward.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {{ spacing: number }} options\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport default function outerIn(polygon, { spacing }) {\n const rings = collectRings(polygon, spacing);\n if (rings.length === 0) {\n const c = turf.centroid(polygon).geometry.coordinates;\n return turf.lineString([c, c]);\n }\n return connectRings(rings);\n}\n\n/**\n * Erode `polygon` repeatedly by `spacingMeters`, collecting the exterior ring\n * of each result. Returns rings in outer-first order.\n *\n * Exported so innerOut.js can reuse it without duplication.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {number} spacingMeters\n * @returns {number[][][]} array of ring coordinate arrays\n */\nexport function collectRings(polygon, spacingMeters) {\n const spacingKm = spacingMeters / 1000;\n const rings = [];\n\n // Ring 0: the original polygon exterior\n rings.push(polygon.geometry.coordinates[0].slice());\n\n let current = polygon;\n // Safety cap: never more than 1 000 rings (covers a 1 km polygon at 1 m spacing)\n for (let i = 0; i < 1000; i++) {\n let eroded;\n try {\n eroded = turf.buffer(current, -spacingKm, { units: 'kilometers', steps: 16 });\n } catch {\n break;\n }\n if (!eroded) break;\n\n const largest = getLargestPolygon(eroded);\n if (!largest) break;\n\n const outerRing = largest.geometry.coordinates[0];\n if (!outerRing || outerRing.length < 4) break;\n\n rings.push(outerRing.slice());\n current = largest;\n }\n\n return rings;\n}\n\n/**\n * Connect an ordered array of rings into a single continuous open spiral.\n *\n * Each ring is traced as an open polygon (no closing segment) to avoid\n * self-intersections at ring seams. The last vertex of ring i connects\n * to the closest vertex on ring i+1 (the transition segment). Because\n * the rings are concentric and convex, this transition cannot cross any\n * previously traced segment.\n *\n * @param {number[][][]} rings - ring coordinate arrays (may be closed or open)\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport function connectRings(rings) {\n const path = [];\n\n for (const ring of rings) {\n // Work with an open ring (strip closing duplicate when present)\n const isClosed =\n ring[0][0] === ring[ring.length - 1][0] &&\n ring[0][1] === ring[ring.length - 1][1];\n const open = isClosed ? ring.slice(0, -1) : ring.slice();\n\n if (path.length === 0) {\n path.push(...open);\n } else {\n const lastPt = path[path.length - 1];\n const startIdx = findClosestIndex(open, lastPt);\n // reorderRing returns an open ring starting at startIdx\n path.push(...reorderRing(open, startIdx));\n }\n }\n\n if (path.length < 2) return turf.lineString([path[0], path[0]]);\n return turf.lineString(path);\n}\n", "import * as turf from '@turf/turf';\nimport { collectRings, connectRings } from './outerIn.js';\n\n/**\n * Inner-out coverage: trace concentric rings from the innermost inward offset\n * back out to the polygon boundary.\n *\n * Shares ring-collection and ring-connection logic with outerIn; simply\n * reverses the ring order before connecting.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {{ spacing: number }} options\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport default function innerOut(polygon, { spacing }) {\n const rings = collectRings(polygon, spacing);\n if (rings.length === 0) {\n const c = turf.centroid(polygon).geometry.coordinates;\n return turf.lineString([c, c]);\n }\n // Reverse: innermost ring first, outermost ring last\n return connectRings([...rings].reverse());\n}\n", "import * as turf from '@turf/turf';\nimport { metersToDegreesLat, metersToDegreesLng } from '../utils/coords.js';\n\n// Maximum Hilbert grid order allowed (n = 2^MAX_ORDER = 512).\n// At order 9, a 10 km polygon with 20 m spacing needs ~500 cells/side \u2192 fits.\n// Raising this past 9 can make the loop iterate millions of cells.\nconst MAX_N = 512;\n\n/**\n * Hilbert-curve coverage path.\n *\n * The polygon's bounding box is divided into a grid of cells with side length\n * \u2248 `spacing`. A Hilbert space-filling curve visits every cell in the smallest\n * 2^k \u00D7 2^k grid that encompasses the polygon grid, and the path is built from\n * the centres of cells that lie inside the polygon, in Hilbert order.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {{ spacing: number }} options\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport default function hilbert(polygon, { spacing }) {\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const centerLat = (minY + maxY) / 2;\n\n const cellW = metersToDegreesLng(spacing, centerLat);\n const cellH = metersToDegreesLat(spacing);\n\n const numCols = Math.max(1, Math.ceil((maxX - minX) / cellW));\n const numRows = Math.max(1, Math.ceil((maxY - minY) / cellH));\n\n // n must be the smallest power of 2 \u2265 max(numCols, numRows), capped at MAX_N\n const n = Math.min(MAX_N, nextPowerOf2(Math.max(numCols, numRows)));\n\n const insideCoords = [];\n\n for (let d = 0; d < n * n; d++) {\n const { x, y } = hilbertDToXY(n, d);\n\n // Skip cells that fall outside the actual polygon bbox grid\n if (x >= numCols || y >= numRows) continue;\n\n // Map grid cell (x, y) to the geographic centre of that cell\n const lng = minX + (x + 0.5) * cellW;\n const lat = minY + (y + 0.5) * cellH;\n\n if (turf.booleanPointInPolygon(turf.point([lng, lat]), polygon)) {\n insideCoords.push([lng, lat]);\n }\n }\n\n if (insideCoords.length === 0) {\n const c = turf.centroid(polygon).geometry.coordinates;\n return turf.lineString([c, c]);\n }\n if (insideCoords.length === 1) {\n return turf.lineString([insideCoords[0], insideCoords[0]]);\n }\n\n return turf.lineString(insideCoords);\n}\n\n// \u2500\u2500 Hilbert d \u2192 (x, y) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Convert a Hilbert curve index `d` to grid coordinates (x, y) for an n\u00D7n grid\n * where n is a power of 2.\n *\n * Classic iterative algorithm; correct for n \u2264 2^16.\n *\n * Verification (n=4):\n * d=0 \u2192 (0,0), d=1 \u2192 (1,0), d=2 \u2192 (1,1), d=3 \u2192 (0,1)\n * d=4 \u2192 (0,2), d=5 \u2192 (0,3), d=6 \u2192 (1,3), d=7 \u2192 (1,2)\n *\n * @param {number} n - grid size (power of 2)\n * @param {number} d - Hilbert index\n * @returns {{ x: number, y: number }}\n */\nfunction hilbertDToXY(n, d) {\n let x = 0, y = 0;\n let t = d;\n for (let s = 1; s < n; s *= 2) {\n const rx = 1 & Math.floor(t / 2);\n const ry = 1 & (t ^ rx);\n if (ry === 0) {\n if (rx === 1) {\n x = s - 1 - x;\n y = s - 1 - y;\n }\n const tmp = x; x = y; y = tmp; // rotate\n }\n x += s * rx;\n y += s * ry;\n t = Math.floor(t / 4);\n }\n return { x, y };\n}\n\nfunction nextPowerOf2(n) {\n if (n <= 1) return 1;\n let p = 1;\n while (p < n) p *= 2;\n return p;\n}\n", "import * as turf from '@turf/turf';\nimport { metersToDegreesLat, metersToDegreesLng } from '../utils/coords.js';\n\n/**\n * Grid-spiral coverage path.\n *\n * The polygon's bounding box is divided into a grid of square cells (same\n * cell size as the Hilbert strategy). The cell containing the centroid is\n * the starting point, and cells are visited in an outward expanding-square\n * spiral order \u2014 right, up, left, down, each side one step longer than the\n * last, repeating until the whole grid is covered.\n *\n * Because every step in the spiral moves exactly one cell (no diagonals),\n * consecutive path points are always grid-adjacent. This guarantees:\n * \u2022 The path stays inside the polygon (only inside cells are emitted).\n * \u2022 No self-intersections for convex polygons.\n * \u2022 Coverage accuracy \u2248 that of the Hilbert strategy with a more\n * visually intuitive \"target / expanding rings\" appearance.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {{ spacing: number }} options\n * spacing \u2013 cell side length in metres (same parameter as other strategies)\n * @returns {import('@turf/turf').Feature<import('@turf/turf').LineString>}\n */\nexport default function gridSpiral(polygon, { spacing }) {\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const centerLat = (minY + maxY) / 2;\n\n const cellW = metersToDegreesLng(spacing, centerLat);\n const cellH = metersToDegreesLat(spacing);\n\n const numCols = Math.max(1, Math.ceil((maxX - minX) / cellW));\n const numRows = Math.max(1, Math.ceil((maxY - minY) / cellH));\n\n // Cell containing the polygon centroid\n const [cx, cy] = turf.centroid(polygon).geometry.coordinates;\n const startCol = Math.min(numCols - 1, Math.max(0, Math.floor((cx - minX) / cellW)));\n const startRow = Math.min(numRows - 1, Math.max(0, Math.floor((cy - minY) / cellH)));\n\n // Generate spiral cell order and keep only cells inside the polygon\n const insideCoords = [];\n\n for (const [col, row] of spiralOrder(startCol, startRow, numCols, numRows)) {\n const lng = minX + (col + 0.5) * cellW;\n const lat = minY + (row + 0.5) * cellH;\n if (turf.booleanPointInPolygon(turf.point([lng, lat]), polygon)) {\n insideCoords.push([lng, lat]);\n }\n }\n\n if (insideCoords.length === 0) {\n const c = turf.centroid(polygon).geometry.coordinates;\n return turf.lineString([c, c]);\n }\n if (insideCoords.length === 1) {\n return turf.lineString([insideCoords[0], insideCoords[0]]);\n }\n\n return turf.lineString(insideCoords);\n}\n\n// \u2500\u2500 Spiral traversal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Generate (col, row) pairs in an outward expanding-square spiral starting\n * from (startCol, startRow), clamped to [0, numCols) \u00D7 [0, numRows).\n *\n * Movement pattern:\n * right 1 \u2192 up 1 \u2192 left 2 \u2192 down 2 \u2192 right 3 \u2192 up 3 \u2192 left 4 \u2192 down 4 \u2026\n *\n * Each step moves exactly one cell, so consecutive cells in the output are\n * always horizontal or vertical grid neighbours \u2014 never diagonal.\n *\n * @param {number} startCol\n * @param {number} startRow\n * @param {number} numCols\n * @param {number} numRows\n * @returns {Generator<[number, number]>}\n */\nfunction* spiralOrder(startCol, startRow, numCols, numRows) {\n // Directions: right (+col), up (+row), left (\u2212col), down (\u2212row)\n const DIRS = [[1, 0], [0, 1], [-1, 0], [0, -1]];\n\n let col = startCol;\n let row = startRow;\n yield [col, row]; // centre cell\n\n const maxRadius = Math.max(numCols, numRows);\n let dirIdx = 0; // start moving right\n\n // Each step-size is used for two consecutive direction changes:\n // stepSize=1 \u2192 right 1, up 1\n // stepSize=2 \u2192 left 2, down 2\n // stepSize=3 \u2192 right 3, up 3 \u2026\n for (let stepSize = 1; stepSize <= maxRadius * 2; stepSize++) {\n for (let turn = 0; turn < 2; turn++) {\n const [dc, dr] = DIRS[dirIdx];\n for (let i = 0; i < stepSize; i++) {\n col += dc;\n row += dr;\n if (col >= 0 && col < numCols && row >= 0 && row < numRows) {\n yield [col, row];\n }\n }\n dirIdx = (dirIdx + 1) % 4;\n }\n }\n}\n", "/**\n * Ramer-Douglas-Peucker path simplification.\n *\n * Removes waypoints whose perpendicular deviation from the straight line\n * between their neighbours is less than `toleranceMeters`. Points on straight\n * runs (deviation \u2248 0) are collapsed to just the two endpoints; corners and\n * curves are preserved.\n *\n * @param {number[][]} coords - array of [lng, lat] waypoints\n * @param {number} toleranceMeters\n * @returns {number[][]} simplified coordinate array (always \u2265 2 points)\n */\nexport function simplifyPath(coords, toleranceMeters) {\n if (coords.length <= 2) return coords;\n const result = rdp(coords, 0, coords.length - 1, toleranceMeters);\n // rdp returns the indices to keep; map back to coordinates\n return result.map(i => coords[i]);\n}\n\n// \u2500\u2500 Internal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/**\n * Recursive RDP step. Returns the indices into `coords` that should be kept.\n */\nfunction rdp(coords, lo, hi, tol) {\n if (hi - lo < 2) {\n // 0 or 1 intermediate points \u2014 nothing to drop\n const out = [];\n for (let i = lo; i <= hi; i++) out.push(i);\n return out;\n }\n\n // Find the point with maximum perpendicular distance from the chord lo\u2192hi\n let maxDist = 0;\n let maxIdx = lo;\n for (let i = lo + 1; i < hi; i++) {\n const d = ptSegDistMeters(coords[i], coords[lo], coords[hi]);\n if (d > maxDist) { maxDist = d; maxIdx = i; }\n }\n\n if (maxDist > tol) {\n // Split at the outlier and recurse\n const left = rdp(coords, lo, maxIdx, tol);\n const right = rdp(coords, maxIdx, hi, tol);\n // left includes maxIdx as its last element; right starts at maxIdx\n return [...left, ...right.slice(1)];\n }\n\n // All intermediate points are within tolerance \u2014 keep only the endpoints\n return [lo, hi];\n}\n\n/**\n * Perpendicular distance in metres from point `p` to segment `a\u2192b`.\n * Uses a flat-earth approximation (cos-scaled longitude); accurate to ~0.5%\n * for the small polygons this library operates on.\n */\nfunction ptSegDistMeters(p, a, b) {\n const M_PER_DEG_LAT = 111_000;\n const cosLat = Math.cos((p[1] * Math.PI) / 180);\n\n const px = p[0] * cosLat * M_PER_DEG_LAT, py = p[1] * M_PER_DEG_LAT;\n const ax = a[0] * cosLat * M_PER_DEG_LAT, ay = a[1] * M_PER_DEG_LAT;\n const bx = b[0] * cosLat * M_PER_DEG_LAT, by = b[1] * M_PER_DEG_LAT;\n\n const dx = bx - ax, dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n\n let cx, cy;\n if (lenSq === 0) {\n cx = px - ax; cy = py - ay;\n } else {\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n cx = px - (ax + t * dx);\n cy = py - (ay + t * dy);\n }\n return Math.sqrt(cx * cx + cy * cy);\n}\n", "import * as turf from '@turf/turf';\nimport { planPath } from './planner.js';\n\n/**\n * @typedef {'vertical'|'horizontal'|'grid'|'balanced'} DivisionStrategy\n */\n\n/** @type {DivisionStrategy[]} */\nexport const DIVISION_STRATEGIES = ['vertical', 'horizontal', 'grid', 'balanced'];\n\n/**\n * Divide a polygon into `parties` regions then plan a coverage path in each.\n *\n * @param {import('./index.d.ts').PolygonInput} geojson\n * @param {{ parties?: number, divisionStrategy?: DivisionStrategy, coverageStrategy?: import('./planner.js').Strategy, spacing: number, angle?: number }} options\n * @param {number} [options.parties=2] - number of regions / agents (\u2265 1)\n * @param {DivisionStrategy} [options.divisionStrategy='vertical'] - how to split the polygon\n * @param {import('./planner.js').Strategy} [options.coverageStrategy='boustrophedon'] - coverage algorithm for each region\n * @param {number} options.spacing - row/cell spacing in metres\n * @param {number} [options.angle=0] - sweep angle (boustrophedon/diagonal only)\n *\n * @returns {import('@turf/turf').FeatureCollection<import('@turf/turf').LineString>}\n * One LineString per non-empty region. Each feature carries:\n * properties.party \u2013 0-based region index\n * properties.strategy, properties.spacing \u2013 forwarded from planPath\n */\nexport function planMultiPath(geojson, {\n parties = 2,\n divisionStrategy = 'vertical',\n coverageStrategy = 'boustrophedon',\n spacing,\n angle = 0,\n} = {}) {\n if (!spacing || spacing <= 0) {\n throw new Error('options.spacing must be a positive number (metres)');\n }\n if (!Number.isInteger(parties) || parties < 1) {\n throw new Error('options.parties must be a positive integer');\n }\n\n const polygon = normalizeToPolygon(geojson);\n const regions = dividePolygon(polygon, parties, divisionStrategy);\n\n if (regions.length === 0) {\n throw new Error('No regions produced \u2014 polygon may be too small for the requested division');\n }\n\n const features = regions.map((region, i) => {\n const path = planPath(region, { strategy: coverageStrategy, spacing, angle });\n path.properties = { ...path.properties, party: i };\n return path;\n });\n\n return turf.featureCollection(features);\n}\n\n/**\n * Divide a polygon Feature into up to `n` non-empty sub-polygon Features.\n * Returns fewer than `n` when some divisions miss the polygon entirely.\n *\n * @param {import('@turf/turf').Feature<import('@turf/turf').Polygon>} polygon\n * @param {number} n\n * @param {DivisionStrategy} [strategy='vertical']\n * @returns {import('@turf/turf').Feature<import('@turf/turf').Polygon>[]}\n */\nexport function dividePolygon(polygon, n, strategy = 'vertical') {\n if (n === 1) return [polygon];\n\n switch (strategy) {\n case 'vertical': return stripDivide(polygon, n, 'x');\n case 'horizontal': return stripDivide(polygon, n, 'y');\n case 'grid': return gridDivide(polygon, n);\n case 'balanced': return balancedDivide(polygon, n);\n default:\n throw new Error(\n `Unknown division strategy \"${strategy}\". Valid: ${DIVISION_STRATEGIES.join(', ')}`\n );\n }\n}\n\n// \u2500\u2500 Division implementations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction stripDivide(polygon, n, axis) {\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const regions = [];\n\n for (let i = 0; i < n; i++) {\n const t0 = i / n, t1 = (i + 1) / n;\n const clip = axis === 'x'\n ? turf.bboxPolygon([minX + t0 * (maxX - minX), minY, minX + t1 * (maxX - minX), maxY])\n : turf.bboxPolygon([minX, minY + t0 * (maxY - minY), maxX, minY + t1 * (maxY - minY)]);\n\n const region = turf.intersect(polygon, clip);\n if (region) regions.push(largestPolygon(region));\n }\n\n return regions;\n}\n\nfunction gridDivide(polygon, n) {\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const cols = Math.ceil(Math.sqrt(n));\n const rows = Math.ceil(n / cols);\n const cellW = (maxX - minX) / cols;\n const cellH = (maxY - minY) / rows;\n const regions = [];\n\n outer: for (let r = 0; r < rows; r++) {\n for (let c = 0; c < cols; c++) {\n if (regions.length >= n) break outer;\n const clip = turf.bboxPolygon([\n minX + c * cellW, minY + r * cellH,\n minX + (c + 1) * cellW, minY + (r + 1) * cellH,\n ]);\n const region = turf.intersect(polygon, clip);\n if (region) regions.push(largestPolygon(region));\n }\n }\n\n return regions;\n}\n\n/**\n * Recursively split a polygon into `n` roughly equal-area pieces.\n *\n * At each level the polygon is bisected along its longer bbox dimension by a\n * line whose position is found via binary search so that the two halves\n * receive area proportional to how many leaf regions they will contain\n * (nLeft : nRight = floor(n/2) : ceil(n/2)). This means odd values of n\n * produce equal-area leaves rather than one oversized half.\n */\nfunction balancedDivide(polygon, n) {\n if (n <= 1) return [polygon];\n\n const nLeft = Math.floor(n / 2);\n const nRight = n - nLeft;\n\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const cosLat = Math.cos(((minY + maxY) / 2) * Math.PI / 180);\n const widthM = (maxX - minX) * cosLat * 111_000;\n const heightM = (maxY - minY) * 111_000;\n\n const axis = widthM >= heightM ? 'x' : 'y';\n const [half1, half2] = bisectPolygon(polygon, axis, nLeft / n);\n\n return [\n ...(half1 ? balancedDivide(half1, nLeft) : []),\n ...(half2 ? balancedDivide(half2, nRight) : []),\n ];\n}\n\n/**\n * Cut `polygon` with a vertical (axis='x') or horizontal (axis='y') line\n * positioned so the smaller side gets `targetFraction` of the total area.\n * Uses 24 iterations of binary search (~60 million\u00D7 precision).\n *\n * @returns {[Feature<Polygon>|null, Feature<Polygon>|null]}\n */\nfunction bisectPolygon(polygon, axis, targetFraction) {\n const [minX, minY, maxX, maxY] = turf.bbox(polygon);\n const totalArea = turf.area(polygon);\n const targetArea = totalArea * targetFraction;\n\n let lo = axis === 'x' ? minX : minY;\n let hi = axis === 'x' ? maxX : maxY;\n\n for (let i = 0; i < 24; i++) {\n const mid = (lo + hi) / 2;\n const clip = axis === 'x'\n ? turf.bboxPolygon([minX, minY, mid, maxY])\n : turf.bboxPolygon([minX, minY, maxX, mid ]);\n const part = turf.intersect(polygon, clip);\n const partArea = part ? turf.area(part) : 0;\n if (partArea < targetArea) lo = mid; else hi = mid;\n }\n\n const mid = (lo + hi) / 2;\n const clipLeft = axis === 'x'\n ? turf.bboxPolygon([minX, minY, mid, maxY])\n : turf.bboxPolygon([minX, minY, maxX, mid ]);\n const clipRight = axis === 'x'\n ? turf.bboxPolygon([mid, minY, maxX, maxY])\n : turf.bboxPolygon([minX, mid, maxX, maxY]);\n\n const left = turf.intersect(polygon, clipLeft);\n const right = turf.intersect(polygon, clipRight);\n return [\n left ? largestPolygon(left) : null,\n right ? largestPolygon(right) : null,\n ];\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n/** For a Polygon|MultiPolygon feature, return just the largest Polygon. */\nfunction largestPolygon(feature) {\n if (feature.geometry.type === 'Polygon') return feature;\n let best = null, bestArea = -Infinity;\n for (const coords of feature.geometry.coordinates) {\n const poly = turf.polygon(coords);\n const a = turf.area(poly);\n if (a > bestArea) { bestArea = a; best = poly; }\n }\n return best;\n}\n\nfunction normalizeToPolygon(geojson) {\n if (!geojson) throw new Error('geojson is required');\n const feature = geojson.type === 'Feature'\n ? geojson\n : { type: 'Feature', geometry: geojson, properties: {} };\n const geom = feature.geometry;\n if (!geom) throw new Error('GeoJSON feature has no geometry');\n if (geom.type === 'Polygon') return feature;\n if (geom.type === 'MultiPolygon') {\n let best = null, bestArea = -Infinity;\n for (const coords of geom.coordinates) {\n const poly = turf.polygon(coords);\n const a = turf.area(poly);\n if (a > bestArea) { bestArea = a; best = poly; }\n }\n return best;\n }\n throw new Error(`Unsupported geometry type \"${geom.type}\"`);\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["turf", "turf", "polygon", "polygon", "turf", "polygon", "turf", "polygon", "turf", "polygon", "turf", "polygon", "polygon", "turf", "polygon", "normalizeToPolygon", "mid"]
|
|
7
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { Feature, FeatureCollection, Polygon, MultiPolygon, LineString } from '@turf/turf';
|
|
2
|
+
|
|
3
|
+
// ── Shared literal types ──────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
/** Name of a coverage path strategy accepted by {@link planPath}. */
|
|
6
|
+
export type Strategy =
|
|
7
|
+
| 'boustrophedon'
|
|
8
|
+
| 'diagonal'
|
|
9
|
+
| 'outer-in'
|
|
10
|
+
| 'inner-out'
|
|
11
|
+
| 'hilbert'
|
|
12
|
+
| 'grid-spiral';
|
|
13
|
+
|
|
14
|
+
/** Name of a polygon division strategy accepted by {@link planMultiPath} and {@link dividePolygon}. */
|
|
15
|
+
export type DivisionStrategy = 'vertical' | 'horizontal' | 'grid' | 'balanced';
|
|
16
|
+
|
|
17
|
+
/** Any GeoJSON input whose geometry is (or contains) a Polygon. */
|
|
18
|
+
export type PolygonInput =
|
|
19
|
+
| Feature<Polygon>
|
|
20
|
+
| Feature<MultiPolygon>
|
|
21
|
+
| Polygon
|
|
22
|
+
| MultiPolygon;
|
|
23
|
+
|
|
24
|
+
// ── planPath ──────────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export interface PlanPathOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Which algorithm to use.
|
|
29
|
+
* @default 'boustrophedon'
|
|
30
|
+
*/
|
|
31
|
+
strategy?: Strategy;
|
|
32
|
+
/** Row / ring separation in metres. Must be > 0. */
|
|
33
|
+
spacing: number;
|
|
34
|
+
/**
|
|
35
|
+
* Sweep rotation in degrees (clockwise from East).
|
|
36
|
+
* Only used by `'boustrophedon'` and `'diagonal'`.
|
|
37
|
+
* @default 0
|
|
38
|
+
*/
|
|
39
|
+
angle?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PathProperties {
|
|
43
|
+
strategy: Strategy;
|
|
44
|
+
spacing: number;
|
|
45
|
+
/** Present and `true` only when the polygon was too small to produce a real path. */
|
|
46
|
+
degenerate?: true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Map of every available strategy name to its implementation.
|
|
51
|
+
* Useful for iterating keys to build a UI dropdown.
|
|
52
|
+
*/
|
|
53
|
+
export const STRATEGIES: Record<
|
|
54
|
+
Strategy,
|
|
55
|
+
(polygon: Feature<Polygon>, opts: { spacing: number; angle: number }) => Feature<LineString>
|
|
56
|
+
>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Plan a coverage path over a polygon using the specified strategy.
|
|
60
|
+
*
|
|
61
|
+
* @throws if `spacing` is missing or ≤ 0
|
|
62
|
+
* @throws if `strategy` is not a valid {@link Strategy}
|
|
63
|
+
* @throws if the geometry is not a Polygon or MultiPolygon
|
|
64
|
+
*/
|
|
65
|
+
export function planPath(
|
|
66
|
+
geojson: PolygonInput,
|
|
67
|
+
options: PlanPathOptions,
|
|
68
|
+
): Feature<LineString, PathProperties>;
|
|
69
|
+
|
|
70
|
+
// ── planMultiPath / dividePolygon ─────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
export interface PlanMultiPathOptions {
|
|
73
|
+
/**
|
|
74
|
+
* Number of regions / agents to divide the polygon into.
|
|
75
|
+
* @default 2
|
|
76
|
+
*/
|
|
77
|
+
parties?: number;
|
|
78
|
+
/**
|
|
79
|
+
* How to divide the polygon between parties.
|
|
80
|
+
* @default 'vertical'
|
|
81
|
+
*/
|
|
82
|
+
divisionStrategy?: DivisionStrategy;
|
|
83
|
+
/**
|
|
84
|
+
* Coverage strategy to run inside each region.
|
|
85
|
+
* @default 'boustrophedon'
|
|
86
|
+
*/
|
|
87
|
+
coverageStrategy?: Strategy;
|
|
88
|
+
/** Row / cell spacing in metres. Must be > 0. */
|
|
89
|
+
spacing: number;
|
|
90
|
+
/**
|
|
91
|
+
* Sweep rotation in degrees (clockwise from East).
|
|
92
|
+
* Only used by `'boustrophedon'` and `'diagonal'`.
|
|
93
|
+
* @default 0
|
|
94
|
+
*/
|
|
95
|
+
angle?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MultiPathProperties extends PathProperties {
|
|
99
|
+
/** 0-based index identifying which party this path belongs to. */
|
|
100
|
+
party: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Ordered list of every available division strategy name. */
|
|
104
|
+
export const DIVISION_STRATEGIES: DivisionStrategy[];
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Divide a polygon into `parties` regions and plan a coverage path in each.
|
|
108
|
+
* Returns a FeatureCollection with one LineString per non-empty region.
|
|
109
|
+
*
|
|
110
|
+
* @throws if `spacing` is missing or ≤ 0
|
|
111
|
+
* @throws if `parties` is not a positive integer
|
|
112
|
+
* @throws if no regions could be produced (polygon too small)
|
|
113
|
+
*/
|
|
114
|
+
export function planMultiPath(
|
|
115
|
+
geojson: PolygonInput,
|
|
116
|
+
options: PlanMultiPathOptions,
|
|
117
|
+
): FeatureCollection<LineString, MultiPathProperties>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Divide a polygon Feature into up to `n` non-empty sub-polygon Features.
|
|
121
|
+
* May return fewer than `n` when some divisions don't intersect the polygon.
|
|
122
|
+
*/
|
|
123
|
+
export function dividePolygon(
|
|
124
|
+
polygon: Feature<Polygon>,
|
|
125
|
+
n: number,
|
|
126
|
+
strategy?: DivisionStrategy,
|
|
127
|
+
): Feature<Polygon>[];
|