diff --git a/src/public/index.html b/src/public/index.html index 63d0fb6..302ed4b 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -27,6 +27,10 @@ + + + + @@ -2025,9 +2029,14 @@ var openStreetMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 22, // increase from 18 to 22 - attribution: '© OpenStreetMap | Data from Meshtastic', + attribution: 'Tiles © OpenStreetMap | Data from Meshtastic', }).addTo(map); + var esriWorldImagery = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { + maxZoom: 21, // esri doesn't have tiles closer than this + attribution: 'Tiles © Esri | Data from Meshtastic' + }); + // create layer groups var nodesLayerGroup = new L.LayerGroup(); var neighboursLayerGroup = new L.LayerGroup(); @@ -2068,16 +2077,9 @@ return div; }; - var baseLayers = { - "Nodes (All)": nodesLayerGroup, - "Nodes (Clustered)": nodesClusteredLayerGroup, - "Nodes (Hide All)": new L.LayerGroup(), - }; - - var overlays = { - "Legend": legendLayerGroup, - "Neighbours": neighboursLayerGroup, - "Waypoints": waypointsLayerGroup, + var tileLayers = { + "Map": openStreetMap, + "Satellite": esriWorldImagery, }; // handle adding/remove legend on map (can't use L.Control as an overlay, so we toggle an empty L.LayerGroup) @@ -2092,7 +2094,21 @@ }); // add layers to control ui - L.control.layers(baseLayers, overlays).addTo(map); + L.control.groupedLayers(tileLayers, { + "Nodes": { + "All": nodesLayerGroup, + "Clustered": nodesClusteredLayerGroup, + "None": new L.LayerGroup(), + }, + "Overlays": { + "Legend": legendLayerGroup, + "Neighbours": neighboursLayerGroup, + "Waypoints": waypointsLayerGroup, + }, + }, { + // make the "Nodes" group exclusive (use radio inputs instead of checkbox) + exclusiveGroups: ["Nodes"], + }).addTo(map); // enable base layers nodesClusteredLayerGroup.addTo(map); diff --git a/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.css b/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.css new file mode 100644 index 0000000..28b169b --- /dev/null +++ b/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.css @@ -0,0 +1,14 @@ +.leaflet-control-layers-group-name { + font-weight: bold; + margin-bottom: .2em; + margin-left: 3px; +} + +.leaflet-control-layers-group { + margin-bottom: .5em; +} + +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + padding-right: 10px; +} diff --git a/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.js b/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.js new file mode 100644 index 0000000..7f0aaa8 --- /dev/null +++ b/src/public/plugins/leaflet.groupedlayercontrol/leaflet.groupedlayercontrol.js @@ -0,0 +1,374 @@ +/* global L */ + +// A layer control which provides for layer groupings. +// Author: Ishmael Smyrnow +L.Control.GroupedLayers = L.Control.extend({ + + options: { + collapsed: true, + position: 'topright', + autoZIndex: true, + exclusiveGroups: [], + groupCheckboxes: false + }, + + initialize: function (baseLayers, groupedOverlays, options) { + var i, j; + L.Util.setOptions(this, options); + + this._layers = []; + this._lastZIndex = 0; + this._handlingClick = false; + this._groupList = []; + this._domGroups = []; + + for (i in baseLayers) { + this._addLayer(baseLayers[i], i); + } + + for (i in groupedOverlays) { + for (j in groupedOverlays[i]) { + this._addLayer(groupedOverlays[i][j], j, i, true); + } + } + }, + + onAdd: function (map) { + this._initLayout(); + this._update(); + + map + .on('layeradd', this._onLayerChange, this) + .on('layerremove', this._onLayerChange, this); + + return this._container; + }, + + onRemove: function (map) { + map + .off('layeradd', this._onLayerChange, this) + .off('layerremove', this._onLayerChange, this); + }, + + addBaseLayer: function (layer, name) { + this._addLayer(layer, name); + this._update(); + return this; + }, + + addOverlay: function (layer, name, group) { + this._addLayer(layer, name, group, true); + this._update(); + return this; + }, + + removeLayer: function (layer) { + var id = L.Util.stamp(layer); + var _layer = this._getLayer(id); + if (_layer) { + delete this._layers[this._layers.indexOf(_layer)]; + } + this._update(); + return this; + }, + + _getLayer: function (id) { + for (var i = 0; i < this._layers.length; i++) { + if (this._layers[i] && L.stamp(this._layers[i].layer) === id) { + return this._layers[i]; + } + } + }, + + _initLayout: function () { + var className = 'leaflet-control-layers', + container = this._container = L.DomUtil.create('div', className); + + // Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released + container.setAttribute('aria-haspopup', true); + + if (L.Browser.touch) { + L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); + } else { + L.DomEvent.disableClickPropagation(container); + L.DomEvent.on(container, 'wheel', L.DomEvent.stopPropagation); + } + + var form = this._form = L.DomUtil.create('form', className + '-list'); + + if (this.options.collapsed) { + if (!L.Browser.android) { + L.DomEvent + .on(container, 'mouseover', this._expand, this) + .on(container, 'mouseout', this._collapse, this); + } + var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container); + link.href = '#'; + link.title = 'Layers'; + + if (L.Browser.touch) { + L.DomEvent + .on(link, 'click', L.DomEvent.stop) + .on(link, 'click', this._expand, this); + } else { + L.DomEvent.on(link, 'focus', this._expand, this); + } + + this._map.on('click', this._collapse, this); + // TODO keyboard accessibility + } else { + this._expand(); + } + + this._baseLayersList = L.DomUtil.create('div', className + '-base', form); + this._separator = L.DomUtil.create('div', className + '-separator', form); + this._overlaysList = L.DomUtil.create('div', className + '-overlays', form); + + container.appendChild(form); + }, + + _addLayer: function (layer, name, group, overlay) { + var id = L.Util.stamp(layer); + + var _layer = { + layer: layer, + name: name, + overlay: overlay + }; + this._layers.push(_layer); + + group = group || ''; + var groupId = this._indexOf(this._groupList, group); + + if (groupId === -1) { + groupId = this._groupList.push(group) - 1; + } + + var exclusive = (this._indexOf(this.options.exclusiveGroups, group) !== -1); + + _layer.group = { + name: group, + id: groupId, + exclusive: exclusive + }; + + if (this.options.autoZIndex && layer.setZIndex) { + this._lastZIndex++; + layer.setZIndex(this._lastZIndex); + } + }, + + _update: function () { + if (!this._container) { + return; + } + + this._baseLayersList.innerHTML = ''; + this._overlaysList.innerHTML = ''; + this._domGroups.length = 0; + + var baseLayersPresent = false, + overlaysPresent = false, + i, obj; + + for (var i = 0; i < this._layers.length; i++) { + obj = this._layers[i]; + this._addItem(obj); + overlaysPresent = overlaysPresent || obj.overlay; + baseLayersPresent = baseLayersPresent || !obj.overlay; + } + + this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none'; + }, + + _onLayerChange: function (e) { + var obj = this._getLayer(L.Util.stamp(e.layer)), + type; + + if (!obj) { + return; + } + + if (!this._handlingClick) { + this._update(); + } + + if (obj.overlay) { + type = e.type === 'layeradd' ? 'overlayadd' : 'overlayremove'; + } else { + type = e.type === 'layeradd' ? 'baselayerchange' : null; + } + + if (type) { + this._map.fire(type, obj); + } + }, + + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) + _createRadioElement: function (name, checked) { + var radioHtml = '