All, no clean code
This commit is contained in:
209
Arrow.js
Normal file
209
Arrow.js
Normal file
@@ -0,0 +1,209 @@
|
||||
export const ARROW_BODY_STYLE_CONSTANT = 1;
|
||||
export const ARROW_BODY_STYLE_LINEAR = 2;
|
||||
export const ARROW_BODY_STYLE_EXPONENTIAL = 3;
|
||||
|
||||
const distancePerDegreeLongitude = 111.320; // 2π×6378.1km/360
|
||||
const distancePerDegreeLatitude = 110.574; // 2π×6356.75km/360
|
||||
|
||||
import * as turf from "@turf/turf";
|
||||
|
||||
|
||||
|
||||
function round(value, decimals = 6) {
|
||||
return Number(value.toFixed(decimals));
|
||||
}
|
||||
|
||||
// Převod z lon/lat do Web Mercator (v metrech)
|
||||
function lonLatToMeters(lon, lat) {
|
||||
const originShift = 2 * Math.PI * 6378137 / 2.0;
|
||||
const mx = lon * originShift / 180.0 ;
|
||||
const my = Math.log(Math.tan((90 + lat) * Math.PI / 360.0)) / (Math.PI / 180.0)* 150;
|
||||
|
||||
//console.log(JSON.stringify(mx, null, 2));
|
||||
|
||||
return {
|
||||
x: mx,
|
||||
y: my * originShift / 180.0
|
||||
};
|
||||
}
|
||||
|
||||
// Převod z Web Mercator zpět do lon/lat
|
||||
function metersToLonLat(mx, my) {
|
||||
const originShift = 2 * Math.PI * 6378137 / 2.0;
|
||||
const lon = (mx / originShift) * 180.0;
|
||||
const lat = 180 / Math.PI * (2 * Math.atan(Math.exp(my / originShift * Math.PI / 180.0)) - Math.PI / 2);
|
||||
|
||||
return {
|
||||
lon: round(lon, 6),
|
||||
lat: round(lat, 6)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 step size for the spline interpolation.
|
||||
* @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 {Object} style - Object with data for the calculation style.
|
||||
* @param {number} style.calculation - The style for the calculation
|
||||
* @param {number} style.range - The range for the calculation style.
|
||||
* @param {number} style.minValue - The minimum value used in the calculation.
|
||||
*
|
||||
* @param {Object|undefined} arrowHeadData - Optional data for the arrowhead.
|
||||
* @param {number} arrowHeadData.widthArrow - The width of the arrowhead.
|
||||
* @param {number} arrowHeadData.lengthArrow - The length of the arrowhead.
|
||||
*
|
||||
* @returns {{x: number, y: number}[]} - An array of points representing the arrow polygon.
|
||||
*/
|
||||
export function getArrowPolygon(
|
||||
arrowData,
|
||||
style= undefined,
|
||||
arrowHeadData = undefined) {
|
||||
|
||||
if (!style)
|
||||
style = {
|
||||
calculation: ARROW_BODY_STYLE_CONSTANT,
|
||||
range: 0,
|
||||
minValue: 0
|
||||
};
|
||||
|
||||
const splinePoints = computeSplinePoints(arrowData.points, arrowData.splineStep);
|
||||
const { leftSidePoints, rightSidePoints } = computeSides(splinePoints, arrowData.spacing, arrowData.offsetDistance, style);
|
||||
const arrowHead= arrowHeadData
|
||||
? createIsoscelesTriangleFromSpline(splinePoints, arrowHeadData.widthArrow, arrowHeadData.lengthArrow)
|
||||
: [];
|
||||
|
||||
const fullPolygon = [...leftSidePoints, ...arrowHead.reverse(), ...rightSidePoints.reverse()];
|
||||
return fullPolygon;
|
||||
}
|
||||
|
||||
function computeSplinePoints(points, splineStep) {
|
||||
let 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 += splineStep) {
|
||||
splinePoints.push({
|
||||
x: cubicInterpolate([p0.x, p1.x, p2.x, p3.x], t),
|
||||
y: cubicInterpolate([p0.y, p1.y, p2.y, p3.y], t)
|
||||
});
|
||||
}
|
||||
}
|
||||
splinePoints.x /100;
|
||||
return splinePoints;
|
||||
}
|
||||
|
||||
function computeSides(splinePoints, spacing, offsetDistance, style) {
|
||||
|
||||
let leftSidePoints = [];
|
||||
let rightSidePoints = [];
|
||||
let accumulatedDistance = 0;
|
||||
|
||||
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) {
|
||||
let distanceX;
|
||||
let distanceY;
|
||||
if (i == 1 || i == splinePoints.length-1)
|
||||
{
|
||||
distanceX = (currentPoint.y - previousPoint.y);
|
||||
distanceY = (previousPoint.x - currentPoint.x);
|
||||
}else
|
||||
{
|
||||
distanceX = (currentPoint.y - previousPoint.y) ;
|
||||
distanceY = (previousPoint.x - currentPoint.x) ;
|
||||
}
|
||||
const length = Math.hypot(distanceX, distanceY);
|
||||
const normalizedPosition = i / (splinePoints.length - 1);
|
||||
|
||||
let localOffsetDistance;
|
||||
|
||||
switch (style.calculation) {
|
||||
case ARROW_BODY_STYLE_LINEAR:
|
||||
localOffsetDistance = offsetDistance * linearWidthCurve(normalizedPosition, style.range, style.minValue);
|
||||
break;
|
||||
case ARROW_BODY_STYLE_EXPONENTIAL:
|
||||
localOffsetDistance = offsetDistance * exponentialWidthCurve(normalizedPosition, style.range, style.minValue);
|
||||
break;
|
||||
case ARROW_BODY_STYLE_CONSTANT:
|
||||
default:
|
||||
localOffsetDistance = offsetDistance;
|
||||
}
|
||||
|
||||
const offsetX = (distanceX / length) * localOffsetDistance;
|
||||
const offsetY = (distanceY / length) * localOffsetDistance;
|
||||
|
||||
accumulatedDistance = 0;
|
||||
leftSidePoints.push({ x: currentPoint.x + offsetX , y: currentPoint.y + offsetY });
|
||||
rightSidePoints.push({ x: currentPoint.x - offsetX , y: currentPoint.y - offsetY });
|
||||
}
|
||||
}
|
||||
|
||||
return { leftSidePoints, rightSidePoints };
|
||||
}
|
||||
|
||||
function createIsoscelesTriangleFromSpline(splinePoints, baseLengthMeters, heightMeters) {
|
||||
if (splinePoints.length < 2) {
|
||||
throw new Error("Potřeba alespoň dva body ve splinePoints");
|
||||
}
|
||||
|
||||
const last = splinePoints[splinePoints.length - 1];
|
||||
const prev = splinePoints[splinePoints.length - 2];
|
||||
|
||||
// Vektor směru (bearing)
|
||||
const dx = last.x - prev.x;
|
||||
const dy = last.y - prev.y;
|
||||
const bearing = (Math.atan2(dx, dy) * 180 / Math.PI + 360) % 360;
|
||||
|
||||
const center = [last.x, last.y]; // GeoJSON formát: [lon, lat]
|
||||
|
||||
const halfBase = baseLengthMeters / 2*100000;
|
||||
|
||||
const leftBase = turf.destination(center, halfBase, bearing - 90, { units: 'meters' });
|
||||
const rightBase = turf.destination(center, halfBase, bearing + 90, { units: 'meters' });
|
||||
const apex = turf.destination(center, heightMeters*100000, bearing, { units: 'meters' });
|
||||
|
||||
return [
|
||||
{ x: leftBase.geometry.coordinates[0], y: leftBase.geometry.coordinates[1] },
|
||||
{ x: apex.geometry.coordinates[0], y: apex.geometry.coordinates[1] },
|
||||
{ x: rightBase.geometry.coordinates[0], y: rightBase.geometry.coordinates[1] }
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
function computeArrowHead(splinePoints, width, length) {
|
||||
const len = splinePoints.length;
|
||||
const lastPoint = splinePoints[len - 1];
|
||||
const secondLastPoint = splinePoints[len - 2];
|
||||
//const x = (lastPoint.x - secondLastPoint.x);
|
||||
//const y = (lastPoint.y - secondLastPoint.y);
|
||||
const x = (lastPoint.x - secondLastPoint.x) ;
|
||||
const y = (lastPoint.y - secondLastPoint.y) ;
|
||||
const magnitude = Math.hypot(x, y);
|
||||
const normalizedX = x / magnitude;
|
||||
const normalizedY = y / magnitude;
|
||||
|
||||
return [
|
||||
{ x: lastPoint.x - normalizedY * width , y: lastPoint.y + normalizedX * width},
|
||||
{ x: (lastPoint.x + normalizedX) * length, y: (lastPoint.y + normalizedY) * length},
|
||||
{ x: lastPoint.x + normalizedY * width , y: lastPoint.y - normalizedX * width},
|
||||
];
|
||||
}
|
||||
|
||||
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 ;
|
||||
}
|
||||
Reference in New Issue
Block a user