From 13a7414798f1a2b3c0e67948d8abe4867b45ec20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Thu, 13 Mar 2025 22:01:07 +0100 Subject: [PATCH 1/6] Mark markers markers that are not on a way in dark red --- frontend/common/map.js | 21 ++++++++++++++++----- frontend/common/styles.css | 1 + 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 4342863..210e8a4 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -139,8 +139,9 @@ function addGeoJsonToMap(dat) { async function updateBrouter () { if (markers.length > 0) { - for (i=1; i< markers.length-1; i++) { + for (i=0; i< markers.length; i++) { markers[i]._icon.classList.remove("red"); + markers[i]._icon.classList.remove("darkred"); } markers[markers.length-1]._icon.classList.add("red"); markers[0]._icon.classList.add("green"); @@ -156,16 +157,26 @@ async function updateBrouter () { const marker1 = markers[i].getLatLng(); const marker2 = markers[i+1].getLatLng(); const url = `https://brouter.de/brouter?lonlats=${marker1.lng},${marker1.lat}|${marker2.lng},${marker2.lat}&profile=rail&alternativeidx=0&format=geojson`; - fetch(url).then((response) => response.json()) + fetch(url).then((response) => { + if (geojson != undefined) { + map.removeLayer(geojson); + } + if (!response.ok) { + throw new Error("HTTP error " + response.status); + } + return response.json() + }) .then((data) => { - if (geojson != undefined) { - map.removeLayer(geojson); - } delete data.features[0].properties.messages geojsons.push(data.features[0]); const dat = {type: "FeatureCollection", features: geojsons}; geojson = addGeoJsonToMap(dat); }) + .catch(err => { + markers[i]._icon.classList.add("darkred"); + markers[i+1]._icon.classList.add("darkred"); + } + ) } } diff --git a/frontend/common/styles.css b/frontend/common/styles.css index 150beda..67294e2 100644 --- a/frontend/common/styles.css +++ b/frontend/common/styles.css @@ -21,3 +21,4 @@ body { } img.red { filter: hue-rotate(120deg); } img.green { filter: hue-rotate(-120deg); } +img.darkred { filter: hue-rotate(160deg); } From 13900c8092bbc7af5082e4743a26874666e1209f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Thu, 13 Mar 2025 23:29:33 +0100 Subject: [PATCH 2/6] Bugfix: refresh the path correctly --- frontend/common/map.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 210e8a4..682dc40 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -147,10 +147,10 @@ async function updateBrouter () { markers[0]._icon.classList.add("green"); } geojsons = []; + if (geojson != undefined) { + map.removeLayer(geojson); + } if (markers.length < 2) { - if (geojson != undefined) { - map.removeLayer(geojson); - } return; } for (let i = 0; i < markers.length - 1 ; i++) { @@ -158,15 +158,15 @@ async function updateBrouter () { const marker2 = markers[i+1].getLatLng(); const url = `https://brouter.de/brouter?lonlats=${marker1.lng},${marker1.lat}|${marker2.lng},${marker2.lat}&profile=rail&alternativeidx=0&format=geojson`; fetch(url).then((response) => { - if (geojson != undefined) { - map.removeLayer(geojson); - } if (!response.ok) { throw new Error("HTTP error " + response.status); } return response.json() }) .then((data) => { + if (geojson != undefined) { + map.removeLayer(geojson); + } delete data.features[0].properties.messages geojsons.push(data.features[0]); const dat = {type: "FeatureCollection", features: geojsons}; From 634df5ebf47413374d89e22a015cc74537da0ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Tue, 18 Mar 2025 20:02:01 +0100 Subject: [PATCH 3/6] Alert on invalid filename (fixes #1) --- frontend/common/map.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 682dc40..72f1a5f 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -229,9 +229,14 @@ async function pickDirectory(e){ return; } const dat = {type: "FeatureCollection", features: geojsons}; - const file = await dirHandle.getFileHandle(`${filename}.geojson`, { - create: true - }); + try { + const file = await dirHandle.getFileHandle(`${filename}.geojson`, { + create: true + }); + } catch (error) { + alert(`Could not open file: ${error.message}`); + return + } const blob = new Blob([JSON.stringify(dat)]); const writableStream = await file.createWritable(); await writableStream.write(blob); From 4c9b1b555f31abe59fcf80af7d5ecb1d14893d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Tue, 25 Mar 2025 22:22:03 +0100 Subject: [PATCH 4/6] Build points from repo --- scripts/mapbuilder.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/mapbuilder.sh b/scripts/mapbuilder.sh index ae8f8e1..5616ce4 100755 --- a/scripts/mapbuilder.sh +++ b/scripts/mapbuilder.sh @@ -15,8 +15,17 @@ do done + tippecanoe -aN -z"$zoom" -o "$temp/strecken.pmtiles" $temp/*.json -mv $temp/strecken.pmtiles "$2" +if [ -f "$1/points.json" ] +then + ogr2ogr -t_srs WGS84 "$temp/points.json" "$1/points.json" + tippecanoe -aN -z12 -r1 -o "$temp/points.pmtiles" "$temp/points.json" + jq '.points_url = "points.pmtiles"' "$1/layers.json" > "$2/layers.json" +fi + + +mv $temp/*.pmtiles "$2" rm -r $temp From 198175fde2c06dee44b38dc54c7649da3b251a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Tue, 25 Mar 2025 22:35:31 +0100 Subject: [PATCH 5/6] Improve point handling, fix path saving --- frontend/common/map.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 72f1a5f..9bb9cf2 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -43,7 +43,7 @@ let pointPaintRules = [ stroke: 'white', width: 1.5, }), - filter: (z,f) => { return f.props.zoom < z } + filter: (z,f) => { return f.props.zoom > 0 && f.props.zoom < z } } ] @@ -68,7 +68,7 @@ let pointRules = [ return `400 ${size}px sans-serif`; }, }), - filter: (z,f) => { return f.props.zoom < z }, + filter: (z,f) => { return f.props.zoom > 0 && f.props.zoom < z }, sort: (a,b) => { return a.zoom - b.zoom } } ]; @@ -229,8 +229,9 @@ async function pickDirectory(e){ return; } const dat = {type: "FeatureCollection", features: geojsons}; + let file; try { - const file = await dirHandle.getFileHandle(`${filename}.geojson`, { + file = await dirHandle.getFileHandle(`${filename}.geojson`, { create: true }); } catch (error) { From d91e0bd8ad0681f8a7d85bb6fed8aef9c75f865a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Thu, 1 May 2025 21:07:45 +0200 Subject: [PATCH 6/6] Fix attribution, add a detector for sharp angles The sharp angle detector prevents common errors such as accidentally taking a wrong turn or switching to the wrong track, especially in tram networks --- frontend/common/map.js | 81 ++++++++++++++++++++++++++++++++++---- frontend/common/styles.css | 5 +++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 9bb9cf2..76d8289 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -15,6 +15,8 @@ legend.onAdd = function (map) { const map = L.map("map", { center: [52,13], zoom: 3, minZoom: 0 }); +map.attributionControl.setPrefix('Made with Streckenkarte' ); + function update_hash() { const {lat, lng} = this.getCenter(); @@ -94,6 +96,7 @@ fetch("layers.json") } ); const strecken = protomapsL.leafletLayer({ + attribution: "", url: data["pmtiles_url"] ?? "strecken.pmtiles", maxDataZoom: data["maxZoom"] ?? 10, maxZoom: 19, @@ -104,6 +107,7 @@ fetch("layers.json") strecken.addTo(map); if ("points_url" in data) { const points = protomapsL.leafletLayer({ + attribution: "", url: data["points_url"], maxDataZoom: data["maxZoom"] ?? 10, maxZoom: 19, @@ -120,6 +124,7 @@ fetch("layers.json") let dirHandle; let editMode = false; let markers = []; +let anglemarkers = []; let geojsons = []; let geojson; let editlayer; @@ -137,6 +142,39 @@ function addGeoJsonToMap(dat) { return g; } +function computeVector(coords, i) { + return [coords[i][0] - coords[i-1][0], coords[i][1] - coords[i-1][1]]; +} + +function scalarProduct(a,b) { + return a[0] * b[0] + a[1] * b[1] +} + +async function recompute_anglemarkers(g) { + for (k=0; k < anglemarkers.length ; k++) { + map.removeLayer(anglemarkers[k]) + } + anglemarkers = []; + const limit = document.getElementById("angle").value + + for (j=0; j< g.length; j++) { + const coords = g[j].geometry.coordinates; + console.log(coords); + for (let i=1; i < coords.length - 1; i++) { + const a = computeVector(coords,i); + const b = computeVector(coords,i+1); + let angle = Math.acos(scalarProduct(a,b)/Math.sqrt(scalarProduct(a,a)*scalarProduct(b,b))) + if (angle > limit ) { + let mark = new L.marker(L.latLng(coords[i][1],coords[i][0])) + anglemarkers.push(mark); + mark.addTo(map); + mark._icon.classList.add("warn"); + } + console.log(angle) + } + } +} + async function updateBrouter () { if (markers.length > 0) { for (i=0; i< markers.length; i++) { @@ -147,6 +185,7 @@ async function updateBrouter () { markers[0]._icon.classList.add("green"); } geojsons = []; + recompute_anglemarkers(geojsons); if (geojson != undefined) { map.removeLayer(geojson); } @@ -167,8 +206,11 @@ async function updateBrouter () { if (geojson != undefined) { map.removeLayer(geojson); } - delete data.features[0].properties.messages + delete data.features[0].properties.messages; + + console.log(data.features[0].geometry.coordinates) geojsons.push(data.features[0]); + recompute_anglemarkers(geojsons); const dat = {type: "FeatureCollection", features: geojsons}; geojson = addGeoJsonToMap(dat); }) @@ -199,6 +241,14 @@ map.on('click', function(e) { }); +async function quitEdit(e) { + e.stopPropagation() + L.DomEvent.preventDefault(e); + editMode = false; + document.querySelector(".edit-ui").style.display = "none"; + document.getElementById("edit-mode").style.display = "block"; +} + async function pickDirectory(e){ e.stopPropagation() L.DomEvent.preventDefault(e); @@ -207,18 +257,21 @@ async function pickDirectory(e){ if (!dirHandle) { return; } + const layerspan = document.querySelector("#layername") + layerspan.innerHTML = "" for (i = 0; i < l.length ; i++ ) { console.log(l[i].dirname); if (l[i].dirname === dirHandle.name) { + console.log(l[i]) + console.log(this) editlayer = l[i]; - this.innerHTML = "Editing layer " + layer_legend(l[i]) + "
" + this.innerHTML + layerspan.innerHTML = "Editing layer " + layer_legend(l[i]) + "
" break; } } - - document.getElementById("edit-mode").style.color = "red"; - document.getElementById("edit-mode").innerHTML = "save"; editMode = true; + document.getElementById("edit-mode").style.display = "none"; + document.querySelector(".edit-ui").style.display = "block"; } else { if (geojsons.length < 1) { alert("There is no path to save!"); @@ -260,8 +313,22 @@ if (edit) { customButton.onAdd = () => { const buttonDiv = L.DomUtil.create('div', 'legend'); if ("showDirectoryPicker" in window) { - buttonDiv.innerHTML = ``; - buttonDiv.addEventListener('click', pickDirectory, this) + // const button = L.DomUtil.create('button'); + // button.id = 'edit-mode'; + // button.innerHTML = 'Edit'; + // buttonDiv.appendChild(button) + // button.addEventListener('click', pickDirectory, this) + L.DomEvent.disableClickPropagation(buttonDiv); + buttonDiv.addEventListener('mouseover', L.DomEvent.stopPropagation); + buttonDiv.addEventListener('click', L.DomEvent.preventDefault) + buttonDiv.addEventListener('click', L.DomEvent.stopPropagation) + buttonDiv.innerHTML = ` +
+
+
+
+ +
` } else { buttonDiv.innerHTML = "Your browser does not support editing.
As of 2025, editing is supported on Chromium-based browsers only."; } diff --git a/frontend/common/styles.css b/frontend/common/styles.css index 67294e2..731b8bb 100644 --- a/frontend/common/styles.css +++ b/frontend/common/styles.css @@ -1,3 +1,4 @@ + body { margin: 0; padding: 0; @@ -22,3 +23,7 @@ body { img.red { filter: hue-rotate(120deg); } img.green { filter: hue-rotate(-120deg); } img.darkred { filter: hue-rotate(160deg); } +img.warn { filter: hue-rotate(160deg); } +div.edit-ui { + display: none; +}