diff --git a/MapArrow.js b/MapArrow.js index 30b7d6e..4265011 100644 --- a/MapArrow.js +++ b/MapArrow.js @@ -9,12 +9,7 @@ import * as turf from "@turf/turf"; import { ARROW_BODY_STYLE_CONSTANT, ARROW_BODY_STYLE_LINEAR, ARROW_BODY_STYLE_EXPONENTIAL } from "./Arrow.js"; map.on('load', () => { - const points = [ - [1.42076, 40.08804], - [358.4050, 50.52] - ]; - - const fullPolygon = getArrowPolygon(points, 20000); // offset 20 km + const fullPolygon = getArrowPolygon(arrowData, style, arrowHeadData); map.addSource("arrow-shape", { type: "geojson", data: fullPolygon }); map.addLayer({ id: "arrow-shape", @@ -53,26 +48,27 @@ map.on('load', () => { }); const points = [ - { x: 70, y: 38 }, - { x: 71, y: 45}, - { x: 65, y: 50 }, - { x: 70, y: 53} -]; + [1.42076, 40.08804], + [15.42076, 80.08804], + [55.42076, 75.08804], + [120.42076, 40.08804], + [358.4050, 50.52] + ]; const arrowData = { points: points, - splineStep: 0.01, + splineStep: 20, spacing: 0.01, - offsetDistance: 10000 + offsetDistance: 20000 }; const style = { - calculation: ARROW_BODY_STYLE_LINEAR, + calculation: ARROW_BODY_STYLE_CONSTANT, range: 1, minValue: 0.1 }; const arrowHeadData = { - widthArrow: 1, - lengthArrow: 1 + widthArrow: 10, + lengthArrow: 5 }; // Cubic interpolation source from https://www.paulinternet.nl/?page=bicubic @@ -98,18 +94,28 @@ function cubicInterpolate(p, x) { * * @returns {{x: number, y: number}[]} - An array of points representing the arrow polygon. */ -export function getArrowPolygon(arrowData, style= undefined, arrowHeadData = undefined) { - const splineStep = 20; - const smooth = computeSplinePoints(arrowData.points, splineStep); - const { leftSidePoints, rightSidePoints } = computeSideOffsets(smooth, arrowData.offsetDistance); +export function getArrowPolygon(arrowData, style, arrowHeadData) { + if (!style) + style = { + calculation: ARROW_BODY_STYLE_CONSTANT, + range: 0, + minValue: 0 + }; - const end = smooth[smooth.length -1]; - const bearing = averageBearing(smooth, 3); - const triangle = createIsoscelesTriangleCoords(turf.point(end), arrowData.offsetDistance * 5, arrowData.offsetDistance * 5, bearing); + const splinePoints = computeSplinePoints(arrowData.points, arrowData.splineStep); + const { leftSidePoints, rightSidePoints } = computeSideOffsets(splinePoints, arrowData.offsetDistance, style); + + const end = splinePoints[splinePoints.length -1]; + const bearing = averageBearing(splinePoints, 3); + const arrowHead= arrowHeadData + ? createIsoscelesTriangleCoords( + turf.point(end), + arrowData.offsetDistance * arrowHeadData.widthArrow, arrowData.offsetDistance * arrowHeadData.lengthArrow, bearing) + : []; const polygonCoords = [ ...leftSidePoints, - ...triangle, + ...arrowHead, ...rightSidePoints.reverse(), leftSidePoints[0] ]; @@ -125,13 +131,13 @@ function averageBearing(points, count = 3) { bearings.push(b); } } - // Průměr s korekcí kruhového rozsahu + const sinSum = bearings.reduce((sum, b) => sum + Math.sin(b * Math.PI / 180), 0); const cosSum = bearings.reduce((sum, b) => sum + Math.cos(b * Math.PI / 180), 0); return Math.atan2(sinSum, cosSum) * 180 / Math.PI; } -function computeSplinePoints(points, segments = 10) { +function computeSplinePoints(points, splineStep = 10) { if (points.length < 2) return points; const result = []; @@ -140,8 +146,8 @@ function computeSplinePoints(points, segments = 10) { const p1 = points[i]; const p2 = points[i + 1]; const p3 = points[i + 2] || p2; - for (let j = 0; j < segments; j++) { - const t = j / segments; + for (let j = 0; j < splineStep; j++) { + const t = j / splineStep; 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]); @@ -152,16 +158,32 @@ function computeSplinePoints(points, segments = 10) { return result; } -function computeSideOffsets(points, offsetMeters) { +function computeSideOffsets(points, offsetMeters, style) { let leftSidePoints = []; let rightSidePoints = []; + const total = points.length - 1; for (let i = 1; i < points.length; i++) { const previousPoint = points[i - 1]; const currentPoint = points[i]; const bearing = turf.bearing(turf.point(previousPoint), turf.point(currentPoint)); - leftSidePoints.push(turf.destination(turf.point(currentPoint), offsetMeters, bearing - 90, { units: 'meters' }).geometry.coordinates); - rightSidePoints.push(turf.destination(turf.point(currentPoint), offsetMeters, bearing + 90, { units: 'meters' }).geometry.coordinates); + const normalizedPosition = i / total; + + let localOffsetDistance; + switch (style.calculation) { + case ARROW_BODY_STYLE_LINEAR: + localOffsetDistance = offsetMeters * linearWidthCurve(normalizedPosition, style.range, style.minValue); + break; + case ARROW_BODY_STYLE_EXPONENTIAL: + localOffsetDistance = offsetMeters * exponentialWidthCurve(normalizedPosition, style.range, style.minValue); + break; + case ARROW_BODY_STYLE_CONSTANT: + default: + 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); } return { leftSidePoints, rightSidePoints }; @@ -202,4 +224,12 @@ function generateLatLonGrid(step = 10) { type: "FeatureCollection", features }; +} + +function exponentialWidthCurve(normalizedPosition, range = 5, minValue = 0.1) { + return minValue + (1 - minValue) * Math.exp(-range * normalizedPosition); +} + +function linearWidthCurve(normalizedPosition, range = 1, minValue = 0.1) { + return 1 + (minValue - 1) * normalizedPosition / range ; } \ No newline at end of file