StorymapperArrow/Drawing.js
2025-07-11 10:34:10 +02:00

512 lines
16 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 { getFrontline } from "athena-utils/shape/Frontline.js";
import { LEFT_SIDE, RIGHT_SIDE, BOTH_SIDES } from "athena-utils/shape/Frontline.js";
const ARROW = "arrow";
const FRONTLINE = "frontline";
const DEFAULT_ARROW_PARAMS = {
splineStep: 20,
offsetDistance: 12000,
calculation: ARROW_BODY_STYLE_LINEAR,
range: 1,
minValue: 0.1,
widthArrow: 5,
lengthArrow: 8
};
const DEFAULT_FRONTLINE_PARAMS = {
splineStep: 0.08,
offsetDistance: 10000,
style: LEFT_SIDE,
protrusion: {
length: 15000,
startSize: 5000,
endSize: 500,
gap: 15000
}
};
let currentFeature = null;
let currentDrawStyle = ARROW;
const arrowParamsMap = new Map();
const frontlineParamsMap = new Map();
mapboxgl.accessToken = 'pk.eyJ1Ijoib3V0ZG9vcm1hcHBpbmdjb21wYW55IiwiYSI6ImNqYmh3cDdjYzNsMnozNGxsYzlvMmk2bTYifQ.QqcZ4LVoLWnXafXdjZxnZg';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [10, 50],
zoom: 5
});
// Drawing visuals
const draw = new MapboxDraw({
displayControlsDefault: false,
controls: {
line_string: true
},
defaultMode: 'draw_line_string',
styles: [
// Main drawing line
{
id: 'gl-draw-line',
type: 'line',
filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true']],
layout: {
'line-cap': 'round',
'line-join': 'round'
},
paint: {
'line-color': 'rgb(0, 255, 0)',
'line-width': 4
}
},
// This makes the nonstop visible line invisible
{
id: 'gl-draw-line-inactive',
type: 'line',
filter: ['all', ['==', '$type', 'LineString'], ['==', 'deactive', 'false']],
layout: {
'line-cap': 'round',
'line-join': 'round'
},
paint: {
'line-color': 'rgba(0, 0, 0, 0)',
'line-width': 2
}
},
// Selected point
{
id: 'gl-draw-polygon-and-line-vertex-active',
type: 'circle',
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'vertex'], ['==', 'active', 'true']],
paint: {
'circle-radius': 6,
'circle-color': 'rgb(255, 255, 0)'
}
},
// Main points
{
id: 'gl-draw-polygon-and-line-vertex-inactive',
type: 'circle',
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'vertex'], ['==', 'active', 'false']],
paint: {
'circle-radius': 4,
'circle-color': 'rgb(0, 255, 0)'
}
},
// Midpoints
{
id: 'gl-draw-polygon-midpoint',
type: 'circle',
filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint']],
paint: {
'circle-radius': 4,
'circle-color': 'rgb(0, 255, 174)'
}
}
]
});
// Map controlls
map.addControl(draw, 'top-left');
map.on('draw.create', handleDraw);
map.on('draw.update', handleDraw);
map.on('click', (e) => {
hideArrowEditor();
hideFrontlineEditor();
handleClick(e, ARROW, arrowParamsMap, showArrowEditor);
handleClick(e, FRONTLINE, frontlineParamsMap, showFrontlineEditor);
});
map.dragPan.enable();
function handleDraw(e) {
const feature = e.features[0];
const coords = feature.geometry.coordinates;
const id = feature.id;
if (currentDrawStyle === ARROW) {
DrawArrow(coords, id);
} else if (currentDrawStyle === FRONTLINE) {
DrawFrontline(coords, id);
}
}
function handleClick(click, prefix, paramsMap, showEditorFunction) {
const features = map.queryRenderedFeatures(click.point, {
layers: map.getStyle().layers
.filter(l => l.id.startsWith(prefix + "-"))
.map(l => l.id)
});
if (features.length === 0) return;
currentDrawStyle = prefix;
updateButtonStyles();
const feature = features[0];
const featureId = feature.layer.id.replace(prefix + "-", '');
const allFeatures = draw.getAll().features;
currentFeature = allFeatures.find(f => f.id === featureId);
if (currentFeature) {
const params = paramsMap.get(featureId);
if (params) {
showEditorFunction(params);
}
draw.changeMode('direct_select', { featureId: featureId });
}
}
function DrawArrow(coordinates, polygonId) {
if (!arrowParamsMap.has(polygonId)) {
arrowParamsMap.set(polygonId, structuredClone(DEFAULT_ARROW_PARAMS));
}
const params = arrowParamsMap.get(polygonId);
const arrowGeoJSON = getArrowPolygon(
{
points: coordinates,
splineStep: params.splineStep,
offsetDistance: params.offsetDistance
},
{
calculation: params.calculation,
range: params.range,
minValue: params.minValue
},
{
widthArrow: params.widthArrow,
lengthArrow: params.lengthArrow
}
);
updatePolygonOnMap(ARROW, polygonId, drawOnMap, arrowGeoJSON);
}
function DrawFrontline(coordinates, polygonId) {
if (!frontlineParamsMap.has(polygonId)) {
frontlineParamsMap.set(polygonId, structuredClone(DEFAULT_FRONTLINE_PARAMS));
}
const params = frontlineParamsMap.get(polygonId);
const frontlineGeoJSON = getFrontline(
{
points: coordinates,
splineStep: params.splineStep,
offsetDistance: params.offsetDistance,
style: params.style
},
params.protrusion
);
let polygonToDraw = frontlineGeoJSON;
// Only one side
if (frontlineGeoJSON.leftPoly || frontlineGeoJSON.rightPoly) {
polygonToDraw = frontlineGeoJSON.leftPoly || frontlineGeoJSON.rightPoly;
}
// Both sides
if(frontlineGeoJSON.leftPoly && frontlineGeoJSON.rightPoly){
updatePolygonOnMap(FRONTLINE, polygonId, drawOnMap, polygonToDraw);
let additionalPolygonToDraw = frontlineGeoJSON.rightPoly;
drawOnMap(additionalPolygonToDraw, FRONTLINE + "-" + (polygonId+1));
return;
}
updatePolygonOnMap(FRONTLINE, polygonId, drawOnMap, polygonToDraw);
}
function updatePolygonOnMap(prefix, polygonId, drawFunction, geojson) {
const layerId =  prefix + "-" + polygonId;
if (map.getLayer(layerId)) map.removeLayer(layerId);
if (map.getSource(layerId)) map.removeSource(layerId);
drawFunction(geojson, layerId);
const allFeatures = draw.getAll().features;
currentFeature = allFeatures.find(f => f.polygonId === polygonId);
}
function drawOnMap(frontlineGeoJSON, id) {
if (map.getLayer(id)) map.removeLayer(id);
if (map.getSource(id)) map.removeSource(id);
map.addSource(id, {
type: 'geojson',
data: frontlineGeoJSON
});
map.addLayer({
id: id,
type: 'fill',
source: id,
paint: {
'fill-color': '#ff6600',
'fill-opacity': 0.6,
'fill-outline-color': '#cc3300'
}
});
}
function handleDelete(polygonType)
{
if (!currentFeature) return;
   
const id = currentFeature.id;
draw.delete(id);
//ARROW
if(polygonType == ARROW){
const arrowLayerId = polygonType + "-" + id;
DeleteFromMap(arrowLayerId, id);
}
//Frontline
if(polygonType == FRONTLINE){
const frontlineLayerId = polygonType + "-" + id;
if (frontlineParamsMap.get(id).style == 3){ //BOTH_SIDES
const frontlineLayerIdB = polygonType + "-" + (id+1);
DeleteFromMap(frontlineLayerId, id);
DeleteFromMap(frontlineLayerIdB, id+1);
hideArrowEditor();
hideFrontlineEditor();
return;
}
DeleteFromMap(frontlineLayerId, id);
}
   
hideArrowEditor();
hideFrontlineEditor();
}
function DeleteFromMap(layerId, FeatureId){
if (map.getLayer(layerId)) map.removeLayer(layerId);
if (map.getSource(layerId)) map.removeSource(layerId);
arrowParamsMap.delete(FeatureId);
}
function showArrowEditor(params) {
const popup = document.getElementById('arrow-editor');
popup.style.display = 'block';
popup.style.left = '10px';
popup.style.top = '70px';
document.getElementById('splineStep').value = params.splineStep;
document.getElementById('offsetDistance').value = params.offsetDistance;
document.getElementById('range').value = params.range;
document.getElementById('minValue').value = params.minValue;
document.getElementById('widthArrow').value = params.widthArrow;
document.getElementById('lengthArrow').value = params.lengthArrow;
}
function hideArrowEditor() {
const popup = document.getElementById('arrow-editor');
popup.style.display = 'none';
currentFeature = null;
}
function showFrontlineEditor(params) {
const popup = document.getElementById('frontline-editor');
popup.style.display = 'block';
popup.style.left = '10px';
popup.style.top = '70px';
document.getElementById('splineStepFrontline').value = params.splineStep;
document.getElementById('offsetDistanceFrontline').value = params.offsetDistance;
document.getElementById('styleFrontline').value = params.style;
document.getElementById('protrusionLength').value = params.protrusion.length;
document.getElementById('protrusionStartSize').value = params.protrusion.startSize;
document.getElementById('protrusionEndSize').value = params.protrusion.endSize;
document.getElementById('protrusionGap').value = params.protrusion.gap;
}
function hideFrontlineEditor() {
const popup = document.getElementById('frontline-editor');
popup.style.display = 'none';
currentFeature = null;
}
function updateButtonStyles() {
const buttonsContainer = document.getElementById('drawStyleButtons');
Array.from(buttonsContainer.querySelectorAll('button')).forEach(btn => {
const isSelected = btn.getAttribute('data-style') === currentDrawStyle;
btn.style.backgroundColor = isSelected ? '#333' : '#f0f0f0';
btn.style.color = isSelected ? '#fff' : '#000';
});
}
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('removeArrow').addEventListener('click', () => {
   
    handleDelete(ARROW);
});
document.getElementById('removeFrontline').addEventListener('click', () => {
   
    handleDelete(FRONTLINE);
});
document.getElementById('applyArrowChanges').addEventListener('click', () => {
   
    if (!currentFeature) return;
    console.log("Test");
    const coords = currentFeature.geometry.coordinates;
    const id = currentFeature.id;
    const splineStep = parseFloat(document.getElementById('splineStep').value);
    const offsetDistance = parseFloat(document.getElementById('offsetDistance').value);
    const range = parseFloat(document.getElementById('range').value);
    const minValue = parseFloat(document.getElementById('minValue').value);
    const widthArrow = parseFloat(document.getElementById('widthArrow').value);
    const lengthArrow = parseFloat(document.getElementById('lengthArrow').value);
    arrowParamsMap.set(id, {
        splineStep,
        offsetDistance,
        calculation: ARROW_BODY_STYLE_LINEAR,
        range,
        minValue,
        widthArrow,
        lengthArrow
    });
    const arrowGeoJSON = getArrowPolygon({
        points: coords,
        splineStep,
        offsetDistance
    }, {
        calculation: ARROW_BODY_STYLE_LINEAR,
        range,
        minValue
    }, {
        widthArrow,
        lengthArrow
    });
    //ARROW
    const arrowLayerId = ARROW + "-" + id;
    if (map.getLayer(arrowLayerId)) map.removeLayer(arrowLayerId);
    if (map.getSource(arrowLayerId)) map.removeSource(arrowLayerId);
    drawOnMap(arrowGeoJSON, ARROW + "-" + id);
    const allFeatures = draw.getAll().features;
    currentFeature = allFeatures.find(f => f.id === id);
});
document.getElementById('applyFrontlineChanges').addEventListener('click', () => {
if (!currentFeature) return;
const coords = currentFeature.geometry.coordinates;
const id = currentFeature.id;
const splineStep = parseFloat(document.getElementById('splineStepFrontline').value);
const offsetDistance = parseFloat(document.getElementById('offsetDistanceFrontline').value);
const style = parseInt(document.getElementById('styleFrontline').value);
const length = parseFloat(document.getElementById('protrusionLength').value);
const startSize = parseFloat(document.getElementById('protrusionStartSize').value);
const endSize = parseFloat(document.getElementById('protrusionEndSize').value);
const gap = parseFloat(document.getElementById('protrusionGap').value);
frontlineParamsMap.set(id, {
splineStep,
offsetDistance,
style,
protrusion: {
length,
startSize,
endSize,
gap
}
});
const frontlineData = {
points: coords,
splineStep,
offsetDistance,
style
};
const protrusionData = frontlineParamsMap.get(id).protrusion;
console.log("Update");
console.log(frontlineData);
console.log(protrusionData);
const frontlineGeoJSON = getFrontline(frontlineData, protrusionData);
console.log("frontlineGeoJSON", frontlineGeoJSON);
let polygonToDraw = frontlineGeoJSON;
if (frontlineGeoJSON.leftPoly || frontlineGeoJSON.rightPoly) {
polygonToDraw = frontlineGeoJSON.leftPoly || frontlineGeoJSON.rightPoly;
}
if(frontlineGeoJSON.leftPoly && frontlineGeoJSON.rightPoly){
let polygonToDrawLeft = frontlineGeoJSON.leftPoly;
let polygonToDrawRight = frontlineGeoJSON.rightPoly;
console.log("polygonToDrawLeft", polygonToDrawLeft);
console.log("polygonToDrawRight", polygonToDrawRight);
const frontlineLayerId = FRONTLINE + "-" + id;
if (map.getLayer(frontlineLayerId)) map.removeLayer(frontlineLayerId);
if (map.getSource(frontlineLayerId)) map.removeSource(frontlineLayerId);
console.log(frontlineLayerId);
console.log(FRONTLINE + "-" + (id+1));
drawOnMap(polygonToDrawLeft, FRONTLINE + "-" + id);
drawOnMap(polygonToDrawRight, FRONTLINE + "-" + (id+1));
const allFeatures = draw.getAll().features;
currentFeature = allFeatures.find(f => f.id === id);
return;
}
//if (last was both destroy id+1) TODO
console.log("Test2");
const frontlineLayerId = FRONTLINE + "-" + id;
if (map.getLayer(frontlineLayerId+1)) map.removeLayer(frontlineLayerId+1);
if (map.getSource(frontlineLayerId+1)) map.removeSource(frontlineLayerId+1);
if (map.getLayer(frontlineLayerId)) map.removeLayer(frontlineLayerId);
if (map.getSource(frontlineLayerId)) map.removeSource(frontlineLayerId);
drawOnMap(polygonToDraw, FRONTLINE + "-" + id);
const allFeatures = draw.getAll().features;
currentFeature = allFeatures.find(f => f.id === id);
});
const buttonsContainer = document.getElementById('drawStyleButtons');
buttonsContainer.addEventListener('click', (event) => {
if (event.target.tagName !== 'BUTTON') return;
Array.from(buttonsContainer.querySelectorAll('button')).forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
currentDrawStyle = event.target.getAttribute('data-style');
updateButtonStyles();
});
updateButtonStyles();
});