var editor_mode = 0; var mymap; var polyconf_show_street_names = 5; // Zoom level for when to start showing street names var polyconf_show_cities = 5; /* City outlines will be filled on this level and further away */ var polyconf_show_districts = 4; /* Shown from polyconf_show_cities until this*/ var dijkstraserv_base = "https://notsyncing.net/dijkstra/"; var wikiurl_base = "https://wiki.linux-forks.de/mediawiki/index.php/" var tiles_base = "https://notsyncing.net/maps.linux-forks.de/tiles/" var streetLabelsRenderer = new L.StreetLabels({ collisionFlg: true, propertyName: 'name', showLabelIf: function (layer) { switch (layer.geometry.type) { case "LineString": if (mymap.getZoom() <= polyconf_show_street_names) return false; break; case "Polygon": if (mymap.getZoom() > polyconf_show_street_names) return false; break; default: return false; } return true; }, fontStyle: { dynamicFontSize: true, fontSize: 10, fontSizeUnit: "px", lineWidth: 3.0, fillStyle: "black", strokeStyle: "white", }, }); streetLabelsRenderer._getCollisionFlag = function (layer) { if (!(layer instanceof L.Polygon)) // Always check collision for streets return true; zoom = mymap.getZoom(); return (zoom < 5); } streetLabelsRenderer._getDynamicFontSize = function (layer) { zoom = mymap.getZoom(); switch (layer.geometry.type) { case "LineString": if (zoom <= 8) return 11; else return 2**(zoom - 9) * 11; break; case "Polygon": var size; if (zoom >= 4) size = 30; else size = 20; switch (layer.properties.type) { case "city": break; case "town": size *= 2.0/3.0; break; case "village": size *= 1.5/3; break; case "district": size *= 2.0/3; break; default: size *= 0.5/3; break; } return size; default: break; } return 10; } var style_outlines = { radius: 8, fillColor: "#ff7800", color: "black", opacity: 1.0, fillOpacity: 0.5 }; var style_streets = { }; var style_route = { radius: 8, fillColor: "#00ff00", color: "red", opacity: 1.0, fillOpacity: 0.5 }; // Projection fix from: https://gis.stackexchange.com/questions/200865/leaflet-crs-simple-custom-scale var factorx = 1 / 256 * 4; var factory = factorx; var originx = 7000 + 8 + 0.5; var originy = 7000 + 8 - 0.5; var zoom_level_real = 6; L.CRS.pr = L.extend({}, L.CRS.Simple, { projection: L.Projection.LonLat, transformation: new L.Transformation(factorx,factorx * originx,-factory,factory * originy), scale: function(zoom) { return Math.pow(2, zoom); }, zoom: function(scale) { return Math.log(scale) / Math.LN2; }, distance: function(latlng1, latlng2) { var dx = latlng2.lng - latlng1.lng , dy = latlng2.lat - latlng1.lat; return Math.sqrt(dx * dx + dy * dy); }, infinite: true }); // Init map mymap = L.map('mapid', { renderer: streetLabelsRenderer, editable: true, crs: L.CRS.pr }).setView([0, 0], 5); var mapheight = 16384; var mapwidth = mapheight; var sw = mymap.unproject([0, 0], zoom_level_real); var ne = mymap.unproject([mapwidth, mapheight], zoom_level_real); var layerbounds = new L.LatLngBounds(sw,ne); var layers = L.control.layers({}, {}).addTo(mymap); function load_svg(name, url, active=1) { var xhttp_ps = new XMLHttpRequest(); xhttp_ps.onreadystatechange = function() { if (this.readyState == 4) { if (this.status == 200) { var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg"); svgElement.setAttribute('viewBox', "0 0 16384 16384"); svgElement.innerHTML = xhttp_ps.responseText; var svgElementBounds = [[0, 0], [1000, 1000]]; var overlay = L.svgOverlay(svgElement, svgElementBounds); layers.addOverlay(overlay, name); if (active) overlay.addTo(mymap); return overlay; } else { alert("Error: Could not load SVG map layer (" + name + ")"); } } } ; xhttp_ps.open("GET", url, true); xhttp_ps.send(); } function load_tiles(name, id) { var url = ""; if (name != "") { url = tiles_base + '{id}/{z}/{y}/{x}.png'; } var satellite = L.tileLayer(url, { maxZoom: 14 /*8*/, maxNativeZoom: 6, minNativeZoom: 0, minZoom: 0, noWrap: true, attribution: 'Map data © Linux-Forks', id: id, tileSize: 256, zoomOffset: 0, opacity: 1.0, bounds: layerbounds }); layers.addBaseLayer(satellite, name); return satellite; } var current_location = ""; var current_feature = null; function load_geojson(name, url, geotype, iconcolor, active=1, style={}) { var xhttp_ps = new XMLHttpRequest(); xhttp_ps.onreadystatechange = function() { if (this.readyState == 4) { if (this.status == 200) { onEachFeature = null; pointToLayer = null; filter = null; switch (geotype) { case "street": onEachFeature = function(feature, layer) { layer.myTag = geotype; layer.myName = name; layer.on("click", function (e) { current_feature = feature; }); }; break; case "outline": onEachFeature = function(feature, layer) { layer.myTag = geotype; layer.myName = name; layer.on("click", function (e) { current_location = feature.properties.name; }); } break; default: /* else it is a marker with the specified icon */ onEachFeature = function(feature, layer) { label = String(feature.properties.name) layer.bindPopup('

' + feature.properties.name + ' (' + feature.geometry.coordinates + ')

' + '

' + '

' + feature.properties.description + '

'); layer.bindTooltip(label, { permanent: true, direction: "center", className: "city-names" }).openTooltip().on('click', jump_to_marker); }; pointToLayer = function(feature, latlng) { label = String(feature.properties.name) if (geotype == "auto") { iconname = "star"; // default icon iconcolor = "orange"; for (var i = 0; i < feature.properties.categories.length; i++) { category = feature.properties.categories[i].toLowerCase(); switch (category) { case "stations": iconname = "train"; iconcolor = "blue"; break; case "shops": iconname = "shopping-cart"; iconcolor = "green"; break; case "city": case "village": case "town": case "settlements": iconname = "city"; iconcolor = "red"; break; case "parks": iconname = "tree"; iconcolor = "green"; break; case "courts": iconname = "balance-scale"; iconcolor = "black"; break; case "train depots": iconname = "wrench"; iconcolor = "violet"; break; case "hotels": iconname = "hotel"; iconcolor = "gray"; break; case "beaches": iconname = "umbrella-beach"; iconcolor = "orange"; break; case "waterway": iconname = "water"; iconcolor = "blue"; break; } if (iconname != "star") break; } } else { iconname = geotype; } marker = new L.marker(latlng,{ icon: L.AwesomeMarkers.icon({ icon: iconname, markerColor: iconcolor }) }).bindTooltip(label, { permanent: false, direction: "center", opacity: 0.7 }).openTooltip(); return marker; }; break; } var json = JSON.parse(xhttp_ps.responseText); geojson = L.geoJSON(json, { style: style, onEachFeature: onEachFeature, pointToLayer: pointToLayer, filter: filter }); layers.addOverlay(geojson, name); if (active) geojson.addTo(mymap); return geojson; } else { console.log("Error: Could not load geojson map layer (" + name + ")."); } } } ; xhttp_ps.open("GET", url, true); xhttp_ps.send(); } load_tiles("Satellite (2020-04-09)", 'world-2020-04-09').addTo(mymap); load_tiles("Satellite (2019-05-04, wrong coords)", 'world-2019-05-04'); load_tiles("None", ''); load_geojson("All", "./geojson/all.json", "auto", "auto", 0); load_geojson("Streets", "./geojson/streets.json", "street", "auto", 1, style_streets); load_geojson("Cities", "./geojson/city_outlines.json", "outline", "auto", 1, style_outlines); L.control.scale().addTo(mymap); function resolve_latlng(latlng, recenter = 0) { latlng.lng = Math.round(latlng.lng); latlng.lat = Math.round(latlng.lat); return latlng; } function get_current_location_str() { var latlng = resolve_latlng(mymap.getCenter()); return latlng.lng + "," + latlng.lat + "," + mymap.getZoom(); } /* Important: Do not use mymap.setView, use this function instead */ function jump_to(latlng, zoom = -1) { if (zoom == -1) zoom = mymap.getZoom(); if (!editor_mode) { pos = resolve_latlng(latlng); document.location.hash = "#" + pos.lng + "," + pos.lat + "," + zoom; } else { mymap.setView(latlng, zoom); } } function jump_to_marker(e) { jump_to(e.target.getLatLng()); } function prompt_location() { var str = prompt("Enter coordinates to jump to:\n\n" + "Format: x, y [, zoom]", get_current_location_str()); if (str) { if (!editor_mode) document.location.hash = "#" + str; else onHashChange(null, "#" + str); } } var search_element; var route_element; function build_sidebar() { if (!search_element) { search_element = document.createElement("div"); search_element.style.overflow = "scroll"; search_element.style.padding = "6px"; search_element.style.height = "100%"; search_element.innerHTML = '
'; } if (!route_element) { route_element = document.createElement("div"); route_element.style.overflow = "scroll"; route_element.style.padding = "6px"; route_element.style.height = "100%"; route_element.innerHTML = '<-- Return to search


'; } } var current_sidebar = "search"; function toggle_search(type = current_sidebar, force_act = 0) { if (el = document.getElementById('sidebar')) { el.parentNode.removeChild(el); if (force_act == 0 && type == current_sidebar) return; } td = document.getElementById('windowtr').insertCell(0); td.style.width = "400px"; td.style.borderRight = "1px solid black"; td.style.verticalAlign = "top"; td.id = "sidebar"; switch (type) { case "search": td.appendChild(search_element); document.getElementById('search_query').select(); break; case "route": td.appendChild(route_element); break; default: alert("JS error: sidebar"); break; } current_sidebar = type; } function htmlEntities(str) { return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function polyline_get_middle_coords(coords) { var ret = [2]; ret[0] = coords[0][0] + (coords[coords.length - 1][0] - coords[0][0]) / 2; ret[1] = coords[0][1] + (coords[coords.length - 1][1] - coords[0][1]) / 2; return ret; } var highlighted_line; var default_street_color = "#3388ff"; function search(e) { var query = document.getElementById("search_query").value; var real_query = true; document.getElementById('search_results').innerHTML = ""; if (true) { if (query.length == 0) { real_query = false; query = "!@#$%^&"; // Cheap workaround to (hopefully) match nothing } results = document.createElement("ul"); for (var i = 0; i < layers._layers.length; i++) { if (!layers._layers[i].layer._layers) continue; for (key in layers._layers[i].layer._layers) { item = layers._layers[i].layer._layers[key]; switch (item.feature.geometry.type) { case "Point": regex = new RegExp(query, 'i'); if (item.feature.properties.name.match(regex)) { el = document.createElement("li"); el.innerHTML = "[" + layers._layers[i].name + "] " + '' + htmlEntities(item.feature.properties.name) + ""; results.appendChild(el); item.setOpacity(1.0); // TODO: setOpacity does not disable hover and click events } else { if (real_query) item.setOpacity(0.0); // TODO else item.setOpacity(1.0); // TODO } break; case "LineString": if (item.options.color != default_street_color) { // De-hilight last search item.options.color = default_street_color; item.redraw(); } regex = new RegExp(query, 'i'); if (item.feature.properties.name.match(regex)) { item.options.color = "#FF0000"; item.redraw(); el = document.createElement("li"); zpos = polyline_get_middle_coords(item.feature.geometry.coordinates); el.innerHTML = "[" + layers._layers[i].name + "] " + '' + htmlEntities(item.feature.properties.name) + ""; results.appendChild(el); } break; case "Polygon": regex = new RegExp(query, 'i'); if (item.feature.properties.name.match(regex)) { el = document.createElement("li"); zpos = resolve_latlng(layers._layers[i].layer._layers[item._leaflet_id].getCenter()); el.innerHTML = "[" + layers._layers[i].name + "] " + '' + htmlEntities(item.feature.properties.name) + ""; results.appendChild(el); } break; default: break; } } } document.getElementById('search_results').appendChild(results); } return false; } var route_layer; function route(e) // BOOKMARK { if (route_layer) { mymap.removeLayer(route_layer); } start = document.getElementById('route_start').value; destination = document.getElementById('route_destination').value; if ((start == "") || (destination == "")) { document.getElementById('route_results').innerHTML = ""; return; } document.getElementById('route_submit').disabled = true; document.getElementById('route_results').innerHTML = "Loading..."; var xhttp_ps = new XMLHttpRequest(); xhttp_ps.onreadystatechange = function() { if (this.readyState == 4) { if (this.status == 200) { var json = JSON.parse(xhttp_ps.responseText); geojson = L.geoJSON(json, { style: style_route }); route_layer = geojson.addTo(mymap); str = "
    "; last_through = ""; geojson.eachLayer( function(layer) { if (last_through != layer.feature.properties.through) { str += '
  1. ' + htmlEntities(layer.feature.properties.description) + "
  2. "; last_through = layer.feature.properties.through; } }); str += "
"; document.getElementById('route_results').innerHTML = str; } else { document.getElementById('route_results').innerHTML = "No results."; } document.getElementById('route_submit').disabled = false; } }; coordstr = start + ';' + destination; xhttp_ps.open("GET", dijkstraserv_base + coordstr, true); xhttp_ps.send(); } function set_route_start(latlng) { toggle_search("route", 1); document.getElementById('route_start').value = latlng.lat + ',' + latlng.lng; route(); } function set_route_destination(latlng) { toggle_search("route", 1); document.getElementById('route_destination').value = latlng.lat + ',' + latlng.lng; route(); } L.MyControl = L.Control.extend({ options: { position: 'topleft', callback: null, kind: '', html: '' }, onAdd: function (map) { var container = L.DomUtil.create('div', 'leaflet-control leaflet-bar'), link = L.DomUtil.create('a', '', container); link.href = '#'; link.title = this.options.title; link.innerHTML = this.options.html; L.DomEvent.on(link, 'mousedown', L.DomEvent.stop); L.DomEvent.on(link, 'mouseup', L.DomEvent.stop); L.DomEvent.on(link, 'click', L.DomEvent.stop) .on(link, 'click', function () { window.LAYER = this.options.callback.call(); }, this); return container; } }); L.SearchControl = L.MyControl.extend({ options: { position: 'topleft', callback: toggle_search, title: 'Search', html: '🔍' } }); L.GotoControl = L.MyControl.extend({ options: { position: 'topleft', callback: prompt_location, title: 'Jump to coordinates', html: '⌖' } }); mymap.addControl(new L.SearchControl()); mymap.addControl(new L.GotoControl()); L.CurrentPosControl = L.Control.extend({ options: { position : 'bottomleft', callback: null, kind: "", html: '' }, onAdd: function (map) { container = L.DomUtil.create('div', 'leaflet-control-scale leaflet-bar'); container.style.visibility = "hidden"; div = L.DomUtil.create('div', '', container); div.style.backgroundColor = "white"; div.style.paddingLeft = "4px"; div.style.paddingRight = "4px"; div.style.borderRadius = "2px"; div.style.margin = "2px"; div.style.opacity = "0.6"; div.style.fontWeight = "bold"; div.innerHTML = ""; div.id = "current_position_label"; return container; } }); mymap.addControl(new L.CurrentPosControl()); var popup = L.popup(); L.AwesomeMarkers.Icon.prototype.options.prefix = 'fa'; var baseballIcon = L.AwesomeMarkers.icon({ icon: 'coffee', markerColor: 'red' }); function onMapClick(e) { var addinfo = ""; pos = resolve_latlng(e.latlng); route_links = '
[route from here]'; route_links += ' [route to here]'; if (current_location != "") addinfo = " (part of " + current_location + ")"; if (current_feature) { popup.setLatLng(e.latlng).setContent("This is " + current_feature.properties.name + addinfo + route_links).openOn(mymap); } else { popup.setLatLng(e.latlng).setContent("You clicked the map at " + pos.lng + "," + pos.lat + addinfo + route_links).openOn(mymap); } current_feature = null; current_location = ""; } mymap.on('click', onMapClick); var is_user_drag = 0; function update_hash_from_position(e) { if (is_user_drag) is_user_drag = 0; else return; if (!editor_mode) document.location.hash = "#" + get_current_location_str(); } function dragstart(e) { is_user_drag = 1; } function update_aa_status() { if (mymap.getZoom() > zoom_level_real) document.getElementById('mapid').classList.add("no-aa"); else document.getElementById('mapid').classList.remove("no-aa"); } function update_street_width() { zoom = mymap.getZoom(); var w; if (zoom <= 6) { w = 2; } else { w = 2**zoom * 0.016 * 2; } var myStyle = {weight: w, opacity: 0.7}; mymap.eachLayer( function(layer) { if ( layer.myTag && layer.myTag === "street") { layer.setStyle(myStyle); } }); } function update_outline_visibility() { zoom = mymap.getZoom(); mymap.eachLayer( function(layer) { if ( layer.myTag && layer.myTag === "outline") { var fillOpacity; if (zoom <= polyconf_show_cities) fillOpacity = 0.5; //else if (zoom == polyconf_show_cities + 1) // opacity = 0.2; else fillOpacity = 0.0; layer.setStyle({fillOpacity: fillOpacity}); } }); } function update_current_mouse_position(e) { var lbl = document.getElementById('current_position_label'); if (e.type == "mouseout") { lbl.parentElement.style.visibility = "hidden"; return; } lbl.parentElement.style.visibility = "visible"; pos = resolve_latlng(e.latlng); lbl.innerHTML = pos.lng + "," + pos.lat; } mymap.on('zoomend', function () {is_user_drag = 1; update_hash_from_position();}); mymap.on('zoomend', update_aa_status); mymap.on('zoomend', update_street_width); mymap.on('zoomend', update_outline_visibility); mymap.on('moveend', update_hash_from_position); mymap.on('dragstart', function () { is_user_drag = 1;}); mymap.on('keydown', function (e) { if (e.originalEvent.code.match(/Arrow.*/)) is_user_drag = 1;}); mymap.on('overlayadd', function (e) { update_street_width(); update_outline_visibility(); }); mymap.on('mousemove', update_current_mouse_position); mymap.on('mouseout', update_current_mouse_position); function onHashChange(e, hash=null) { if (!hash) hash = document.location.hash; if (hash == "") return; if (hash == "#" + get_current_location_str()) return; // We're already there coordstr = decodeURIComponent(hash.slice(1)).split(","); if (coordstr.length < 2) coordstr.push(0); // Default y if (coordstr.length < 3) coordstr.push(mymap.getZoom()); // Default zoom var latlng = L.latLng(parseFloat(coordstr[1]), parseFloat(coordstr[0])); mymap.setView(latlng, parseInt(coordstr[2])); if (coordstr.length > 3) { /* Drop named marker */ foo = L.marker(latlng).bindPopup(htmlEntities(coordstr[3])).addTo(mymap).openPopup(); } } build_sidebar(); window.addEventListener("hashchange", onHashChange, false); onHashChange(null);