From 2f18d2f06f3b2a7eb4211bba2528b60398f70b4f Mon Sep 17 00:00:00 2001 From: Tomas Richtar Date: Tue, 27 May 2025 18:12:47 +0200 Subject: [PATCH] Arrow, Circle, Rectangle -> MapArrow.js --- MapArrow.js | 218 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 189 insertions(+), 29 deletions(-) diff --git a/MapArrow.js b/MapArrow.js index 4265011..dc1f95e 100644 --- a/MapArrow.js +++ b/MapArrow.js @@ -1,3 +1,7 @@ +import * as turf from "@turf/turf"; +import { toMercator, toWgs84 } from '@turf/projection'; +import { ARROW_BODY_STYLE_CONSTANT, ARROW_BODY_STYLE_LINEAR, ARROW_BODY_STYLE_EXPONENTIAL } from "./Arrow.js"; + mapboxgl.accessToken = 'pk.eyJ1Ijoib3V0ZG9vcm1hcHBpbmdjb21wYW55IiwiYSI6ImNqYmh3cDdjYzNsMnozNGxsYzlvMmk2bTYifQ.QqcZ4LVoLWnXafXdjZxnZg'; const map = new mapboxgl.Map({ container: 'map', @@ -5,22 +9,95 @@ mapboxgl.accessToken = 'pk.eyJ1Ijoib3V0ZG9vcm1hcHBpbmdjb21wYW55IiwiYSI6ImNqYmh3c zoom: 5 }); -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 fullPolygon = getArrowPolygon(arrowData, style, arrowHeadData); + const circleGeoJSON = getCirclePolygon(circleCenter, circleRadius, circleDensity); + const circle = turf.circle([20, 80], 120000, { units: "meters", steps: 64 }); // Circle created directly with the Turf library + const rectangleGeoJSON = getRectanglePolygon([20, 80], 2200, 2200); + + //ARROW map.addSource("arrow-shape", { type: "geojson", data: fullPolygon }); map.addLayer({ - id: "arrow-shape", - type: "fill", - source: "arrow-shape", - paint: { + "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 + map.addSource("circlePolygon", { + "type": "geojson", + "data": circleGeoJSON + }); + + map.addLayer({ + "id": "circlePolygon", + "type": "fill", + "source": "circlePolygon", + "layout": {}, + "paint": { + "fill-color": "blue", + "fill-opacity": 0.6 + } + }); + map.addLayer({ + "id": "circlePolygon-outline", + "type": "line", + "source": "circlePolygon", + "paint": { + "line-color": "#000000", + "line-width": 2, + "line-opacity": 1 + } + }); + // CIRCLE + + // RECTANGLE + map.addSource("rectanglePolygon", { + "type": "geojson", + "data": rectangleGeoJSON + }); + + // Přidání vrstvy pro vykreslení polygonu + map.addLayer({ + "id": "rectanglePolygon", + "type": "fill", + "source": "rectanglePolygon", + "layout": {}, + "paint": { + "fill-color": "red", + "fill-opacity": 0.6 + } + }); + + // Přidání outline pro polygon + map.addLayer({ + "id": "rectanglePolygon-outline", + "type": "line", + "source": "rectanglePolygon", + "paint": { + "line-color": "#000", + "line-width": 3 + } + }); + // RECTANGLE + + //MAP GRID const grid = generateLatLonGrid(10); map.addSource("latLonGrid", { type: "geojson", data: grid }); map.addLayer({ @@ -33,20 +110,10 @@ map.on('load', () => { "line-opacity": 0.5 } }); - - // Obrys (černý) - map.addLayer({ - id: "arrow-outline", - type: "line", - source: "arrow-shape", - paint: { - "line-color": "#000000", - "line-width": 2, - "line-opacity": 1 - } - }); + //MAP GRID }); +//ARROW const points = [ [1.42076, 40.08804], [15.42076, 80.08804], @@ -54,7 +121,6 @@ const points = [ [120.42076, 40.08804], [358.4050, 50.52] ]; - const arrowData = { points: points, splineStep: 20, @@ -70,15 +136,30 @@ const arrowData = { widthArrow: 10, lengthArrow: 5 }; +//ARROW +//CIRCLE +const circleCenter = [20, 80]; +const circleRadius = 120; +const circleDensity = 20; +//CORCLE +//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]))); } +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 ; +} + /** * @param {Object} arrowData - Object with data for arrow - * @param {{x: number, y: number}[]} arrowData.points - List of points defining the arrow's path. + * @param {Array<[number, 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). @@ -88,7 +169,7 @@ function cubicInterpolate(p, x) { * @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 {Object} arrowHeadData - Optional data for the arrowhead. * @param {number} arrowHeadData.widthArrow - The width of the arrowhead. * @param {number} arrowHeadData.lengthArrow - The length of the arrowhead. * @@ -196,7 +277,93 @@ function createIsoscelesTriangleCoords(center, baseLengthMeters, heightMeters, b const tip = turf.destination(center, heightMeters, bearing, { units: 'meters' }).geometry.coordinates; return [left, tip, right]; } +//ARROW +//CIRCLE +const distancePerDegreeLongitude = 111.320; // 2π×6378.1km/360 +const distancePerDegreeLatitude = 110.574; // 2π×6356.75km/360 + +/** + * @param {Object} center - The center point of the circle. + * @param {number} center.x + * @param {number} center.y + * @param {number} radius - The radius of the circle. + * @param {number} density - The number of points used to approximate the circle. + * + * @returns {Object} GeoJSON Feature representing the circle polygon. + */ +export function getCirclePolygon(center, radius, density = 64) { + const points = []; + + const coords = { + latitude: center[1], // Latitude + longitude: center[0] // Longitude + }; + + const distanceX = radius / (distancePerDegreeLongitude * Math.cos(coords.latitude * Math.PI / 180)); + const distanceY = radius / distancePerDegreeLatitude; + + for (let i = 0; i < density; i++) { + const angle = (i / density) * Math.PI * 2; + const x = distanceX * Math.cos(angle); + const y = distanceY * Math.sin(angle); + points.push([coords.longitude + x, coords.latitude + y]); + } + + // Close the circle by adding the first point again + points.push(points[0]); + + return { + type: "Feature", + geometry: { + type: "Polygon", + coordinates: [points] + }, + properties: {} + }; +} +//CIRCLE + +//RECTANGLE +/** + * @param {Object} center - The center point of the rectangle. + * @param {number} center.x + * @param {number} center.y + * @param {number} width - The length of the first side of the rectangle. + * @param {number} height - The length of the second side of the rectangle. + * @param {number} rotation - The angle (in radians) by which to rotate the rectangle. + * + * @returns {Object} GeoJSON Feature representing the rectangle polygon. + */ +export function getRectanglePolygon(center, width, height, rotation = 0) { + const widthMeters = width * 1000 ; + const heightMeters = height * 1000; + + const centerMerc = toMercator(turf.point(center)).geometry.coordinates; + + const halfWidth = widthMeters / 2; + const halfHeight = heightMeters / 2; + + let corners = [ + [centerMerc[0] - halfWidth, centerMerc[1] + halfHeight], // topLeft + [centerMerc[0] + halfWidth, centerMerc[1] + halfHeight], // topRight + [centerMerc[0] + halfWidth, centerMerc[1] - halfHeight], // bottomRight + [centerMerc[0] - halfWidth, centerMerc[1] - halfHeight], // bottomLeft + ]; + + if (rotation !== 0) { + const rad = (rotation * Math.PI) / 180; + corners = corners.map(([x, y]) => rotateXY(x, y, centerMerc[0], centerMerc[1], rad)); + } + + corners.push(corners[0]); + const wgsCoords = corners.map(([x, y]) => toWgs84([x, y])); + + return turf.polygon([wgsCoords]); +} +//RECTANGLE + +//MAP GRID function generateLatLonGrid(step = 10) { const features = []; @@ -225,11 +392,4 @@ function generateLatLonGrid(step = 10) { 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 +//MAP GRID \ No newline at end of file