From 355fdd0c0920fd919a1198935e5004b51de5887c Mon Sep 17 00:00:00 2001 From: Tomas Richtar Date: Fri, 6 Jun 2025 11:02:47 +0200 Subject: [PATCH] Frontline --- Arrow.js | 38 +++++--- ArrowPoints.js | 8 +- BasicShapes.js | 37 ++++++-- Frontline.js | 217 +++++++++++++++++++++++++--------------------- MapPolygons.js | 140 +++++++++++++++++++++++++++--- Polygon.js | 3 +- package-lock.json | 2 +- 7 files changed, 303 insertions(+), 142 deletions(-) diff --git a/Arrow.js b/Arrow.js index 9cdb650..eed5f97 100644 --- a/Arrow.js +++ b/Arrow.js @@ -2,12 +2,20 @@ export const ARROW_BODY_STYLE_CONSTANT = 1; export const ARROW_BODY_STYLE_LINEAR = 2; export const ARROW_BODY_STYLE_EXPONENTIAL = 3; + +export const METERS = 'meters'; + import * as turf from "@turf/turf"; //ARROW // Cubic interpolation source from https://www.paulinternet.nl/?page=bicubic -function cubicInterpolate(p, x) { - return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); +/** + * @param {number[]} points - An array of 4 values [p0, p1, p2, p3] representing control points. + * @param {number} t - The relative position between p1 and p2 (range typically from 0 to 1). + * @returns {number} The interpolated value. + */ +export function cubicInterpolate(p, t) { + return p[1] + 0.5 * t * (p[2] - p[0] + t * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + t * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); } function exponentialWidthCurve(normalizedPosition, range = 5, minValue = 0.1) { @@ -37,12 +45,18 @@ function linearWidthCurve(normalizedPosition, range = 1, minValue = 0.1) { * @returns {GeoJSON.Feature} - An array of points representing the arrow polygon. */ export function getArrowPolygon(arrowData, style, arrowHeadData) { - if (!style) + if (!arrowData || !(arrowData.points) || arrowData.points.length === 0) { + console.warn("getArrowPolygon: Invalid arrowData or empty points array."); + return []; + } + + if (!style) { style = { calculation: ARROW_BODY_STYLE_CONSTANT, range: 0, minValue: 0 }; + } const splinePoints = computeSplinePoints(arrowData.points, arrowData.splineStep); const { leftSidePoints, rightSidePoints } = computeSideOffsets(splinePoints, arrowData.offsetDistance, style); @@ -69,8 +83,7 @@ function averageBearing(points, count = 3) { const bearings = []; for (let i = points.length - count; i < points.length -1; i++) { if (i >= 0) { - const b = turf.bearing(turf.point(points[i]), turf.point(points[i + 1])); - bearings.push(b); + bearings.push(turf.bearing(turf.point(points[i]), turf.point(points[i + 1]))); } } @@ -90,7 +103,7 @@ function computeSplinePoints(points, splineStep = 10) { const p3 = points[i + 2] || p2; for (let j = 0; j < splineStep; j++) { const t = j / splineStep; - const lon = cubicInterpolate([p0[0], p1[0], p2[0], p3[0]], t); + const lon = cubicInterpolate([p0[0], p1[0], p2[0], p3[0]], t); const lat = cubicInterpolate([p0[1], p1[1], p2[1], p3[1]], t); result.push([lon, lat]); } @@ -112,7 +125,7 @@ function computeSideOffsets(points, offsetMeters, style) { const normalizedPosition = i / total; let localOffsetDistance; - switch (style.calculation) { + switch (style.calculation) { case ARROW_BODY_STYLE_LINEAR: localOffsetDistance = offsetMeters * linearWidthCurve(normalizedPosition, style.range, style.minValue); break; @@ -124,8 +137,8 @@ function computeSideOffsets(points, offsetMeters, style) { localOffsetDistance = offsetMeters; } - leftSidePoints.push(turf.destination(turf.point(currentPoint), localOffsetDistance, bearing - 90, { units: 'meters' }).geometry.coordinates); - rightSidePoints.push(turf.destination(turf.point(currentPoint), localOffsetDistance, bearing + 90, { units: 'meters' }).geometry.coordinates); + leftSidePoints.push(turf.destination(turf.point(currentPoint), localOffsetDistance, bearing - 90, { units: METERS }).geometry.coordinates); + rightSidePoints.push(turf.destination(turf.point(currentPoint), localOffsetDistance, bearing + 90, { units: METERS }).geometry.coordinates); } return { leftSidePoints, rightSidePoints }; @@ -133,9 +146,9 @@ function computeSideOffsets(points, offsetMeters, style) { function createIsoscelesTriangleCoords(center, baseLengthMeters, heightMeters, bearing = 0) { const halfBase = baseLengthMeters / 2; - const left = turf.destination(center, halfBase, bearing - 90, { units: 'meters' }).geometry.coordinates; - const right = turf.destination(center, halfBase, bearing + 90, { units: 'meters' }).geometry.coordinates; - const tip = turf.destination(center, heightMeters, bearing, { units: 'meters' }).geometry.coordinates; + const left = turf.destination(center, halfBase, bearing - 90, { units: METERS }).geometry.coordinates; + const right = turf.destination(center, halfBase, bearing + 90, { units: METERS }).geometry.coordinates; + const tip = turf.destination(center, heightMeters, bearing, { units: METERS }).geometry.coordinates; return [left, tip, right]; } //GEOJSON @@ -162,7 +175,6 @@ export function getArrowPolygonEuclidean( arrowData, style= undefined, arrowHeadData = undefined) { - if (!style) style = { calculation: ARROW_BODY_STYLE_CONSTANT, diff --git a/ArrowPoints.js b/ArrowPoints.js index e2c3f33..eb158a8 100644 --- a/ArrowPoints.js +++ b/ArrowPoints.js @@ -4,7 +4,7 @@ import { getArrowPolygonEuclidean } from "athena-utils/shape/Arrow.js"; import { ARROW_BODY_STYLE_CONSTANT, ARROW_BODY_STYLE_LINEAR, ARROW_BODY_STYLE_EXPONENTIAL } from "athena-utils/shape/Arrow.js"; import { getCirclePolygonEuclidean } from "athena-utils/shape/BasicShapes.js"; import { getRectanglePolygonEuclidean } from "athena-utils/shape/BasicShapes.js"; -import { getFrontline } from "athena-utils/shape/Frontline.js"; +import { getFrontlineEuclidean } from "athena-utils/shape/Frontline.js"; import { LEFT_SIDE, RIGHT_SIDE, BOTH_SIDES } from "athena-utils/shape/Frontline.js"; // Polygon merge using Turf library import {mergeTurfPolygons} from "athena-utils/shape/Polygon.js"; @@ -171,18 +171,18 @@ const mergedRectangle = mergeTurfPolygons(circlePolygon, circlePolygonB); const rectanglePoly = getRectanglePolygonEuclidean(circleCenter, rectangleSideA, rectangleSideB, rectangleRotation*-1); const rectangleToTurfPoly = toTurfPolygon(rectanglePoly); -const frontlinePolygonA = getFrontline(frontlineDataA); +const frontlinePolygonA = getFrontlineEuclidean(frontlineDataA); let frontlinePolygonMergedA = toTurfPolygon(frontlinePolygonA.body); -const frontlinePolygonB = getFrontline(frontlineDataB, protrusionDataB); +const frontlinePolygonB = getFrontlineEuclidean(frontlineDataB, protrusionDataB); let frontlinePolygonMergedB = mergeTurfPolygons(frontlinePolygonB.body,frontlinePolygonB.protrusions[0]); for (let i = 1; i < frontlinePolygonB.protrusions.length; i++) { frontlinePolygonMergedB = addTurfPolygonToMerge(frontlinePolygonMergedB, frontlinePolygonB.protrusions[i]); } -const frontlinePolygonC = getFrontline(frontlineDataC, protrusionDataC); +const frontlinePolygonC = getFrontlineEuclidean(frontlineDataC, protrusionDataC); let frontlinePolygonMergedLeft = mergeTurfPolygons(frontlinePolygonC.bodyLeft, frontlinePolygonC.protrusionsLeft[0]); for (let i = 1; i < frontlinePolygonC.protrusionsLeft.length; i++) { diff --git a/BasicShapes.js b/BasicShapes.js index ff0c457..ef706d8 100644 --- a/BasicShapes.js +++ b/BasicShapes.js @@ -2,8 +2,8 @@ import * as turf from "@turf/turf"; import { toMercator, toWgs84 } from '@turf/projection'; //CIRCLE -const distancePerDegreeLongitude = 111.320; // 2π×6378.1km/360 -const distancePerDegreeLatitude = 110.574; // 2π×6356.75km/360 +const DISTANCE_PER_DEGREE_LONGITUDE = 111.320; // 2π×6378.1km/360 +const DISTANCE_PER_DEGREE_LATITUDE = 110.574; // 2π×6356.75km/360 //GEOJSON /** @@ -16,15 +16,20 @@ const distancePerDegreeLatitude = 110.574; // 2π×6356.75km/360 * @returns {GeoJSON.Feature} GeoJSON Feature representing the circle polygon. */ export function getCirclePolygon(center, radius, density = 64) { + if (!center || !radius ) { + console.warn("getCirclePolygon: Invalid center, radius."); + return []; + } + const points = []; const coords = { - latitude: center[1], // Latitude - longitude: center[0] // Longitude + latitude: center[1], + longitude: center[0] }; - const distanceX = radius / (distancePerDegreeLongitude * Math.cos(coords.latitude * Math.PI / 180)); - const distanceY = radius / distancePerDegreeLatitude; + const distanceX = radius / (DISTANCE_PER_DEGREE_LONGITUDE * Math.cos(coords.latitude * Math.PI / 180)); + const distanceY = radius / DISTANCE_PER_DEGREE_LATITUDE; for (let i = 0; i < density; i++) { const angle = (i / density) * Math.PI * 2; @@ -37,6 +42,7 @@ export function getCirclePolygon(center, radius, density = 64) { points.push(points[0]); return turf.polygon([[...points]]); + /* return { "type": "Feature", "geometry": { @@ -44,7 +50,7 @@ export function getCirclePolygon(center, radius, density = 64) { "coordinates": [points] }, "properties": {} - }; + };*/ } //GEOJSON //CANVAS @@ -58,6 +64,11 @@ export function getCirclePolygon(center, radius, density = 64) { * @returns {{x: number, y: number}[]} An array of points representing the vertices of the circle polygon. */ export function getCirclePolygonEuclidean(center, radius, density) { + if (!center || !radius || !density) { + console.warn("getCirclePolygonEuclidean: Invalid center, radius or density."); + return []; + } + const points = []; for (let i = 0; i < density; i++) { const angle = (i / density) * Math.PI * 2; @@ -83,6 +94,11 @@ export function getCirclePolygonEuclidean(center, radius, density) { * @returns {GeoJSON.Feature} GeoJSON Feature representing the rectangle polygon. */ export function getRectanglePolygon(center, width, height, rotation = 0) { + if (!center || !width || !height) { + console.warn("getRectanglePolygon: Invalid center, width or height."); + return []; + } + const widthMeters = width * 1000 ; const heightMeters = height * 1000; @@ -130,7 +146,12 @@ function rotateXY(x, y, cx, cy, angleRad) { * * @returns {{x: number, y: number}[]} An array of points representing the vertices of the rectangle polygon. */ -export function getRectanglePolygonEuclidean(center, sideA, sideB, rotation) { +export function getRectanglePolygonEuclidean(center, sideA, sideB, rotation = 0) { + if (!center || !sideA || !sideB) { + console.warn("getRectanglePolygonEuclidean: Invalid center, sideA or sideB."); + return []; + } + const halfA = sideA / 2; const halfB = sideB / 2; diff --git a/Frontline.js b/Frontline.js index 088fafa..795f876 100644 --- a/Frontline.js +++ b/Frontline.js @@ -1,25 +1,32 @@ -// Cubic interpolation source from https://www.paulinternet.nl/?page=bicubic -function cubicInterpolate(p, x) { - return p[1] + 0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); -} +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} arrowData - Object with data for arrow - * @param {{x: number, y: number}[]} arrowData.points - List of points defining the arrow's path. - * @param {number} arrowData.splineStep - The density factor for the arrow's points. - * @param {number} arrowData.spacing - The spacing between the points along the arrow. - * @param {number} arrowData.offsetDistance - The offset distance for the arrow's path (width). - * @param {number} arrowData.protrusionSize - Length of the square protrusion from the offset side. + * @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. * - * @returns {{x: number, y: number}[]} - An array of points representing the arrow polygon. */ -export function getFrontline(protrusionData) { - const style = protrusionData.style ?? LEFT_SIDE; - const splinePoints = computeSplinePoints(protrusionData.points, protrusionData.splineStep); +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 = []; @@ -28,47 +35,73 @@ export function getFrontline(protrusionData) { const { leftSidePoints: leftSidePointsLeftSide, rightSidePoints: rightSidePointsLeftSide - } = computeSides(splinePoints, protrusionData.spacing, protrusionData.offsetDistance, LEFT_SIDE); - bodyPolygonLeft = [...leftSidePointsLeftSide, ...rightSidePointsLeftSide.reverse()]; + } = computeSides(splinePoints, frontlineData.offsetDistance, LEFT_SIDE); + bodyPolygonLeft = [...leftSidePointsLeftSide, ...rightSidePointsLeftSide.reverse()]; const { leftSidePoints: leftSidePointsRightSide, rightSidePoints: rightSidePointsRightSide - } = computeSides(splinePoints, protrusionData.spacing, protrusionData.offsetDistance, RIGHT_SIDE); - bodyPolygonRight = [...leftSidePointsRightSide, ...rightSidePointsRightSide.reverse()]; + } = computeSides(splinePoints, frontlineData.offsetDistance, RIGHT_SIDE); + bodyPolygonRight = [...leftSidePointsRightSide, ...rightSidePointsRightSide.reverse()]; } - const { leftSidePoints, rightSidePoints } = computeSides(splinePoints, protrusionData.spacing, protrusionData.offsetDistance, protrusionData.style); - + const { leftSidePoints, rightSidePoints } = computeSides(splinePoints, frontlineData.offsetDistance, frontlineData.style); const bodyPolygon = [...leftSidePoints, ...rightSidePoints.reverse()]; - let protrusionPolygons = []; + if (protrusionData == null) { + if (style === BOTH_SIDES) { + const polygonCoords = [ + ...bodyPolygonLeft, + ...bodyPolygonRight, + bodyPolygonLeft[0] + ]; + return turf.polygon([[...polygonCoords]]); + } else { + const polygonCoords = [ + ...bodyPolygon, + bodyPolygon[0] + ]; + return turf.polygon([[...polygonCoords]]); + } + } + const polygonCoordsLeft= [ + ...bodyPolygonLeft, + bodyPolygonLeft[0] + ]; + const polygonCoordsRight= [ + ...bodyPolygonRight, + bodyPolygonRight[0] + ]; + + const polygonCoords = [ + ...bodyPolygon, + bodyPolygon[0] + ]; if (style === LEFT_SIDE) { - protrusionPolygons = computeProtrusion(leftSidePoints, protrusionData); + return { + body: turf.polygon([[...polygonCoords]]), + protrusions: computeProtrusion(leftSidePoints, protrusionData), + }; } else if (style === RIGHT_SIDE) { - protrusionPolygons = computeProtrusion(rightSidePoints, protrusionData); - } else if (style === BOTH_SIDES){ - let protrusionPolygonsLeft = computeProtrusion(leftSidePoints, protrusionData); - let protrusionPolygonsRight = computeProtrusion(rightSidePoints, protrusionData); - //protrusionPolygonsA = [...computeProtrusion(leftSidePoints, protrusionData), ...computeProtrusion(rightSidePoints, protrusionData)]; - return { - bodyLeft: bodyPolygonLeft, - bodyRight: bodyPolygonRight, - protrusionsLeft: protrusionPolygonsLeft, - protrusionsRight: protrusionPolygonsRight - }; + return { + body: turf.polygon([[...polygonCoords]]), + protrusions: computeProtrusion(rightSidePoints, protrusionData) + }; + } else if (style === BOTH_SIDES) { + return { + bodyLeft: turf.polygon([[...polygonCoordsLeft]]), + bodyRight: turf.polygon([[...polygonCoordsRight]]), + protrusionsLeft: computeProtrusion(leftSidePoints, protrusionData), + protrusionsRight: computeProtrusion(rightSidePoints, protrusionData) + }; } - - return { - body: bodyPolygon, - protrusions: protrusionPolygons - }; } function computeSplinePoints(points, density) { - let splinePoints = []; + 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]; @@ -76,18 +109,17 @@ function computeSplinePoints(points, density) { const p2 = points[i + 1]; const p3 = points[i + 2] || p2; for (let t = 0; t <= 1; t += density) { - splinePoints.push({ - x: cubicInterpolate([p0.x, p1.x, p2.x, p3.x], t), - y: cubicInterpolate([p0.y, p1.y, p2.y, p3.y], t) - }); + 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, spacing, offsetDistance, style = LEFT_SIDE) { - let dots = []; +function computeSides(splinePoints, offsetDistance, style = LEFT_SIDE) { let leftSidePoints = []; let rightSidePoints = []; let accumulatedDistance = 0; @@ -95,98 +127,81 @@ function computeSides(splinePoints, spacing, offsetDistance, style = LEFT_SIDE) for (let i = 1; i < splinePoints.length; i++) { const previousPoint = splinePoints[i - 1]; const currentPoint = splinePoints[i]; - const segmentLength = Math.hypot(currentPoint.x - previousPoint.x, currentPoint.y - previousPoint.y); - accumulatedDistance += segmentLength; - if (accumulatedDistance >= spacing || i === 1 || i === splinePoints.length - 1) { - const distanceX = currentPoint.y - previousPoint.y; - const distanceY = previousPoint.x - currentPoint.x; - const length = Math.hypot(distanceX, distanceY); - - let localOffsetDistance = offsetDistance; - const offsetX = (distanceX / length) * localOffsetDistance; - const offsetY = (distanceY / length) * localOffsetDistance; + const segmentDistance = turf.distance(turf.point(previousPoint), turf.point(currentPoint), { units: METERS }); + accumulatedDistance += segmentDistance; + const bearing = turf.bearing(turf.point(previousPoint), turf.point(currentPoint)); + let leftPoint, rightPoint; - dots.push({ x: currentPoint.x + offsetX, y: currentPoint.y + offsetY }); - dots.push({ x: currentPoint.x - offsetX, y: currentPoint.y - offsetY }); - - accumulatedDistance = 0; - - if (style === LEFT_SIDE) { - leftSidePoints.push({ x: currentPoint.x + offsetX, y: currentPoint.y + offsetY}); - rightSidePoints.push({ x: currentPoint.x, y: currentPoint.y}); - } else if (style === RIGHT_SIDE) { - leftSidePoints.push({ x: currentPoint.x, y: currentPoint.y}); - rightSidePoints.push({ x: currentPoint.x - offsetX, y: currentPoint.y - offsetY}); - } else if (style === BOTH_SIDES) { - leftSidePoints.push({ x: currentPoint.x + offsetX, y: currentPoint.y + offsetY }); - rightSidePoints.push({ x: currentPoint.x - offsetX, y: currentPoint.y - offsetY}); - } + if (style === LEFT_SIDE) { + leftPoint = turf.destination(turf.point(currentPoint), offsetDistance, bearing - 90, { units: METERS }).geometry.coordinates; + rightPoint = currentPoint; + } else if (style === RIGHT_SIDE) { + leftPoint = currentPoint; + rightPoint = turf.destination(turf.point(currentPoint), offsetDistance, bearing + 90, { units: METERS }).geometry.coordinates; + } else if (style === BOTH_SIDES) { + leftPoint = turf.destination(turf.point(currentPoint), offsetDistance, bearing - 90, { units: METERS }).geometry.coordinates; + rightPoint = turf.destination(turf.point(currentPoint), offsetDistance, bearing + 90, { units: METERS }).geometry.coordinates; } + + leftSidePoints.push(leftPoint); + rightSidePoints.push(rightPoint); + accumulatedDistance = 0; } return { leftSidePoints, rightSidePoints }; } function computeProtrusion(leftSidePoints, protrusionData) { - let protrusions = []; - + 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 dx = p1.x - p0.x; - const dy = p1.y - p0.y; - const length = Math.hypot(dx, dy); - segments.push({ p0, p1, dx, dy, length }); + 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.protrusionGap + protrusionData.protrusionStartSize); d += protrusionData.protrusionGap) { - positions.push(d + protrusionData.protrusionStartSize); + 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.protrusionStartSize); + 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) { + while (currentSegmentIndex < segments.length && currentSegmentPos + segments[currentSegmentIndex].length < distance) { currentSegmentPos += segments[currentSegmentIndex].length; currentSegmentIndex++; } - if (currentSegmentIndex >= segments.length) break; + if (currentSegmentIndex >= segments.length) { + break; + } const seg = segments[currentSegmentIndex]; const localDistance = distance - currentSegmentPos; - const t = localDistance / seg.length; + const pointOnSegment = turf.along(turf.lineString([seg.p0, seg.p1]), localDistance, { units: METERS }).geometry.coordinates; + const normalBearing = seg.bearing - 90; + const tangentBearing = seg.bearing; + const centerPoint = turf.destination(turf.point(pointOnSegment), protrusionData.length, normalBearing, { units: METERS }).geometry.coordinates; - const x = seg.p0.x + seg.dx * t; - const y = seg.p0.y + seg.dy * t; + const corner1 = turf.destination(turf.point(pointOnSegment), -protrusionData.startSize, tangentBearing, { units: METERS }).geometry.coordinates; + const corner2 = turf.destination(turf.point(pointOnSegment), protrusionData.startSize, tangentBearing, { units: METERS }).geometry.coordinates; + const corner3 = turf.destination(turf.point(centerPoint), protrusionData.endSize, tangentBearing, { units: METERS }).geometry.coordinates; + const corner4 = turf.destination(turf.point(centerPoint), -protrusionData.endSize, tangentBearing, { units: METERS }).geometry.coordinates; - const nx = -seg.dy / seg.length; - const ny = seg.dx / seg.length; - - const centerX = x - nx * protrusionData.protrusionLength; - const centerY = y - ny * protrusionData.protrusionLength; - - const ux = seg.dx / seg.length; - const uy = seg.dy / seg.length; - - const corner1 = { x: x - ux * protrusionData.protrusionStartSize - nx * 0, y: y - uy * protrusionData.protrusionStartSize - ny * 0 }; - const corner2 = { x: x + ux * protrusionData.protrusionStartSize - nx * 0, y: y + uy * protrusionData.protrusionStartSize - ny * 0 }; - const corner3 = { x: centerX + ux * protrusionData.protrusionEndSize, y: centerY + uy * protrusionData.protrusionEndSize }; - const corner4 = { x: centerX - ux * protrusionData.protrusionEndSize, y: centerY - uy * protrusionData.protrusionEndSize }; - - protrusions.push([corner1, corner2, corner3, corner4]); + const polygonCoords = [corner1, corner2, corner3, corner4, corner1]; + const polygon = turf.polygon([polygonCoords]); + protrusions.push(polygon); } return protrusions; diff --git a/MapPolygons.js b/MapPolygons.js index 021dd69..408169b 100644 --- a/MapPolygons.js +++ b/MapPolygons.js @@ -1,42 +1,134 @@ +//import { getArrowPolygon } from "athena-utils/shape/Arrow.js"; import { getArrowPolygon } from "athena-utils/shape/Arrow.js"; import { ARROW_BODY_STYLE_CONSTANT, ARROW_BODY_STYLE_LINEAR, ARROW_BODY_STYLE_EXPONENTIAL } from "athena-utils/shape/Arrow.js"; import { getCirclePolygon } from "athena-utils/shape/BasicShapes.js"; import { getRectanglePolygon } from "athena-utils/shape/BasicShapes.js"; +import { getFrontline } from "athena-utils/shape/Frontline.js"; +import { LEFT_SIDE, RIGHT_SIDE, BOTH_SIDES } from "athena-utils/shape/Frontline.js"; mapboxgl.accessToken = 'pk.eyJ1Ijoib3V0ZG9vcm1hcHBpbmdjb21wYW55IiwiYSI6ImNqYmh3cDdjYzNsMnozNGxsYzlvMmk2bTYifQ.QqcZ4LVoLWnXafXdjZxnZg'; - const map = new mapboxgl.Map({ - container: 'map', - center: [10, 50], - zoom: 5 - }); +const map = new mapboxgl.Map({ + container: 'map', + center: [10, 50], + zoom: 5 +}); map.on('load', () => { const fullPolygon = getArrowPolygon(arrowData, style, arrowHeadData); const circleGeoJSON = getCirclePolygon(circleCenter, circleRadius, circleDensity); const rectangleGeoJSON = getRectanglePolygon([20, 80], 2200, 2200); const rectangleBGeoJSON = getRectanglePolygon([20, 20], 2200, 2200); + const frontlineGeoJSON = getFrontline(frontlineData, protrusionData); + + var frontlineFeatureCollection = []; + var frontlineFeatureCollectionA = []; + + if (frontlineData.style === BOTH_SIDES) { + frontlineFeatureCollection = { + type: "FeatureCollection", + features: [ + frontlineGeoJSON.bodyLeft, + ...frontlineGeoJSON.protrusionsLeft + ] + }; + frontlineFeatureCollectionA = { + type: "FeatureCollection", + features: [ + frontlineGeoJSON.bodyRight, + ...frontlineGeoJSON.protrusionsRight + ] + }; + }else { + frontlineFeatureCollection = { + type: "FeatureCollection", + features: [ + frontlineGeoJSON.body, + ...frontlineGeoJSON.protrusions + ] + }; + }; + + // FRONTLINE + map.addSource("frontlinePolygon", { + "type": "geojson", + "data": frontlineFeatureCollection + }); - //ARROW - map.addSource("arrow-shape", { type: "geojson", data: fullPolygon }); map.addLayer({ - "id": "arrow-shape", + "id": "frontlinePolygon", "type": "fill", - "source": "arrow-shape", + "source": "frontlinePolygon", + "layout": {}, "paint": { - "fill-color": "#ff0000", - "fill-opacity": 0.7 + "fill-color": "blue", + "fill-opacity": 0.6 } }); map.addLayer({ - "id": "arrow-outline", + "id": "frontlinePolygon-outline", "type": "line", - "source": "arrow-shape", + "source": "frontlinePolygon", "paint": { "line-color": "#000000", "line-width": 2, "line-opacity": 1 } }); + + if (frontlineData.style === BOTH_SIDES) { + map.addSource("frontlinePolygonA", { + "type": "geojson", + "data": frontlineFeatureCollectionA + }); + + map.addLayer({ + "id": "frontlinePolygonA", + "type": "fill", + "source": "frontlinePolygonA", + "layout": {}, + "paint": { + "fill-color": "green", + "fill-opacity": 0.6 + } + }); + map.addLayer({ + "id": "frontlinePolygonA-outline", + "type": "line", + "source": "frontlinePolygonA", + "paint": { + "line-color": "#000000", + "line-width": 2, + "line-opacity": 1 + } + }); + } + // FRONTLINE + + //ARROW + if (fullPolygon.length != 0) + { + map.addSource("arrow-shape", { type: "geojson", data: fullPolygon }); + + map.addLayer({ + "id": "arrow-shape", + "type": "fill", + "source": "arrow-shape", + "paint": { + "fill-color": "#ff0000", + "fill-opacity": 0.7 + } + }); + map.addLayer({ + "id": "arrow-outline", + "type": "line", + "source": "arrow-shape", + "paint": { + "line-color": "#000000", + "line-width": 2, + "line-opacity": 1 + } + }); + } //ARROW // CIRCLE @@ -161,11 +253,31 @@ const arrowData = { }; //ARROW +//FRONTLINE +const frontlinePoints = [ + [10.42076, 40.08804], + [25.42076, 80.08804], + [65.42076, 75.08804] + ]; +const frontlineData = { + points: frontlinePoints, + splineStep: 0.08, + offsetDistance: 20000, + style: BOTH_SIDES, + }; +const protrusionData = { + length: 30000, + startSize: 10000, + endSize: 1000, + gap: 30000, +}; +//FRONTLINE + //CIRCLE const circleCenter = [20, 80]; const circleRadius = 120; const circleDensity = 20; -//CORCLE +//CIRCLE //MAP GRID function generateLatLonGrid(step = 10) { diff --git a/Polygon.js b/Polygon.js index faa7719..0b2313b 100644 --- a/Polygon.js +++ b/Polygon.js @@ -75,5 +75,6 @@ export function mergePolygonFeatures(features) { if (features.length === 1) return features[0]; + console.log("3" + features.length) return turf.union(turf.featureCollection(features)); -} \ No newline at end of file +} diff --git a/package-lock.json b/package-lock.json index c78ee40..4833b0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2718,7 +2718,7 @@ "dev": true }, "node_modules/athena-utils": { - "resolved": "git+https://git.projectathena.ca/andyaxxe/athena-utils.git#284cea819e", + "resolved": "git+https://git.projectathena.ca/andyaxxe/athena-utils.git#541d465195", "dev": true, "dependencies": { "@turf/turf": "7.2.0"