import { cubicInterpolate } from "athena-utils/shape/Arrow.js"; import * as turf from '@turf/turf'; export const LEFT_SIDE = 1; export const RIGHT_SIDE = 2; export const BOTH_SIDES = 3; export const METERS = 'meters'; /** * @param {Object, Object} frontlineData, protrusionData - Object containing parameters for the frontline. * @param {{x: number, y: number}[]} frontlineData.points - List of points defining the base path of the frontline. * @param {number} frontlineData.splineStep - The resolution of the spline interpolation (smaller = smoother curve). * @param {number} frontlineData.spacing - Distance between interpolated points along the path. * @param {number} frontlineData.offsetDistance - Distance to offset the entire shape from the base path. * @param {string} frontlineData.style - Which side to draw the protrusions on (e.g., "LEFT_SIDE" or "RIGHT_SIDE"). * @param {number} protrusionData.Length - Length of each individual protrusion element. * @param {number} protrusionData.StartSize - Width of protrusion at the start (base). * @param {number} protrusionData.EndSize - Width of protrusion at the end (tip). * @param {number} protrusionData.Gap - Distance between the starts of each protrusion. * */ export function getFrontline(frontlineData, protrusionData = null) { if (!frontlineData || !(frontlineData.points) || frontlineData.points.length === 0) { console.warn("getFrontline: Invalid frontlineData or empty points array."); return []; } const style = frontlineData.style ?? LEFT_SIDE; const splinePoints = computeSplinePoints(frontlineData.points, frontlineData.splineStep); let bodyPolygonLeft = []; let bodyPolygonRight = []; if (style === BOTH_SIDES) { const left = computeSides(splinePoints, frontlineData.offsetDistance, LEFT_SIDE); bodyPolygonLeft = [...left.leftSidePoints, ...left.rightSidePoints.reverse()]; const right = computeSides(splinePoints, frontlineData.offsetDistance, RIGHT_SIDE); bodyPolygonRight = [...right.leftSidePoints, ...right.rightSidePoints.reverse()]; } const { leftSidePoints, rightSidePoints } = computeSides(splinePoints, frontlineData.offsetDistance, frontlineData.style); const bodyPolygon = [...leftSidePoints, ...rightSidePoints.reverse()]; if (protrusionData == null) { let polygonCoords; if (style === BOTH_SIDES) { polygonCoords = [ ...bodyPolygonLeft, ...bodyPolygonRight, bodyPolygonLeft[0] ]; } else { polygonCoords = [ ...bodyPolygon, bodyPolygon[0] ]; } return turf.polygon([polygonCoords]); } const prostrusionsData = (points, sidePoints) => { const coords = [...points, points[0]]; const basePoly = turf.polygon([coords]); return constructProstrusions(basePoly, sidePoints, protrusionData, frontlineData); }; if (style === LEFT_SIDE) { return { rightPoly: null, leftPoly: prostrusionsData(bodyPolygon, leftSidePoints) }; } else if (style === RIGHT_SIDE) { return { rightPoly: prostrusionsData(bodyPolygon, rightSidePoints), leftPoly: null }; } else if (style === BOTH_SIDES) { return { rightPoly: prostrusionsData(bodyPolygonRight, rightSidePoints), leftPoly: prostrusionsData(bodyPolygonLeft, leftSidePoints), }; } } function constructProstrusions(mainPoly, points, protrusionData, frontlineData) { const protrusions = computeProtrusion(points, protrusionData, frontlineData.offsetDistance); for(let i = 0; i <= protrusions.length -1 ; i++) { mainPoly = turf.union(turf.featureCollection([mainPoly, protrusions[i]])); } return mainPoly; } function computeSplinePoints(points, density) { if (points.length < 2) return points; const splinePoints = []; for (let i = 0; i < points.length - 1; i++) { const p0 = points[i === 0 ? i : i - 1]; const p1 = points[i]; const p2 = points[i + 1]; const p3 = points[i + 2] || p2; for (let t = 0; t <= 1; t += density) { const lon = cubicInterpolate([p0[0], p1[0], p2[0], p3[0]], t); const lat = cubicInterpolate([p0[1], p1[1], p2[1], p3[1]], t); splinePoints.push([lon, lat]); } } splinePoints.push(points[points.length - 1]); return splinePoints; } function computeSides(splinePoints, offsetDistance, style = LEFT_SIDE) { let leftSidePoints = []; let rightSidePoints = []; for (let i = 1; i < splinePoints.length; i++) { const previousPoint = splinePoints[i - 1]; const currentPoint = splinePoints[i]; const bearing = turf.bearing(turf.point(previousPoint), turf.point(currentPoint)); const leftPoint = style === RIGHT_SIDE ? currentPoint : turf.destination(turf.point(currentPoint), offsetDistance, bearing - 90, { units: METERS }).geometry.coordinates; const rightPoint = style === LEFT_SIDE ? currentPoint : turf.destination(turf.point(currentPoint), offsetDistance, bearing + 90, { units: METERS }).geometry.coordinates; leftSidePoints.push(leftPoint); rightSidePoints.push(rightPoint); } return { leftSidePoints, rightSidePoints }; } function computeProtrusion(leftSidePoints, protrusionData, sideOffset) { const protrusions = []; const segments = []; let totalLength = 0; for (let i = 0; i < leftSidePoints.length - 1; i++) { const p0 = leftSidePoints[i]; const p1 = leftSidePoints[i + 1]; const length = turf.distance(turf.point(p0), turf.point(p1), { units: METERS }); const bearing = turf.bearing(turf.point(p0), turf.point(p1)); segments.push({ p0, p1, length, bearing }); totalLength += length; } const positions = []; for (let d = 0; d <= totalLength - (protrusionData.gap + protrusionData.startSize); d += protrusionData.gap) { positions.push(d + protrusionData.startSize); } if (positions[positions.length - 1] < totalLength) { positions.push(totalLength - protrusionData.startSize); } let currentSegmentIndex = 0; let currentSegmentPos = 0; for (const distance of positions) { while (currentSegmentIndex < segments.length && currentSegmentPos + segments[currentSegmentIndex].length < distance) { currentSegmentPos += segments[currentSegmentIndex].length; currentSegmentIndex++; } if (currentSegmentIndex >= segments.length) { break; } const seg = segments[currentSegmentIndex]; const localDistance = distance - currentSegmentPos; const pointOnSegment = turf.along(turf.lineString([seg.p0, seg.p1]), localDistance, { units: METERS }).geometry.coordinates; const thicknessOffset = sideOffset * 0.1; const adjustedPoint = movePoint(pointOnSegment, thicknessOffset, seg.bearing + 90); const centerPoint = movePoint(adjustedPoint, protrusionData.length, seg.bearing - 90); const corner1 = movePoint(adjustedPoint, -protrusionData.startSize, seg.bearing); const corner2 = movePoint(adjustedPoint, protrusionData.startSize, seg.bearing); const corner3 = movePoint(centerPoint, protrusionData.endSize, seg.bearing); const corner4 = movePoint(centerPoint, -protrusionData.endSize, seg.bearing); const polygon = turf.polygon([[corner1, corner2, corner3, corner4, corner1]]); protrusions.push(polygon); } return protrusions; } function movePoint(point, distance, bearing) { return turf.destination(turf.point(point), distance, bearing, { units: METERS }).geometry.coordinates; }