Search code examples
zoomingleafletlayerswitching

Leaflet 0.7.7 Zoombased layer switching separated by theme


We are working on a school project where the aim is to create a map in which based on the amount of zoom the layer switches from one aggregate level to a smaller aggregate level. Additionally, we have several groups of layers based on a theme for which this needs to apply. So you'd click on a theme and a new group of layers that switches based on zoom level become active and when you click another theme another group of layers become active and switch based on zoom level. This means that the themes are exclusionary, ideally you can't have more than one theme active at a time.

We tried to make this work in several ways already but without much success. Using the L.Control.Layers we were unable to group different layers together under one radio button and have them switch based on zoom since the layer control build into leaflet always splits them up into separate ones. Even using L.layerGroup to combine several layer variables or creating several layers into one variable and then adding them to the map using l.control.layer.

We also tried to use L.easyButton (https://github.com/CliffCloud/Leaflet.EasyButton). This allowed us to put the variables under one button and add a zoom based layer switching inside of it. However, the issue here is that we are unable to deactivate the functionality once activated. Which results in several of them being active at one point and overlapping each other.

If possible we would like to know if we should use a different approach or if either the leaflet control function or the use of easyButton could work and how?

This is example code for one of the buttons, which would appear several times but show a different theme:

        L.easyButton( '<span class="star">&starf;</span>', function (polygon) {

                    var ejerlav_polygon = new L.tileLayer.betterWms(
                    'http://[IP]:[PORT]/geoserver/prlayer/wms', {
                    layers: 'prlayer:ejerlav',
                    transparent: true,
                    styles: 'polygon',          
                    format: 'image/png'});

                    var municipality_polygon = new L.tileLayer.betterWms(
                    'http://[IP]:[PORT]/geoserver/prlayer/wms', {
                    layers: 'prlayer:municipality',
                    transparent: true,
                    styles: 'polygon',
                    format: 'image/png'});                      

                    map.on("zoomend", function() {
                        if (map.getZoom() <= 10 && map.getZoom() >= 2) {
                            map.addLayer(municipality_polygon);
                        } else if (map.getZoom() > 10 || map.getZoom() < 2) {
                            map.removeLayer(municipality_polygon);
                        }
                        });

                            map.on("zoomend", function() {
                        if (map.getZoom() <= 11 && map.getZoom() >= 11) {
                            map.addLayer(ejerlav_polygon);

                        } else if (map.getZoom() > 11 || map.getZoom() < 11) {
                            map.removeLayer(ejerlav_polygon);
                        }
                        });

        }).addTo(map);

Solution

  • If my understanding is correct, you would like to give the user the ability to switch between "themes" (some sort of group of layers that switch themselves based on the map current zoom level), possibly using Leaflet Layers Control?

    And regarding the switch based on map zoom, you cannot just change the Tile Layer template URL because you use some WMS?

    As for the latter functionality (switching layers within a group / theme based on map zoom), a "simple" solution would be to create your own type of layer that will listen to map "zoomend" event and change the Tile Layer WMS accordingly.

    L.LayerSwitchByZoom = L.Class.extend({
      initialize: function (layersArray) {
        var self = this;
    
        this._layersByZoom = layersArray;
        this._maxZoom = layersArray.length - 1;
    
        this._switchByZoomReferenced = function () {
          self._switchByZoom();
        };
      },
    
      onAdd: function (map) {
        this._map = map;
    
        map.on("zoomend", this._switchByZoomReferenced);
        this._switchByZoom();
      },
    
      onRemove: function (map) {
        map.off("zoomend", this._switchByZoomReferenced);
        this._removeCurrentLayer();
    
        this._map = null;
      },
    
      addTo: function (map) {
        map.addLayer(this);
        return this;
      },
    
      _switchByZoom: function () {
        var map = this._map,
            z = Math.min(map.getZoom(), this._maxZoom);
    
        this._removeCurrentLayer();
        this._currentLayer = this._layersByZoom[z];
        map.addLayer(this._currentLayer);
      },
    
      _removeCurrentLayer: function () {
        if (this._currentLayer) {
          map.removeLayer(this._currentLayer);
        }
      }
    });
    

    You would then instantiate that layer "theme" / group by specifying an array of layers (your Tile Layers WMS), where the array index corresponds to the zoom level at which that Tile Layer should appear.

    var myLayerSwitchByZoomA = new L.LayerSwitchByZoom([
      osmMapnik, // zoom 0, osmMapnik is a Tile Layer or any other layer
      osmDE, // zoom 1
      osmFR, // zoom 2
      osmHOT // zoom 3, etc.
    ]);
    

    Once this new layer type is set, you can use it in the Layers Control like any other type of Layer / Tile Layer, etc.

    L.control.layers({
      "OpenStreetMap": myLayerSwitchByZoomA,
      "ThunderForest": myLayerSwitchByZoomB
    }).addTo(map);
    

    Demo: http://jsfiddle.net/ve2huzxw/85/

    Note that you could further improve the implementation of L.LayerSwitchByZoom to avoid flickering when changing the layer after zoom end, etc.