diff --git a/README.md b/README.md index d6db7f0..acbaeda 100644 --- a/README.md +++ b/README.md @@ -82,20 +82,6 @@ value also increases the size of the `strecken.pmtiles` file. If not given explicitly `maxZoom` defaults to 10. -## Map Editing Mode - -As a new experimental feature, Streckenkarte implements a simple -[brouter][brouter] frontend to allow for easy editing, currently only -supported on browsers that support -[showDirectoryPicker](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker). It -can be accessed by adding `?edit=1` to the URL and then clicking the -"edit" button that appears in the top right corner. Then, the -directory containing the files for the layer that is to be edited can -be opened in the file selection dialog and lines can be drawn as in -the normal brouter web frontend. By clicking on "save", the lines are -saved locally in the chosen directory and will only appear on the -production map if processed correctly by the tile-generating script. - ## Troubleshooting ### My pmtiles file is huge (hundreds of megabytes) diff --git a/frontend/common/map.js b/frontend/common/map.js deleted file mode 100644 index 771c311..0000000 --- a/frontend/common/map.js +++ /dev/null @@ -1,206 +0,0 @@ -function layer_legend(layer) { - return '' + layer["name"]; -} - -const rules = new Array; -const l = new Array; -const legend = L.control({position: 'bottomleft'}); -legend.onAdd = function (map) { - const div = L.DomUtil.create('div', 'legend'); - for (let i = 0; i < l.length; i++) { - div.innerHTML += layer_legend(l[i]) + "
"; - } - return div; -}; - - -const map = L.map("map", { center: [52,13], zoom: 3, minZoom: 0 }); - -function update_hash() { - const {lat, lng} = this.getCenter(); - const zoom = this.getZoom(); - const digits = 4; - window.history.replaceState(null, '', `#map=${zoom}/${lat.toFixed(digits)}/${lng.toFixed(digits)}`); -} - -function onHashChange() { - const hash = document.location.hash; - const coords = decodeURIComponent(hash.slice(5)).split("/") // strip off the #map= part - const latLng = L.latLng(parseFloat(coords[1]), parseFloat(coords[2])); - map.setView(latLng, parseInt(coords[0])); -} - -map.on("moveend", update_hash); -map.on("zoomend", update_hash); - -fetch("layers.json") - .then((response) => response.json()) - .then((data) => { - 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"] }); - rules.push({ - dataLayer: key, - symbolizer: new protomapsL.LineSymbolizer(layers[key]) - }); - } - const tiles = data["tilelayer"] - const osm = L.tileLayer( - tiles["url_template"], - { - maxZoom: 19, - attribution: tiles["attribution"] - } - ); - const strecken = protomapsL.leafletLayer({ - url: "strecken.pmtiles", - maxDataZoom: data["maxZoom"] ?? 10, - maxZoom: 19, - paintRules: rules, - }); - - osm.addTo(map); - legend.addTo(map); - strecken.addTo(map); - }) - -let dirHandle; -let editMode = false; -let markers = []; -let geojsons = []; -let geojson; -let editlayer; - -function addGeoJsonToMap(dat) { - if (editlayer != undefined) { - const style = { - "color": editlayer.color - }; - g = L.geoJSON(dat, {style: style}); - } else { - g = L.geoJSON(dat); - } - g.addTo(map); - return g; -} - -async function updateBrouter () { - if (markers.length > 0) { - for (i=1; i< markers.length-1; i++) { - markers[i]._icon.classList.remove("red"); - } - markers[markers.length-1]._icon.classList.add("red"); - markers[0]._icon.classList.add("green"); - } - if (markers.length < 2) { - if (geojson != undefined) { - map.removeLayer(geojson); - } - return; - } - geojsons = []; - 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`; - fetch(url).then((response) => response.json()) - .then((data) => { - if (geojson != undefined) { - map.removeLayer(geojson); - } - geojsons.push(data.features[0]); - const dat = {type: "FeatureCollection", features: geojsons}; - geojson = addGeoJsonToMap(dat); - }) - } -} - -map.on('click', function(e) { - if (!editMode) { - return; - } - marker = new L.marker(e.latlng, {draggable: true}) ; - markers.push(marker); - marker.on("click", function(e) { - map.removeLayer(this); - markers = markers.filter(item => item != this); - updateBrouter(); - }) - marker.on("dragend", function(e) { - updateBrouter(); - }); - marker.addTo(map); - updateBrouter(); -}); - - -async function pickDirectory(e){ - e.stopPropagation() - L.DomEvent.preventDefault(e); - if (!editMode) { - dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' }); - for (i = 0; i < l.length ; i++ ) { - console.log(l[i].dirname); - if (l[i].dirname === dirHandle.name) { - editlayer = l[i]; - this.innerHTML = "Editing layer " + layer_legend(l[i]) + "
" + this.innerHTML - break; - } - } - - document.getElementById("edit-mode").style.color = "red"; - document.getElementById("edit-mode").innerHTML = "save"; - editMode = true; - } else { - const filename = window.prompt("Enter filename:", "test"); - const dat = {type: "FeatureCollection", features: geojsons}; - const file = await dirHandle.getFileHandle(`${filename}.geojson`, { - create: true - }); - const blob = new Blob([JSON.stringify(dat)]); - const writableStream = await file.createWritable(); - await writableStream.write(blob); - await writableStream.close(); - addGeoJsonToMap(dat); - for (i=0; i { - const buttonDiv = L.DomUtil.create('div', 'legend'); - if ("showDirectoryPicker" in window) { - buttonDiv.innerHTML = ``; - buttonDiv.addEventListener('click', pickDirectory, this) - } else { - buttonDiv.innerHTML = "Your browser does not support editing.
As of 2025, editing is supported on Chromium-based browsers only."; - } - return buttonDiv; - }; - customButton.addTo(map) -} - - -function resize() { - document.getElementById("map").style.height = window.innerHeight + 'px'; -} -resize(); -window.addEventListener('resize', () => { - resize(); -}); - -window.addEventListener("hashchange", onHashChange); -onHashChange(); - - - diff --git a/frontend/map.js b/frontend/map.js new file mode 100644 index 0000000..413d285 --- /dev/null +++ b/frontend/map.js @@ -0,0 +1,75 @@ + +const rules = new Array; +const l = new Array; +var legend = L.control({position: 'bottomleft'}); +legend.onAdd = function (map) { + var div = L.DomUtil.create('div', 'legend'); + for (var i = 0; i < l.length; i++) { + div.innerHTML += '' + l[i]["name"] + "
"; + } + return div; +}; + + +const map = L.map("map", { center: [52,13], zoom: 3, minZoom: 0 }); + +function update_hash() { + const {lat, lng} = this.getCenter(); + const zoom = this.getZoom(); + const digits = 4; + window.history.replaceState(null, '', `#map=${zoom}/${lat.toFixed(digits)}/${lng.toFixed(digits)}`); +} + +function onHashChange() { + const hash = document.location.hash; + const coords = decodeURIComponent(hash.slice(5)).split("/") // strip off the #map= part + const latLng = L.latLng(parseFloat(coords[1]), parseFloat(coords[2])); + map.setView(latLng, parseInt(coords[0])); +} + +map.on("moveend", update_hash); +map.on("zoomend", update_hash); + +fetch("layers.json") + .then((response) => response.json()) + .then((data) => { + document.title = data["name"] + layers = data["layers"] + for (let key in layers) { + l.push({ name: layers[key]["humanname"], color: layers[key]["color"] }); + rules.push({ + dataLayer: key, + symbolizer: new protomapsL.LineSymbolizer(layers[key]) + }); + } + const tiles = data["tilelayer"] + var osm = L.tileLayer( + tiles["url_template"], + { + maxZoom: 19, + attribution: tiles["attribution"] + } + ); + var strecken = protomapsL.leafletLayer({ + url: "strecken.pmtiles", + maxDataZoom: data["maxZoom"] ?? 10, + maxZoom: 19, + paintRules: rules, + }); + + osm.addTo(map); + legend.addTo(map); + strecken.addTo(map); + }) + +function resize() { + document.getElementById("map").style.height = window.innerHeight + 'px'; +} +resize(); +window.addEventListener('resize', () => { + resize(); +}); + +window.addEventListener("hashchange", onHashChange); +onHashChange(); + diff --git a/frontend/common/styles.css b/frontend/styles.css similarity index 77% rename from frontend/common/styles.css rename to frontend/styles.css index 150beda..d756f67 100644 --- a/frontend/common/styles.css +++ b/frontend/styles.css @@ -19,5 +19,3 @@ body { display: inline-block; margin-right: 0.7em; } -img.red { filter: hue-rotate(120deg); } -img.green { filter: hue-rotate(-120deg); }