diff --git a/frappe/public/js/frappe/form/controls/geolocation.js b/frappe/public/js/frappe/form/controls/geolocation.js index 827a9be315..045863ce42 100644 --- a/frappe/public/js/frappe/form/controls/geolocation.js +++ b/frappe/public/js/frappe/form/controls/geolocation.js @@ -47,6 +47,7 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f if (!this.map) { this.customize_draw_controls(); this.bind_leaflet_map(); + this.bind_leaflet_layers_control(); } if (this.disabled) { this.map.dragging.disable(); @@ -156,13 +157,45 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f this.map = L.map(this.map_id); this.map.setView(frappe.utils.map_defaults.center, frappe.utils.map_defaults.zoom); - L.tileLayer(frappe.utils.map_defaults.tiles, frappe.utils.map_defaults.options).addTo( - this.map + this.streetLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.default_tile.url, + frappe.utils.map_defaults.tiles.default_tile.options + ); + this.satelliteLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.satellite_tile.url, + frappe.utils.map_defaults.tiles.satellite_tile.options + ); + this.labelsLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.labels_tail.url, + frappe.utils.map_defaults.tiles.labels_tail.options + ); + this.terrainLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.terrain_lines_tail.url, + frappe.utils.map_defaults.tiles.terrain_lines_tail.options ); + this.streetLayer.addTo(this.map); + this.editableLayers = new L.FeatureGroup(); } + bind_leaflet_layers_control() { + // Add layers control for switching between map types + // Define base and overlay layers as properties of the class instance for access in other methods + + const baseLayers = { + Default: this.streetLayer, + Satellite: this.satelliteLayer, + }; + const overlays = { + Labels: this.labelsLayer, + Terrain: this.terrainLayer, + }; + + L.control.layers(baseLayers, overlays).addTo(this.map); + this.display_leaflet_overlays_control("none"); + } + bind_leaflet_locate_control() { // To request location update and set location, sets current geolocation on load this.locate_control = L.control.locate({ position: "topright" }); @@ -214,6 +247,17 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f }); } + display_leaflet_overlays_control(display = "") { + const layerControlContainer = document.querySelector(".leaflet-control-layers-overlays"); + const separator = document.querySelector(".leaflet-control-layers-separator"); + if (layerControlContainer) { + layerControlContainer.style.display = display; + } + if (separator) { + separator.style.display = display; + } + } + bind_leaflet_event_listeners() { this.bound_event_listeners = true; this.map.on("draw:created", (e) => { @@ -231,6 +275,27 @@ frappe.ui.form.ControlGeolocation = class ControlGeolocation extends frappe.ui.f this.editableLayers.removeLayer(layer); this.set_value(JSON.stringify(this.editableLayers.toGeoJSON())); }); + + // Remove overlays and overlays options when selecting the default view + this.map.on("baselayerchange", (e) => { + if (e.name === "Satellite") { + // Show overlays options and separator only in Satellite view + this.display_leaflet_overlays_control(); + } else { + // Hide overlays options and separator in other views + this.display_leaflet_overlays_control("none"); + // Remove all overlays + Object.values(this.map._layers).forEach((layer) => { + if ( + layer instanceof L.TileLayer && + (layer._url === frappe.utils.map_defaults.tiles.labels_tail.url || + layer._url === frappe.utils.map_defaults.tiles.terrain_lines_tail.url) + ) { + this.map.removeLayer(layer); + } + }); + } + }); } add_non_group_layers(source_layer, target_group) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 4c0787bdff..5a5b75bdeb 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1203,10 +1203,34 @@ Object.assign(frappe.utils, { map_defaults: { center: [19.08, 72.8961], zoom: 13, - tiles: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", - options: { - attribution: - '© OpenStreetMap contributors', + tiles: { + default_tile: { + url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + options: { + attribution: + '© OpenStreetMap contributors', + }, + }, + satellite_tile: { + url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", + options: { + attribution: "© Esri © OpenStreetMap Contributors", + }, + }, + labels_tail: { + url: "https://tiles.stadiamaps.com/tiles/stamen_toner_labels/{z}/{x}/{y}{r}.png", + options: { + attribution: + '© Stadia Maps © Stamen Design © OpenMapTiles', + }, + }, + terrain_lines_tail: { + url: "https://tiles.stadiamaps.com/tiles/stamen_terrain_lines/{z}/{x}/{y}{r}.png", + options: { + attribution: + '© Stadia Maps © Stamen Design © OpenMapTiles', + }, + }, }, image_path: "/assets/frappe/images/leaflet/", }, diff --git a/frappe/public/js/frappe/views/map/map_view.js b/frappe/public/js/frappe/views/map/map_view.js index 4e9be64794..4ecc71796f 100644 --- a/frappe/public/js/frappe/views/map/map_view.js +++ b/frappe/public/js/frappe/views/map/map_view.js @@ -43,12 +43,31 @@ frappe.views.MapView = class MapView extends frappe.views.ListView { frappe.utils.map_defaults.zoom ); - L.tileLayer(frappe.utils.map_defaults.tiles, frappe.utils.map_defaults.options).addTo( - this.map + this.streetLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.default_tile.url, + frappe.utils.map_defaults.tiles.default_tile.options + ); + this.satelliteLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.satellite_tile.url, + frappe.utils.map_defaults.tiles.satellite_tile.options + ); + this.labelsLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.labels_tail.url, + frappe.utils.map_defaults.tiles.labels_tail.options + ); + this.terrainLayer = L.tileLayer( + frappe.utils.map_defaults.tiles.terrain_lines_tail.url, + frappe.utils.map_defaults.tiles.terrain_lines_tail.options ); + this.streetLayer.addTo(this.map); + + this.bind_leaflet_layers_control(); this.bind_leaflet_locate_control(); L.control.scale().addTo(this.map); + if (!this.bound_event_listeners) { + this.bind_leaflet_event_listeners(); + } } render() { @@ -140,9 +159,61 @@ frappe.views.MapView = class MapView extends frappe.views.ListView { } } + bind_leaflet_layers_control() { + // Add layers control for switching between map types + // Define base and overlay layers as properties of the class instance for access in other methods + + const baseLayers = { + Default: this.streetLayer, + Satellite: this.satelliteLayer, + }; + const overlays = { + Labels: this.labelsLayer, + Terrain: this.terrainLayer, + }; + + L.control.layers(baseLayers, overlays).addTo(this.map); + this.display_leaflet_overlays_control("none"); + } + bind_leaflet_locate_control() { // To request location update and set location, sets current geolocation on load this.locate_control = L.control.locate({ position: "topright" }); this.locate_control.addTo(this.map); } + + display_leaflet_overlays_control(display = "") { + const layerControlContainer = document.querySelector(".leaflet-control-layers-overlays"); + const separator = document.querySelector(".leaflet-control-layers-separator"); + if (layerControlContainer) { + layerControlContainer.style.display = display; + } + if (separator) { + separator.style.display = display; + } + } + + bind_leaflet_event_listeners() { + this.bound_event_listeners = true; + // Remove overlays and overlays options when selecting the default view + this.map.on("baselayerchange", (e) => { + if (e.name === "Satellite") { + // Show overlays options and separator only in Satellite view + this.display_leaflet_overlays_control(); + } else { + // Hide overlays options and separator in other views + this.display_leaflet_overlays_control("none"); + // Remove all overlays + Object.values(this.map._layers).forEach((layer) => { + if ( + layer instanceof L.TileLayer && + (layer._url === frappe.utils.map_defaults.tiles.labels_tail.url || + layer._url === frappe.utils.map_defaults.tiles.terrain_lines_tail.url) + ) { + this.map.removeLayer(layer); + } + }); + } + }); + } };