StorymapperArrow/Drawing.js
2025-07-10 21:04:42 +02:00

531 lines
18 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, drawArrowOnMap, 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, drawFrontlineOnMap, polygonToDraw);
let additionalPolygonToDraw = frontlineGeoJSON.rightPoly;
drawFrontlineOnMap(additionalPolygonToDraw, FRONTLINE + "-" + (polygonId+1));
return;
}
updatePolygonOnMap(FRONTLINE, polygonId, drawFrontlineOnMap, 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 drawArrowOnMap(geojson, id) {
    map.addSource(id, {
        type: 'geojson',
        data: geojson
    });
    map.addLayer({
        id: id,
        type: 'fill',
        source: id,
        paint: {
            'fill-color': '#ff0000',
            'fill-opacity': 0.5
        }
    });
}
function drawFrontlineOnMap(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 MyDelete(polygonType)
{
    if (!currentFeature) return;
   
    const id = currentFeature.id;
    draw.delete(id);
    //ARROW
    if(polygonType == ARROW){
        const arrowLayerId = polygonType + "-" + id;
        if (map.getLayer(arrowLayerId)) map.removeLayer(arrowLayerId);
        if (map.getSource(arrowLayerId)) map.removeSource(arrowLayerId);
        arrowParamsMap.delete(id);
    }
   
    //Frontline
    if(polygonType == FRONTLINE){
        const frontlineLayerId = polygonType + "-" + id;
        if (frontlineParamsMap.get(id).style == 3){ //BOTH_SIDES
            if (map.getLayer(frontlineLayerId)) map.removeLayer(frontlineLayerId);
            if (map.getSource(frontlineLayerId)) map.removeSource(frontlineLayerId);
            const frontlineLayerIdB = polygonType + "-" + (id+1);
            if (map.getLayer(frontlineLayerIdB)) map.removeLayer(frontlineLayerIdB);
            if (map.getSource(frontlineLayerIdB)) map.removeSource(frontlineLayerIdB);
            frontlineParamsMap.delete(id);
            frontlineParamsMap.delete(id+1);
           
            hideArrowEditor();
            hideFrontlineEditor();
            return;
        }
        if (map.getLayer(frontlineLayerId)) map.removeLayer(frontlineLayerId);
        if (map.getSource(frontlineLayerId)) map.removeSource(frontlineLayerId);
        frontlineParamsMap.delete(id);
    }
   
    hideArrowEditor();
    hideFrontlineEditor();
}
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;
}
document.addEventListener('DOMContentLoaded', () => {
    document.getElementById('removeArrow').addEventListener('click', () => {
       
        MyDelete(ARROW);
    });
    document.getElementById('removeFrontline').addEventListener('click', () => {
       
        MyDelete(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);
        drawArrowOnMap(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));
            drawFrontlineOnMap(polygonToDrawLeft, FRONTLINE + "-" + id);
            drawFrontlineOnMap(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);
        drawFrontlineOnMap(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();
});
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';
    });
}