geoportal-extensions-leaflet
Version:
French Geoportal Extension for Leaflet
882 lines (770 loc) • 31 kB
JavaScript
import L from "leaflet";
import Logger from "../../Common/Utils/LoggerByDefault";
import ID from "../../Common/Utils/SelectorID";
import LayerSwitcherDOM from "../../Common/Controls/LayerSwitcherDOM";
var logger = Logger.getLogger("layerswitcher");
/**
* @classdesc
*
* Leaflet Control Class to manage map layers : their order, visibility and opacity, and display their informations (title, description, legends, metadata...)
*
* Use {@link module:Controls.LayerSwitcher L.geoportalControl.LayerSwitcher()} factory to create instances of that class.
*
* **Extends** Leaflet <a href="http://leafletjs.com/reference.html#control-layers" target="_blank">L.Control.Layers</a> native class.
*
* @namespace
* @alias L.geoportalControl.LayerSwitcher
*/
var LayerSwitcher = L.Control.Layers.extend(/** @lends L.geoportalControl.LayerSwitcher.prototype */ {
includes : LayerSwitcherDOM,
/**
* options by default
* (extend to L.Control.Layers)
*
* @private
*/
options : {
collapsed : true,
position : "topright",
autoZIndex : true,
layers : []
},
// ################################################################### //
// ##################### Methodes surchargées ######################## //
// ################################################################### //
/**
* @constructor LayerSwitcher
*
* @private
* @alias LayerSwitcher
* @extends {L.Control}
* @param {Object} options - options of component
* @param {String} [options.position] - position of component into the map, 'topleft' by default
* @param {Boolean} [options.collapsed = true] - collapse mode, false by default
* @param {Array} [options.layers] - list of layers to be configured. Each array element is an object, with following properties :
* @param {Object} [options.layers.layer] - layer object
* @param {Boolean} [options.layers.display] - display layer in widget layer list
* @param {String} [options.layers.config.visibility] - layer visibility on map
* @param {String} [options.layers.config.title] - layer alias, to be displayed in widget layer list. E.g. : "Cartes IGN"
* @param {String} [options.layers.config.description] - layer description, to be displayed on title hover, or in layer information panel.
* @param {String} [options.layers.config.quicklookUrl] - link to a quick look image for this layer.
* @param {Array} [options.layers.config.legends] - array of layer legends. Each array element is an object, with following properties :
* - url (String, mandatory) : link to a legend
* - minScaleDenominator (Number, optional) : min scale denominator for legend validity.
* @param {Array} [options.layers.config.metadata] - array of layer metadata. Each array element is an object, with property url (String, mandatory) : link to a metadata
*
* @example
* layers = [
* {
* layer : wms1,
* display : false,
* config : {
* title : "test layer name 1",
* description : "test layer desc 1",
* }
* }
* ]
* options = {
* position : "topright",
* collapsed : true,
* layers : layers
* }
*
* var layerSwitcher = L.geoportalControl.LayerSwitcher(options);
*/
initialize : function (options) {
L.Util.setOptions(this, options);
// uuid
this._uid = ID.generate();
// il faut recuperer tous les layers de la carte (cf. onAdd).
// si une configuration de layers est renseignée, on exploite cette
// information pour les layers renseignés.
// Par contre, pour ceux qui n'ont pas de configuration, on exploite
// les informations issues de la configuration (geoportal-configuration) pour les layers IGN,
// et, pour les layers non IGN, on gère avec l'ID pours les valeurs
// de titre ou description ...
// a ton une configuration des layers ?
this._hasLayersConfig = !(!this.options.layers || Object.keys(this.options.layers).length === 0);
// configuration des layers
this._layersConfig = (this._hasLayersConfig) ? this.options.layers : [];
// liste des layers (c'est l'interface avec le dom !)
this._layers = {};
// indice : ordre des layers sur la carte
// plus c'est haut, plus c'est au dessus de la pile
this._lastZIndex = 0;
// si on a une configuration de layers, on l'exploite tout de suite...
if (this._hasLayersConfig) {
for (var i = 0; i < this._layersConfig.length; i++) {
var obj = this._layersConfig[i];
// signature de la fonction pour une compatibilité avec leaflet...
this._addLayer(obj.layer, null, true);
}
}
},
/**
* Method 'onAdd'
* (extend to L.Control.Layers)
* Method to add the control on the map.
*
* @param {Object} map - L.Map
* @returns {HTMLElement} container
*
* @private
*/
onAdd : function (map) {
// on charge tous les layers dans le controle avec une
// configuration automatique des layers pour ceux qui ne sont pas
// renseignés.
var layersMap = map._layers;
// on est dans le cas où nous avons des layers ajoutés à la carte
if (Object.keys(layersMap).length !== 0) {
// pour gerer l'ordre d'affichage des layers sur la map
// ainsi que dans le controle, on veut le fonctionnement suivant :
// layers ID : [21 , 23 , 25 , 27]
// layers Map : [21:1, 23:2, 25:3, 27:4]
// mais l'ordre dans le controle doit être inversé
// layers Ctrl : [27, 25, 23, 21], et ceci sera realisé lors de
// la creation du DOM pour chaque layer (cf. _update())
var layersKeys = Object.keys(layersMap); // trie
this._lastZIndex = 0;
for (var i = 0; i < layersKeys.length; i++) {
var layerId = layersKeys[i];
// gestion des ordres d'affichage des layers
if (this.options.autoZIndex && layersMap[layerId].setZIndex) {
this._lastZIndex++;
layersMap[layerId].setZIndex(this._lastZIndex);
}
// y'a t il une configuration des layers ?
// si oui, le layer renseigné a déjà été pris en compte dans
// le constructeur..., on passe à la suite...
if (this._hasLayersConfig) {
if (this._layers[layerId]) {
continue;
}
}
// sans configuration, on gére en mode auto le layer.
// on utilise cette methode
// this.addOverlay() -> this._addLayer()...
this.addOverlay(layersMap[layerId]);
}
}
// FIXME au cas où les layers n'ont pas été renseignés sur la carte
// (via addTo ou options.layers), on decide de prendre ceux qui
// sont renseignés dans la configuration ?
if (Object.keys(layersMap).length === 0) {
var config = this._layersConfig;
this._lastZIndex = 0;
for (var j = 0; j < config.length; j++) {
var layer = config[j].layer;
if (!map.hasLayer(layer)) {
// on ajoute le layer à la carte
map.addLayer(layer);
// on met en place la structure
this.addOverlay(layer, null);
// on gère l'ordres d'affichage des layers
if (this.options.autoZIndex && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
}
}
}
// mise à jour des visibilités (au niveau du DOM, oeil coché ou non...)
for (var k in this._layers) {
if (this._layers.hasOwnProperty(k)) {
var obj = this._layers[k];
var _layer = obj.layer;
var _visibility = obj.visibility;
// par defaut, la visibilité de la couche est active, donc avec un oeil non coché !
if (!_visibility) {
// on met à jour la liste des layers à afficher !
this._updateVisibilityLayer(_layer);
}
}
}
// le constructeur retourne this._container !
// on appelle la methode hérité car elle va realisée le boulot :
// - _initLayout
// - _update
// - evenements sur la carte : layeradd + layerremove
// this._container = L.Control.Layers.prototype.onAdd.call(this, map);
this._initLayout();
this._update();
map.on("layeradd", this._onLayerChange, this);
map.on("layerremove", this._onLayerChange, this);
// expiremental !
map.eachLayer(function (layer) {
// ecouteur sur la visibilité des attributions d'un layer IGN
layer.on("visibilitychange", function () {
logger.trace("visibilitychange", layer);
},
this);
// ecouteur sur la liste des attributions d'un layer IGN
layer.on("attributionchange", function () {
logger.trace("attributionchange", layer);
},
this);
});
return this._container;
},
/**
* this method is called when the control is removed from the map
* and removes events on map.
* @param {Object} map - the map
*
* @private
*/
onRemove : function (map) {
map.off("layeradd", this._onLayerChange, this);
map.off("layerremove", this._onLayerChange, this);
},
/**
* Method '_addLayer'
* (overwritten : L.Control.Layers)
* Methode creation of a useful structure for the interface with the DOM
* Method private call by this.initialize() or this.addOverlay()
*
* @example
* {
* layer : objectlayer,
* id : id,
* title : title,
* description : description
* (...)
* };
* @param {Object} layer - object type 'L.TileLayer'
* @param {String} name - layer name or title
* @param {Boolean} overlay - overlay or not
*
* @private
*/
_addLayer : function (layer, name, overlay) {
// id du layer (IGN ou non)
var id = (layer._geoportal_id) ? layer._geoportal_id : layer._leaflet_id;
// pas d'ID !?
// le layer n'est pas chargé dans la carte...
if (typeof id === "undefined") {
return;
}
// recherche de la config pour un layer donné
var layerConfig = {};
for (var i in this._layersConfig) {
if (this._layersConfig.hasOwnProperty(i)) {
if (id === L.stamp(this._layersConfig[i].layer)) {
layerConfig = this._layersConfig[i].config;
// display
// ce layer n'est pas pris en compte dans le controle
// mais il peut être affiché dans la map
// si au préalable, le client l'a ajouté...
var display = (typeof this._layersConfig[i].display !== "undefined") ? this._layersConfig[i].display : true;
if (!display) {
return;
}
break;
}
}
}
// construit un objet simplifié pour le dom,
// par defaut, on prend en compte les layers de type IGN
// (info de la configuration).
this._layers[id] = {
layer : layer,
id : id,
overlay : overlay, // not use !
title : (layer._geoportal_id && layer._title) ? layer._title : (name) || id,
description : (layer._geoportal_id && layer._description) ? layer._description : (name) || id,
visibility : true, // par defaut, sauf si surcharge via la config...
legends : (layer._geoportal_id) ? layer._legends : null,
metadata : (layer._geoportal_id) ? layer._metadata : null,
quicklookUrl : (layer._geoportal_id) ? layer._quicklookUrl : null
};
// surcharge la config ci dessus avec les options de configuration saisies
if (layerConfig && Object.keys(layerConfig).length) {
L.Util.extend(this._layers[id], layerConfig);
}
// mise à jour de la visibilité
var _visibility = this._layers[id].visibility;
if (layer._geoportal_id) {
// mise à jour de la visibilité des attributions pour un layer IGN
layer.setVisible(_visibility);
}
if (!_visibility) {
// on met à jour la liste des layers à afficher !
this._updateVisibilityLayer(layer);
}
},
/**
* Method 'addTo'
* (overwritten : L.Control.Layers because of exception with _expandIfNotCollapsed())
*
* @param {Object} map - the map
*
* @returns {Object} this
*/
addTo : function (map) {
L.Control.prototype.addTo.call(this, map);
return this;
},
/**
* Creation of layers of container
* (extend to L.Control.Layers)
*
* Method private call by this.onAdd()
*
* @private
*/
_initLayout : function () {
// fonctionnement lors de l'initialisation :
// onAdd -> this._update -> this._addItem (on boucle sur layers)
// onAdd -> this._initLayout
// creation du container principal
var container = this._container = this._createMainContainerElement();
// ajout dans le container principal d'affichage des layers
var input = this._createMainLayersShowElement();
container.appendChild(input);
// gestion du mode "collapsed"
if (!this.options.collapsed) {
input.checked = true;
}
// ajout dans le container principal de la liste des layers
var divL = this._overlaysList = this._createMainLayersElement();
container.appendChild(divL);
// ajout dans le container principal du picto du controle
var picto = this._createMainPictoElement();
container.appendChild(picto);
// ajout dans le container principal du panneau d'information
var divI = this._createMainInfoElement();
container.appendChild(divI);
// creation du mode draggable
this._createDraggableElement(this._overlaysList, this);
// desactivation des evenements qui peuvent interférer avec la carte
L.DomEvent
.disableClickPropagation(container)
.disableScrollPropagation(container);
// gestion des evenements en interaction avec la carte
this._map.on("moveend", this._onOutOfRangeLayerZoom, this);
// gestion des CSS en fonction du placement du controle
switch (this.getPosition()) {
case "topright":
container.style.position = "relative";
container.style.top = "0";
container.style.right = "0";
break;
case "topleft":
container.style.position = "relative";
container.style.top = "0";
container.style.right = "initial";
picto.style.float = "left";
divL.style.borderBottomRightRadius = "5px";
divL.style.borderBottomLeftRadius = "0";
divI.style.right = "initial";
divI.style.left = "190px";
break;
case "bottomleft":
container.style.position = "relative";
container.style.top = "0";
container.style.right = "initial";
picto.style.float = "left";
divL.style.borderBottomRightRadius = "5px";
divL.style.borderBottomLeftRadius = "0";
divI.style.right = "initial";
divI.style.left = "190px";
// divI.style.top = "initial";
// divI.style.bottom = "190px";
break;
case "bottomright":
container.style.position = "relative";
container.style.top = "0";
container.style.right = "0";
// divI.style.top = "initial";
// divI.style.bottom = "190px";
break;
default :
container.style.position = "relative";
container.style.top = "0";
container.style.right = "0";
}
},
/**
* Update the construction of DOM for each layer
* (extend to L.Control.Layers)
*
* Method private call by this.onAdd()
*
* @private
*/
_update : function () {
if (!this._container) {
return;
}
this._overlaysList.innerHTML = "";
var layersId = [];
for (var i in this._layers) {
if (this._layers.hasOwnProperty(i)) {
layersId.push(i);
}
}
// inversion du sens des layers dans le controle
// car on veut le même ordre que sur la map, et comme je suis un peu
// parano, je re-trie la liste...
var layers = layersId.sort(function (a, b) {
var ia = parseInt(a, 10);
var ib = parseInt(b, 10);
return ia - ib;
}).reverse();
for (var j = 0; j < layers.length; j++) {
var id = layers[j];
var obj = this._layers[id];
this._addItem(obj);
}
},
/**
* Construction of the DOM for each layer
* (extend to L.Control.Layers)
*
* Method private call by this._update()
*
* @private
* @param {Object} obj - layer
* @returns {HTMLElement} container
*/
_addItem : function (obj) {
logger.log("_addItem", obj);
obj.opacity = obj.layer.options.opacity; // ajout de cette option !
var container = this._createContainerLayerElement(obj);
// gestion outOfRange
(obj.layer.options.minZoom > this._map.getZoom() || obj.layer.options.maxZoom < this._map.getZoom())
? L.DomUtil.addClass(container, "outOfRange")
: L.DomUtil.removeClass(container, "outOfRange");
// ajout du container dans la liste des layers (de type overlay uniquement !)
this._overlaysList.appendChild(container);
return container;
},
/**
* Event onLayer change
* (extend to L.Control.Layers because of version 1.0.0)
*
* @private
* @param {Event} e - event
*/
_onLayerChange : function (e) {
var obj = this._layers[L.stamp(e.layer)];
if (!obj) {
return;
}
if (!this._handlingClick) {
this._update();
}
this._map.fire((e.type === "layeradd") ? "overlayadd" : "overlayremove", obj);
},
/**
* Event onLayer remove
* (extend to L.Control.Layers because of version 1.0.0)
*
* @private
* @param {Object} layer - event
* @returns {Object} layer
*/
removeLayer : function (layer) {
// clean DOM !
var id = L.stamp(layer);
delete this._layers[id];
this._update();
// clean Layers
var map = this._map;
if (map) {
if (map.hasLayer(layer)) {
map.removeLayer(layer);
}
}
return this;
},
// ################################################################### //
// ################ Methodes de l'instance (privées) ################# //
// ################################################################### //
/**
* Set visibility of attribution layer
* (call by this._onVisibilityLayerClick())
*
* @private
* @param {Object} layer - layer
*/
_updateVisibilityLayer : function (layer) {
if (!this._map) {
return;
}
this._handlingClick = true;
var visibility = this._layers[L.stamp(layer)].visibility;
if (visibility && !this._map.hasLayer(layer)) {
// input non checked dans le DOM, on ouvre l'oeil
// et on ajoute la couche !
this._map.addLayer(layer);
} else if (!visibility && this._map.hasLayer(layer)) {
// input checked dans le DOM, on ferme l'oeil
// et on supprime la couche !
this._map.removeLayer(layer);
} else {
logger.log("Status unknown layer !?");
}
this._handlingClick = false;
this._refocusOnMap();
},
/**
* Set visibility of layer (DOM)
* (call by this.setVisibility())
*
* @private
* @param {Object} layer - layer
*/
_updateVisibilityDOMLayer : function (layer) {
var layerIdx = L.stamp(layer);
var visibilityElement = L.DomUtil.get(this._addUID("GPvisibility_ID_" + layerIdx)); // FIXME ID !
var visibilityValue = this._layers[layerIdx].visibility;
visibilityElement.checked = visibilityValue;
},
/**
* Set opacity of layer (DOM)
* (call by this.setOpacity())
*
* @private
* @param {Object} layer - layer
*/
_updateOpacityDOMLayer : function (layer) {
var layerIdx = L.stamp(layer);
var opacityValue = layer.options.opacity;
var opacityElement = L.DomUtil.get(this._addUID("GPopacityValue_ID_" + layerIdx)); // FIXME ID !
opacityElement.innerHTML = parseInt(opacityValue * 100, 10) + "%";
opacityElement.value = parseInt(opacityValue * 100, 10);
},
// ################################################################### //
// ################## GESTIONNAIRES d'evenements ##################### //
// ################################################################### //
/**
* Event 'zoom' on layers visibility
*
* FIXME contrainte sur l'emprise du layer ?
*
* @private
*/
_onOutOfRangeLayerZoom : function () {
var map = this._map;
var layers = this._layers;
for (var i in layers) {
if (layers.hasOwnProperty(i)) {
var layer = layers[i].layer;
var id = layers[i].id;
var div = L.DomUtil.get(this._addUID("GPlayerSwitcher_ID_" + id)); // FIXME ID !
if (layer.options.minZoom > map.getZoom() || layer.options.maxZoom < map.getZoom()) {
L.DomUtil.addClass(div, "outOfRange");
} else {
L.DomUtil.removeClass(div, "outOfRange");
}
}
}
},
/**
* Event 'click' on layer visibility
*
* @private
* @param {Event} e - MouseEvent
*/
_onVisibilityLayerClick : function (e) {
var visibilityElement = e.target.id; // ex GPvisibilityPicto_ID_26
var visibilityOrder = ID.index(visibilityElement); // ex. 26
// on met à jour cette interface...
this._layers[visibilityOrder].visibility = L.DomUtil.get(visibilityElement).checked;
var layer = this._layers[visibilityOrder].layer;
this._updateVisibilityLayer(layer);
},
/**
* Event 'click' on layer deleted
*
* @private
* @param {Event} e - MouseEvent
*/
_onDropLayerClick : function (e) {
var layerElement = e.target.id; // ex GPvisibilityPicto_ID_26
var layerOrder = ID.index(layerElement); // ex. 26
var layer = this._layers[layerOrder].layer;
this.removeLayer(layer);
},
/**
* Event 'onchange' on layer opacity
*
* FIXME appel en dur d'un identifiant CSS !
*
* @private
* @param {Event} e - ChangeEvent
*/
_onChangeLayerOpacity : function (e) {
var layerElement = e.target.id; // ex GPvisibilityPicto_ID_26
var layerOrder = ID.index(layerElement); // ex. 26
var layer = this._layers[layerOrder].layer;
var opacityValue = e.target.value;
var opacityId = L.DomUtil.get(this._addUID("GPopacityValue_ID_" + layerOrder)); // FIXME ID !
opacityId.innerHTML = opacityValue + "%";
if (this._map.hasLayer(layer)) {
if (typeof layer.setOpacity !== "undefined") {
layer.setOpacity(opacityValue / 100);
} else {
// Particularité du format GeoJSON
layer.setStyle({
fillOpacity : opacityValue / 100,
opacity : opacityValue / 100
});
}
}
},
/**
* Event 'click' on opening the information window
*
* FIXME appel en dur d'un identifiant CSS !
*
* @private
* @param {Event} e - MouseEvent
*/
_onOpenLayerInfoClick : function (e) {
var layerElement = e.target.id; // ex GPvisibilityPicto_ID_26
var layerOrder = ID.index(layerElement); // ex. 26
var layer = this._layers[layerOrder];
// Close layer info panel
var divId = L.DomUtil.get(e.target.id);
var panel = null;
var info = null;
if (divId.className === "GPlayerInfoOpened") {
L.DomUtil.removeClass(divId, "GPlayerInfoOpened");
L.DomUtil.addClass(divId, "GPlayerInfo");
panel = L.DomUtil.get(this._addUID("GPlayerInfoPanel"));
L.DomUtil.removeClass(panel, "GPpanel");
L.DomUtil.removeClass(panel, "GPlayerInfoPanelOpened");
L.DomUtil.addClass(panel, "GPlayerInfoPanelClosed");
info = L.DomUtil.get(this._addUID("GPlayerInfoContent"));
panel.removeChild(info);
return;
}
var layers = document.getElementsByClassName("GPlayerInfoOpened");
for (var i = 0; i < layers.length; i++) {
layers[i].className = "GPlayerInfo";
}
// Open layer info panel
L.DomUtil.removeClass(divId, "GPlayerInfo");
L.DomUtil.addClass(divId, "GPlayerInfoOpened");
panel = L.DomUtil.get(this._addUID("GPlayerInfoPanel"));
L.DomUtil.addClass(panel, "GPpanel");
L.DomUtil.removeClass(panel, "GPlayerInfoPanelClosed");
L.DomUtil.addClass(panel, "GPlayerInfoPanelOpened");
info = L.DomUtil.get(this._addUID("GPlayerInfoContent"));
if (info) {
panel.removeChild(info);
}
// on récupére les infos associées au layer pour mettre à jour
// dynamiquement le contenu du panel d"infos
var infoLayer = this._createContainerLayerInfoElement(layer);
panel.appendChild(infoLayer);
},
/**
* Event "drag & drop" on move layer
*
* FIXME appel en dur d'un identifiant CSS !
*
* @private
* @param {Event} e - MouseEvent
*/
_onDragAndDropLayerClick : function (e) {
var layerElement = e.target.id; // ex GPvisibilityPicto_ID_26
var layerOrder = ID.index(layerElement); // ex. 26
var layer = this._layers[layerOrder];
logger.log(layer);
var matchesLayers = document.querySelectorAll("div.GPlayerSwitcher_layer");
this._lastZIndex = matchesLayers.length;
for (var i = 0; i < matchesLayers.length; i++) {
var tag = matchesLayers[i].id;
var order = ID.index(tag);
var _layer = this._layers[order].layer;
if (this.options.autoZIndex && _layer.setZIndex) {
this._lastZIndex--;
_layer.setZIndex(this._lastZIndex);
}
}
},
// ################################################################### //
// ###### METHODES PUBLIQUES (INTERFACE AVEC LE CONTROLE) ############ //
// ################################################################### //
/**
* Adding layer configuration to be displayed by the control
*
* @param {Object} layer - layer to add to layer switcher
* @param {Object} config - See {@link module:Controls.LayerSwitcher L.geoportalControl.LayerSwitcher()} for layer display config object definition.
*/
addLayer : function (layer, config) {
var map = this._map;
var cfg = this._layersConfig;
if (!layer) {
logger.log("[ERROR] LayerSwitcher:addLayer - missing layer parameter !");
return;
}
if (!map.hasLayer(layer)) {
logger.log("[WARN] LayerSwitcher:addLayer - layer has not been added on map !");
map.addLayer(layer);
}
var id = L.stamp(layer);
for (var i in cfg) {
if (cfg.hasOwnProperty(i)) {
// layer already added !
if (id === L.stamp(cfg[i].layer)) {
delete cfg[i];
break;
}
}
}
var _config = config || {};
L.Util.extend(_config, {
layer : layer
});
cfg.push(_config);
// layer déjà configuré, il reprend sa place !
if (!this._layers[id]) {
layer.setZIndex(this._lastZIndex++);
}
this.addOverlay(layer);
this._update();
},
/**
* Set the opacity of a layer, and opacity must be a number from 0 to 1.
*
* @param {Object} layer - layer into layerswitcher
* @param {Number} opacity - 0-1.
*/
setOpacity : function (layer, opacity) {
logger.trace(layer, opacity);
if (opacity > 1 || opacity < 0) {
return;
}
if (this._map.hasLayer(layer)) {
if (typeof layer.setOpacity !== "undefined") {
layer.setOpacity(opacity);
} else {
// Particularité du format GeoJSON pour l'opacité
layer.options.opacity = opacity;
layer.setStyle({
fillOpacity : opacity,
opacity : opacity
});
}
this._updateOpacityDOMLayer(layer);
}
},
/**
* Set the visibility of a layer.
*
* @param {Object} layer - layer into layerswitcher
* @param {Object} visibility - true/false.
*/
setVisibility : function (layer, visibility) {
logger.trace(layer, visibility);
this._layers[L.stamp(layer)].visibility = visibility;
this._updateVisibilityDOMLayer(layer);
this._updateVisibilityLayer(layer);
}
});
export default LayerSwitcher;