Arrow fix

This commit is contained in:
Tomas Richtar 2025-05-27 17:33:51 +02:00
parent 3129ec1fea
commit a96f4f29a9

View File

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