X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/703990a5bbdcdb782ef85983b960253aecc3e163..98e4dfb87ebffaf1a2fb93d76ac9816676f23547:/app/assets/javascripts/leaflet.map.js?ds=sidebyside diff --git a/app/assets/javascripts/leaflet.map.js b/app/assets/javascripts/leaflet.map.js index d053a81ce..1324532d3 100644 --- a/app/assets/javascripts/leaflet.map.js +++ b/app/assets/javascripts/leaflet.map.js @@ -1,5 +1,3 @@ -//= require qs/dist/qs - L.extend(L.LatLngBounds.prototype, { getSize: function () { return (this._northEast.lat - this._southWest.lat) * @@ -15,122 +13,34 @@ L.OSM.Map = L.Map.extend({ initialize: function (id, options) { L.Map.prototype.initialize.call(this, id, options); - const layerDefinitions = [ - { - leafletOsmId: "Mapnik", - code: "M", - keyId: "mapnik", - nameId: "standard", - credit: { - id: "make_a_donation", - href: "https://supporting.openstreetmap.org", - donate: true - } - }, - { - leafletOsmId: "CyclOSM", - code: "Y", - keyId: "cyclosm", - nameId: "cyclosm", - credit: { - id: "cyclosm_credit", - children: { - cyclosm_link: { - id: "cyclosm_name", - href: "https://www.cyclosm.org" - }, - osm_france_link: { - id: "osm_france", - href: "https://openstreetmap.fr/" - } - } - } - }, - { - leafletOsmId: "CycleMap", - code: "C", - keyId: "cyclemap", - nameId: "cycle_map", - apiKeyId: "THUNDERFOREST_KEY", - credit: { - id: "thunderforest_credit", - children: { - thunderforest_link: { - id: "andy_allan", - href: "https://www.thunderforest.com/" - } - } - } - }, - { - leafletOsmId: "TransportMap", - code: "T", - keyId: "transportmap", - nameId: "transport_map", - apiKeyId: "THUNDERFOREST_KEY", - credit: { - id: "thunderforest_credit", - children: { - thunderforest_link: { - id: "andy_allan", - href: "https://www.thunderforest.com/" - } - } - } - }, - { - leafletOsmId: "TracestrackTopo", - code: "P", - keyId: "tracestracktopo", - nameId: "tracestracktop_topo", - apiKeyId: "TRACESTRACK_KEY", - credit: { - id: "tracestrack_credit", - children: { - tracestrack_link: { - id: "tracestrack", - href: "https://www.tracestrack.com/" - } - } - } - }, - { - leafletOsmId: "HOT", - code: "H", - keyId: "hot", - nameId: "hot", - credit: { - id: "hotosm_credit", - children: { - hotosm_link: { - id: "hotosm_name", - href: "https://www.hotosm.org/" - }, - osm_france_link: { - id: "osm_france", - href: "https://openstreetmap.fr/" - } - } - } - } - ]; - this.baseLayers = []; - for (const layerDefinition of layerDefinitions) { + for (const layerDefinition of OSM.LAYER_DEFINITIONS) { if (layerDefinition.apiKeyId && !OSM[layerDefinition.apiKeyId]) continue; - const layerOptions = { - attribution: makeAttribution(layerDefinition.credit), - code: layerDefinition.code, - keyid: layerDefinition.keyId, - name: I18n.t(`javascripts.map.base.${layerDefinition.nameId}`) - }; - if (layerDefinition.apiKeyId) { - layerOptions.apikey = OSM[layerDefinition.apiKeyId]; + let layerConstructor = L.OSM.TileLayer; + const layerOptions = {}; + + for (const [property, value] of Object.entries(layerDefinition)) { + if (property === "credit") { + layerOptions.attribution = makeAttribution(value); + } else if (property === "nameId") { + layerOptions.name = I18n.t(`javascripts.map.base.${value}`); + } else if (property === "apiKeyId") { + layerOptions.apikey = OSM[value]; + } else if (property === "leafletOsmId") { + layerConstructor = L.OSM[value]; + } else if (property === "leafletOsmDarkId" && OSM.isDarkMap() && L.OSM[value]) { + layerConstructor = L.OSM[value]; + } else { + layerOptions[property] = value; + } } - const layer = new L.OSM[layerDefinition.leafletOsmId](layerOptions); + const layer = new layerConstructor(layerOptions); + layer.on("add", () => { + this.fire("baselayerchange", { layer: layer }); + }); this.baseLayers.push(layer); } @@ -144,8 +54,14 @@ L.OSM.Map = L.Map.extend({ pane: "overlayPane", code: "G" }); + this.gpsLayer.on("add", () => { + this.fire("overlayadd", { layer: this.gpsLayer }); + }).on("remove", () => { + this.fire("overlayremove", { layer: this.gpsLayer }); + }); + - this.on("layeradd", function (event) { + this.on("baselayerchange", function (event) { if (this.baseLayers.indexOf(event.layer) >= 0) { this.setMaxZoom(event.layer.options.maxZoom); } @@ -190,26 +106,25 @@ L.OSM.Map = L.Map.extend({ link.attr("target", "_blank"); } return link.prop("outerHTML"); - } else { - return text; } + return text; } }, updateLayers: function (layerParam) { - var layers = layerParam || "M", - layersAdded = ""; - - for (var i = this.baseLayers.length - 1; i >= 0; i--) { - if (layers.indexOf(this.baseLayers[i].options.code) >= 0) { - this.addLayer(this.baseLayers[i]); - layersAdded = layersAdded + this.baseLayers[i].options.code; - } else if (i === 0 && layersAdded === "") { - this.addLayer(this.baseLayers[i]); - } else { - this.removeLayer(this.baseLayers[i]); + const oldBaseLayer = this.getMapBaseLayer(); + let newBaseLayer; + + for (const layer of this.baseLayers) { + if (!newBaseLayer || layerParam.includes(layer.options.code)) { + newBaseLayer = layer; } } + + if (newBaseLayer !== oldBaseLayer) { + if (oldBaseLayer) this.removeLayer(oldBaseLayer); + if (newBaseLayer) this.addLayer(newBaseLayer); + } }, getLayersCode: function () { @@ -223,25 +138,25 @@ L.OSM.Map = L.Map.extend({ }, getMapBaseLayerId: function () { - var baseLayerId; - this.eachLayer(function (layer) { - if (layer.options && layer.options.keyid) baseLayerId = layer.options.keyid; - }); - return baseLayerId; + const layer = this.getMapBaseLayer(); + if (layer) return layer.options.layerId; + }, + + getMapBaseLayer: function () { + for (const layer of this.baseLayers) { + if (this.hasLayer(layer)) return layer; + } }, getUrl: function (marker) { - var precision = OSM.zoomPrecision(this.getZoom()), - params = {}; + const params = {}; if (marker && this.hasLayer(marker)) { - var latLng = marker.getLatLng().wrap(); - params.mlat = latLng.lat.toFixed(precision); - params.mlon = latLng.lng.toFixed(precision); + [params.mlat, params.mlon] = OSM.cropLocation(marker.getLatLng(), this.getZoom()); } var url = window.location.protocol + "//" + OSM.SERVER_URL + "/", - query = Qs.stringify(params), + query = new URLSearchParams(params), hash = OSM.formatHash(this); if (query) url += "?" + query; @@ -253,16 +168,15 @@ L.OSM.Map = L.Map.extend({ getShortUrl: function (marker) { var zoom = this.getZoom(), latLng = marker && this.hasLayer(marker) ? marker.getLatLng().wrap() : this.getCenter().wrap(), - str = window.location.hostname.match(/^www\.openstreetmap\.org/i) ? - window.location.protocol + "//osm.org/go/" : - window.location.protocol + "//" + window.location.hostname + "/go/", + str = window.location.protocol + "//" + window.location.hostname.replace(/^www\.openstreetmap\.org/i, "osm.org") + "/go/", char_array = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_~", x = Math.round((latLng.lng + 180.0) * ((1 << 30) / 90.0)), y = Math.round((latLng.lat + 90.0) * ((1 << 30) / 45.0)), // JavaScript only has to keep 32 bits of bitwise operators, so this has to be // done in two parts. each of the parts c1/c2 has 30 bits of the total in it // and drops the last 4 bits of the full 64 bit Morton code. - c1 = interlace(x >>> 17, y >>> 17), c2 = interlace((x >>> 2) & 0x7fff, (y >>> 2) & 0x7fff), + c1 = interlace(x >>> 17, y >>> 17), + c2 = interlace((x >>> 2) & 0x7fff, (y >>> 2) & 0x7fff), digit, i; @@ -291,22 +205,22 @@ L.OSM.Map = L.Map.extend({ return (interlaced_x << 1) | interlaced_y; } - var params = {}; + const params = new URLSearchParams(); var layers = this.getLayersCode().replace("M", ""); if (layers) { - params.layers = layers; + params.set("layers", layers); } if (marker && this.hasLayer(marker)) { - params.m = ""; + params.set("m", ""); } if (this._object) { - params[this._object.type] = this._object.id; + params.set(this._object.type, this._object.id); } - var query = Qs.stringify(params); + const query = params.toString(); if (query) { str += "?" + query; } @@ -315,21 +229,14 @@ L.OSM.Map = L.Map.extend({ }, getGeoUri: function (marker) { - var precision = OSM.zoomPrecision(this.getZoom()), - latLng, - params = {}; + let latLng = this.getCenter(); + const zoom = this.getZoom(); if (marker && this.hasLayer(marker)) { - latLng = marker.getLatLng().wrap(); - } else { - latLng = this.getCenter(); + latLng = marker.getLatLng(); } - params.lat = latLng.lat.toFixed(precision); - params.lon = latLng.lng.toFixed(precision); - params.zoom = this.getZoom(); - - return "geo:" + params.lat + "," + params.lon + "?z=" + params.zoom; + return `geo:${OSM.cropLocation(latLng, zoom).join(",")}?z=${zoom}`; }, addObject: function (object, callback) { @@ -357,7 +264,7 @@ L.OSM.Map = L.Map.extend({ this.removeObject(); - if (object.type === "note") { + if (object.type === "note" || object.type === "changeset") { this._objectLoader = { abort: function () {} }; @@ -365,23 +272,33 @@ L.OSM.Map = L.Map.extend({ this._object = object; this._objectLayer = L.featureGroup().addTo(this); - L.circleMarker(object.latLng, haloStyle).addTo(this._objectLayer); + if (object.type === "note") { + L.circleMarker(object.latLng, haloStyle).addTo(this._objectLayer); - if (object.icon) { - L.marker(object.latLng, { - icon: object.icon, - opacity: 1, - interactive: true - }).addTo(this._objectLayer); + if (object.icon) { + L.marker(object.latLng, { + icon: object.icon, + opacity: 1, + interactive: true + }).addTo(this._objectLayer); + } + } else if (object.type === "changeset") { + if (object.bbox) { + L.rectangle([ + [object.bbox.minlat, object.bbox.minlon], + [object.bbox.maxlat, object.bbox.maxlon] + ], changesetStyle).addTo(this._objectLayer); + } } if (callback) callback(this._objectLayer.getBounds()); - } else { // element or changeset handled by L.OSM.DataLayer + this.fire("overlayadd", { layer: this._objectLayer }); + } else { // element handled by L.OSM.DataLayer var map = this; this._objectLoader = $.ajax({ url: OSM.apiUrl(object), - dataType: "xml", - success: function (xml) { + dataType: "json", + success: function (data) { map._object = object; map._objectLayer = new L.OSM.DataLayer(null, { @@ -393,22 +310,16 @@ L.OSM.Map = L.Map.extend({ } }); - map._objectLayer.interestingNode = function (node, ways, relations) { - if (object.type === "node") { - return true; - } else if (object.type === "relation") { - for (var i = 0; i < relations.length; i++) { - if (relations[i].members.indexOf(node) !== -1) return true; - } - } else { - return false; - } + map._objectLayer.interestingNode = function (node, wayNodes, relationNodes) { + return object.type === "node" || + (object.type === "relation" && Boolean(relationNodes[node.id])); }; - map._objectLayer.addData(xml); + map._objectLayer.addData(data); map._objectLayer.addTo(map); if (callback) callback(map._objectLayer.getBounds()); + map.fire("overlayadd", { layer: map._objectLayer }); } }); } @@ -418,6 +329,7 @@ L.OSM.Map = L.Map.extend({ this._object = null; if (this._objectLoader) this._objectLoader.abort(); if (this._objectLayer) this.removeLayer(this._objectLayer); + this.fire("overlayremove", { layer: this._objectLayer }); }, getState: function () { @@ -469,6 +381,14 @@ L.extend(L.Icon.Default.prototype, { } }); +OSM.isDarkMap = function () { + var mapTheme = $("body").attr("data-map-theme"); + if (mapTheme) return mapTheme === "dark"; + var siteTheme = $("html").attr("data-bs-theme"); + if (siteTheme) return siteTheme === "dark"; + return window.matchMedia("(prefers-color-scheme: dark)").matches; +}; + OSM.getUserIcon = function (url) { return L.icon({ iconUrl: url || OSM.MARKER_RED,