From d8703eb61c4b10165ba639a7fc2035f809b098a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Thu, 1 May 2025 22:04:17 +0200 Subject: [PATCH 1/6] Add configurable brouter profiles per layer --- frontend/common/map.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 4b991d6..c6972b5 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -90,7 +90,10 @@ fetch("layers.json") document.title = data["name"] const layers = data["layers"] for (let key in layers) { - l.push({ dirname: key , name: layers[key]["humanname"], color: layers[key]["color"] }); + let layer = layers[key] + layer.name = layer['humanname'] + layer.dirname = key + l.push(layer) rules.push({ dataLayer: key, symbolizer: new protomapsL.LineSymbolizer(layers[key]) @@ -168,7 +171,6 @@ async function recompute_anglemarkers(g) { 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); @@ -179,7 +181,6 @@ async function recompute_anglemarkers(g) { mark.addTo(map); mark._icon.classList.add("warn"); } - console.log(angle) } } } @@ -204,7 +205,8 @@ async function updateBrouter () { for (let i = 0; i < markers.length - 1 ; i++) { 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`; + const profile = document.querySelector("#brouter-profile").value; + const url = `https://brouter.de/brouter?lonlats=${marker1.lng},${marker1.lat}|${marker2.lng},${marker2.lat}&profile=${profile}&alternativeidx=0&format=geojson`; fetch(url).then((response) => { if (!response.ok) { throw new Error("HTTP error " + response.status); @@ -216,8 +218,6 @@ async function updateBrouter () { map.removeLayer(geojson); } 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}; @@ -251,6 +251,13 @@ map.on('click', function(e) { async function quitEdit(e) { + for (i=0; i< markers.length; i++) { + map.removeLayer(markers[i]); + } + + markers = [] + updateBrouter() + recompute_anglemarkers([]); e.stopPropagation() L.DomEvent.preventDefault(e); editMode = false; @@ -269,11 +276,10 @@ async function pickDirectory(e){ 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]; + const profile = editlayer["brouter-profile"] ?? "rail" ; + document.querySelector("#brouter-profile").value = profile; layerspan.innerHTML = "Editing layer " + layer_legend(l[i]) + "
" break; } @@ -326,6 +332,8 @@ if (edit) { buttonDiv.innerHTML = `
+
+


From 6e2171f65d03faaddc8f7312b815c45c9d5bc94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Thu, 1 May 2025 22:10:23 +0200 Subject: [PATCH 2/6] update the route when the value in the profile is changed --- frontend/common/map.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index c6972b5..9b9c8f1 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -333,7 +333,7 @@ if (edit) {

-
+


From 8c053140a6e4a8d7f30771c14248dea815c0db81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Fri, 2 May 2025 21:34:34 +0200 Subject: [PATCH 3/6] Add mode to edit geojson files on disk --- frontend/common/map.js | 109 ++++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 9b9c8f1..a36834e 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -141,6 +141,13 @@ let geojsons = []; let geojson; let editlayer; +async function purgeLayer(l) { + while (l.length > 0) { + map.removeLayer(l.pop()); + } +} + + function addGeoJsonToMap(dat) { if (editlayer != undefined) { const style = { @@ -163,10 +170,7 @@ function scalarProduct(a,b) { } async function recompute_anglemarkers(g) { - for (k=0; k < anglemarkers.length ; k++) { - map.removeLayer(anglemarkers[k]) - } - anglemarkers = []; + purgeLayer(anglemarkers) const limit = document.getElementById("angle").value for (j=0; j< g.length; j++) { @@ -231,11 +235,8 @@ async function updateBrouter () { } } -map.on('click', function(e) { - if (!editMode) { - return; - } - marker = new L.marker(e.latlng, {draggable: true}) ; +async function addBrouterMarker(pos) { + const marker = new L.marker(pos, {draggable: true}) ; markers.push(marker); marker.on("click", function(e) { map.removeLayer(this); @@ -247,24 +248,62 @@ map.on('click', function(e) { }); marker.addTo(map); updateBrouter(); +} + +map.on('click', function(e) { + if (!editMode) { + return; + } + addBrouterMarker(e.latlng) }); async function quitEdit(e) { - for (i=0; i< markers.length; i++) { - map.removeLayer(markers[i]); - } - + e.stopPropagation() + L.DomEvent.preventDefault(e); + purgeLayer(endmarkers) + purgeLayer(markers) + purgeLayer(mapFeatures) markers = [] updateBrouter() recompute_anglemarkers([]); - e.stopPropagation() - L.DomEvent.preventDefault(e); + editMode = false; document.querySelector(".edit-ui").style.display = "none"; document.getElementById("edit-mode").style.display = "block"; } +const endmarkers = [] +const mapFeatures = [] +const mapJSONs = {} +let editFilename +let editing + +async function startEdit(e) { + editing = true + purgeLayer(endmarkers) + addBrouterMarker(this._latlng) +} + +async function whenClicked(e, feature, filename) { + if (editing) { + return + } + L.DomEvent.stopPropagation(e) + purgeLayer(endmarkers) + const coords = feature.geometry.coordinates + const start = new L.marker(L.latLng(coords[0][1],coords[0][0])).addTo(map) + const end = new L.marker(L.latLng(coords[coords.length-1][1],coords[coords.length-1][0])).addTo(map) + for (let mark of [start, end]) { + endmarkers.push(mark) + mark.on('click', startEdit) + } + editFilename = filename + document.querySelector('#featurename').innerHTML = `Editing ${editFilename.replace(/\.geojson$|\.json$/,"")}
` +} + + + async function pickDirectory(e){ e.stopPropagation() L.DomEvent.preventDefault(e); @@ -275,6 +314,7 @@ async function pickDirectory(e){ } const layerspan = document.querySelector("#layername") layerspan.innerHTML = "" + document.querySelector("#brouter-profile").value = "rail"; for (i = 0; i < l.length ; i++ ) { if (l[i].dirname === dirHandle.name) { editlayer = l[i]; @@ -284,6 +324,21 @@ async function pickDirectory(e){ break; } } + for await (const [name, handle] of dirHandle.entries()) { + if (handle.kind === 'file' && name.endsWith("json")) { + const fileData = await handle.getFile(); + const feature = JSON.parse(await fileData.text()); + const mapFeature = L.geoJson(feature, { + style : { "color": "#000", "width": 5 }, + onEachFeature: function (feature, layer) { + layer.on('click', (event) => whenClicked(event, feature, name)) + } + }) + mapJSONs[name] = feature + mapFeature.addTo(map) + mapFeatures.push(mapFeature) + } + } editMode = true; document.getElementById("edit-mode").style.display = "none"; document.querySelector(".edit-ui").style.display = "block"; @@ -292,30 +347,45 @@ async function pickDirectory(e){ alert("There is no path to save!"); return; } - const filename = window.prompt("Enter filename:", "test"); + const filename = window.prompt("Enter filename:", editFilename ?? "feature"); if (!filename) { return; } - const dat = {type: "FeatureCollection", features: geojsons}; + let dat = {type: "FeatureCollection", features: geojsons}; + if (editFilename != undefined && mapJSONs[editFilename]) { + dat = mapJSONs[editFilename] + dat.features = dat.features.concat(geojsons) + } let file; + let deffilename + if (filename.match(/\.json$|\.geojson$/)) { + deffilename = filename + } else { + deffilename = `${filename}.geojson` + } try { - file = await dirHandle.getFileHandle(`${filename}.geojson`, { + file = await dirHandle.getFileHandle(deffilename, { create: true }); } catch (error) { alert(`Could not open file: ${error.message}`); return } + if (filename != editFilename) { + await dirHandle.removeEntry(editFilename) + } const blob = new Blob([JSON.stringify(dat)]); const writableStream = await file.createWritable(); await writableStream.write(blob); await writableStream.close(); - addGeoJsonToMap(dat); + addGeoJsonToMap({type: "FeatureCollection", features: geojsons}); for (i=0; iEdit
+



From 9b29a713a95efcad557a4fbec907ad9ee84c2a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Fri, 2 May 2025 22:47:05 +0200 Subject: [PATCH 4/6] Rework save button --- frontend/common/map.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index a36834e..7611d81 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -197,6 +197,8 @@ async function updateBrouter () { } markers[markers.length-1]._icon.classList.add("red"); markers[0]._icon.classList.add("green"); + } else { + resetEditing() } geojsons = []; recompute_anglemarkers(geojsons); @@ -204,6 +206,7 @@ async function updateBrouter () { map.removeLayer(geojson); } if (markers.length < 2) { + document.querySelector("#save").disabled = true return; } for (let i = 0; i < markers.length - 1 ; i++) { @@ -221,6 +224,7 @@ async function updateBrouter () { if (geojson != undefined) { map.removeLayer(geojson); } + document.querySelector("#save").disabled = false delete data.features[0].properties.messages; geojsons.push(data.features[0]); recompute_anglemarkers(geojsons); @@ -236,6 +240,10 @@ async function updateBrouter () { } async function addBrouterMarker(pos) { + if (!editing) { + editFilename = undefined + } + purgeLayer(endmarkers) const marker = new L.marker(pos, {draggable: true}) ; markers.push(marker); marker.on("click", function(e) { @@ -279,10 +287,17 @@ const mapJSONs = {} let editFilename let editing +async function resetEditing() { + editFilename = undefined; + editing = false + document.querySelector('#featurename').innerHTML = "" +} + async function startEdit(e) { editing = true purgeLayer(endmarkers) addBrouterMarker(this._latlng) + document.querySelector('#featurename').innerHTML = `Editing ${editFilename.replace(/\.geojson$|\.json$/,"")}
` } async function whenClicked(e, feature, filename) { @@ -299,7 +314,6 @@ async function whenClicked(e, feature, filename) { mark.on('click', startEdit) } editFilename = filename - document.querySelector('#featurename').innerHTML = `Editing ${editFilename.replace(/\.geojson$|\.json$/,"")}
` } @@ -384,8 +398,7 @@ async function pickDirectory(e){ } markers = []; updateBrouter(); - editing = false; - editFilename = undefined; + resetEditing(); alert("Saved file!"); } } @@ -407,7 +420,7 @@ if (edit) {


- +
` } else { buttonDiv.innerHTML = "Your browser does not support editing.
As of 2025, editing is supported on Chromium-based browsers only."; From c4661546f2614eca1d817c666e3c4d27b635fbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Fri, 2 May 2025 23:23:22 +0200 Subject: [PATCH 5/6] Rework filename dialog --- frontend/common/map.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 7611d81..1ef83ec 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -289,6 +289,7 @@ let editing async function resetEditing() { editFilename = undefined; + document.querySelector('#featurename').value = "" editing = false document.querySelector('#featurename').innerHTML = "" } @@ -297,11 +298,10 @@ async function startEdit(e) { editing = true purgeLayer(endmarkers) addBrouterMarker(this._latlng) - document.querySelector('#featurename').innerHTML = `Editing ${editFilename.replace(/\.geojson$|\.json$/,"")}
` } async function whenClicked(e, feature, filename) { - if (editing) { + if (markers.length > 0) { return } L.DomEvent.stopPropagation(e) @@ -314,6 +314,7 @@ async function whenClicked(e, feature, filename) { mark.on('click', startEdit) } editFilename = filename + document.querySelector('#featurename').value = editFilename } @@ -361,7 +362,7 @@ async function pickDirectory(e){ alert("There is no path to save!"); return; } - const filename = window.prompt("Enter filename:", editFilename ?? "feature"); + const filename = document.querySelector('#featurename').value if (!filename) { return; } @@ -385,7 +386,7 @@ async function pickDirectory(e){ alert(`Could not open file: ${error.message}`); return } - if (filename != editFilename) { + if (editFilename != undefined && filename != editFilename) { await dirHandle.removeEntry(editFilename) } const blob = new Blob([JSON.stringify(dat)]); @@ -415,7 +416,7 @@ if (edit) { buttonDiv.innerHTML = `
-
+




From 7ce58ff48ee42dd3116aa65d70cb340d49a65f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antonia=20P=C3=A9rez-Cerezo?= Date: Sat, 3 May 2025 11:13:16 +0200 Subject: [PATCH 6/6] Display distance and number of warnings --- frontend/common/map.js | 12 +++++++++++- frontend/common/styles.css | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/common/map.js b/frontend/common/map.js index 1ef83ec..5b822ce 100644 --- a/frontend/common/map.js +++ b/frontend/common/map.js @@ -172,10 +172,11 @@ function scalarProduct(a,b) { async function recompute_anglemarkers(g) { purgeLayer(anglemarkers) const limit = document.getElementById("angle").value - + let distance = 0 for (j=0; j< g.length; j++) { const coords = g[j].geometry.coordinates; for (let i=1; i < coords.length - 1; i++) { + distance += L.latLng(coords[i][1],coords[i][0]).distanceTo(L.latLng(coords[i-1][1],coords[i-1][0])) 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))) @@ -187,6 +188,14 @@ async function recompute_anglemarkers(g) { } } } + document.querySelector("#distance").innerHTML = `${(distance / 1000).toFixed(2)} km` + const markSpan = document.querySelector("#anglemarkers") + markSpan.innerHTML = `${anglemarkers.length} warning${anglemarkers.length != 1 ? "s" : ""}` + if (anglemarkers.length > 0) { + markSpan.classList.add("warning") + } else { + markSpan.classList.remove("warning") + } } async function updateBrouter () { @@ -416,6 +425,7 @@ if (edit) { buttonDiv.innerHTML = `
+0.0 km - 0 warnings




diff --git a/frontend/common/styles.css b/frontend/common/styles.css index 731b8bb..12343d5 100644 --- a/frontend/common/styles.css +++ b/frontend/common/styles.css @@ -27,3 +27,8 @@ img.warn { filter: hue-rotate(160deg); } div.edit-ui { display: none; } + +span.warning { + color: red; + font-weight: bold; +}