UNPKG

geopf-extensions-openlayers

Version:

French Geoportal Extensions for OpenLayers libraries

1,211 lines (1,107 loc) 78.9 kB
// import CSS import "../../CSS/Controls/LayerSwitcher/GPFlayerSwitcher.css"; // import "../../CSS/Controls/LayerSwitcher/GPFlayerSwitcherStyle.css"; // import OpenLayers // import Control from "ol/control/Control"; import Widget from "../Widget"; import Control from "../Control"; import WMTSSource from "ol/source/WMTS"; import TileWMSSource from "ol/source/TileWMS"; import ImageSource from "ol/source/Image"; import { unByKey as olObservableUnByKey } from "ol/Observable"; import { intersects as olIntersects } from "ol/extent"; import { transformExtent as olTransformExtentProj } from "ol/proj"; import VectorLayer from "ol/layer/Vector"; import TileLayer from "ol/layer/Tile"; import VectorTileLayer from "ol/layer/VectorTile"; import VectorTileSource from "ol/source/VectorTile"; import { applyStyle } from "ol-mapbox-style"; // import local import Utils from "../../Utils/Helper"; import SelectorID from "../../Utils/SelectorID"; import Logger from "../../Utils/LoggerByDefault"; import Config from "../../Utils/Config"; // DOM import LayerSwitcherDOM from "./LayerSwitcherDOM"; var logger = Logger.getLogger("layerswitcher"); /** * @classdesc * OpenLayers Control to manage map layers : their order, visibility and opacity, and display their informations (title, description, legends, metadata...) * * @constructor * @extends {ol.control.Control} * @alias ol.control.LayerSwitcher * @type {ol.control.LayerSwitcher} * @param {Object} options - control options * @param {Array} [options.layers] - list of layers to be configured. Each array element is an object, with following properties : * @param {ol.layer.Layer} [options.layers.layer] - ol.layer.Layer layer to be configured (that has been added to map) * @param {Object} [options.layers.config] - custom configuration object for layer information (title, description, legends, metadata, quicklook url), with following properties : * @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 * @param {Object} [options.options] - ol.control.Control options (see {@link http://openlayers.org/en/latest/apidoc/ol.control.Control.html ol.control.Control}) * @param {Number} [options.options.id] - Ability to add an identifier on the widget (advanced option) * @param {Boolean} [options.options.collapsed = true] - Specify if widget has to be collapsed (true) or not (false) on map loading. Default is true. * @param {Boolean} [options.options.panel = false] - Specify if widget has to have a panel header. Default is false. * @param {Boolean} [options.options.counter = false] - Specify if widget has to have a counter. Default is false. * @param {Boolean} [options.options.allowEdit = true] - Specify if widget has to have an edit button (available only for vector layers). Default is true. * @param {Boolean} [options.options.allowGrayScale = true] - Specify if widget has to have an grayscale button (not available for vector layers). Default is true. * @fires layerswitcher:add * @fires layerswitcher:remove * @fires layerswitcher:extent * @fires layerswitcher:edit * @fires layerswitcher:change:opacity * @fires layerswitcher:change:visibility * @fires layerswitcher:change:position * @fires layerswitcher:change:grayscale * @fires layerswitcher:change:style * @example * map.addControl(new ol.control.LayerSwitcher( * [ * { * layer : wms1, * config : { * title : "test layer name 1", * description : "test layer desc 1", * } * } * ], * { * collapsed : true, * panel : false, * counter : false, * position : "top-left", * allowEdit : true, * allowGrayScale : true, * } * )); * * LayerSwitcher.on("layerswitcher:add", function (e) { * console.warn("layer", e.layer); * }); * LayerSwitcher.on("layerswitcher:remove", function (e) { * console.warn("layer", e.layer); * }); * LayerSwitcher.on("layerswitcher:extent", function (e) { * console.warn("layer", e.layer); * }); * LayerSwitcher.on("layerswitcher:edit", function (e) { * console.warn("layer", e.layer); * }); * LayerSwitcher.on("layerswitcher:change:opacity", function (e) { * console.warn("layer", e.layer, e.opacity); * }); * LayerSwitcher.on("layerswitcher:change:visibility", function (e) { * console.warn("layer", e.layer, e.visibility); * }); * LayerSwitcher.on("layerswitcher:change:position", function (e) { * console.warn("layer", e.layer, e.position); * }); * LayerSwitcher.on("layerswitcher:change:grayscale", function (e) { * console.warn("layer", e.layer, e.grayscale); * }); * LayerSwitcher.on("layerswitcher:change:style", function (e) { * console.warn("layer", e.layer, e.name, e.url); * }); */ var LayerSwitcher = class LayerSwitcher extends Control { /** * See {@link ol.control.LayerSwitcher} * @module LayerSwitcher * @alias module:~controls/LayerSwitcher * @param {*} options - options * @example * import LayerSwitcher from "gpf-ext-ol/controls/LayerSwitcher" * ou * import { LayerSwitcher } from "gpf-ext-ol" */ constructor (options) { options = options || {}; var _options = options.options || {}; var _layers = options.layers || []; // call ol.control.Control constructor super(_options); if (!(this instanceof LayerSwitcher)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } if (!Array.isArray(_layers)) { throw new Error("ERROR WRONG_TYPE : layers should be an array"); } if (typeof _options !== "object") { throw new Error("ERROR WRONG_TYPE : options should be an object"); } /** * Nom de la classe * @private */ this.CLASSNAME = "LayerSwitcher"; this._initialize(_options, _layers); this.container = this._initContainer(_options); // ajout du container (this.element) ? this.element.appendChild(this.container) : this.element = this.container; return this; } // ################################################################### // // ############## public methods (getters, setters) ################## // // ################################################################### // /** * Overload setMap function, that enables to catch map events, such as movend events. * * @param {ol.Map} map - Map. */ setMap (map) { // info : cette méthode est appelée (entre autres?) après un map.addControl() ou map.removeControl() if (map) { // dans le cas de l'ajout du contrôle à la map // on ajoute les couches this._addMapLayers(map); // mode "collapsed" if (!this.collapsed) { this._showLayerSwitcherButton.setAttribute("aria-pressed", true); } // At every map movement, layer switcher may be updated, // according to layers on map, and their range. this._listeners.onMoveListener = map.on( "moveend", () => this._onMapMoveEnd(map) ); // add event listeners when a new layer is added to map, to add it in LayerSwitcher control (and DOM) this._listeners.onAddListener = map.getLayers().on( "add", (evt) => { logger.debug("LayerSwitcher:onAddListener", evt); var layer = evt.element; var id; // on attribue un nouvel identifiant à cette couche, // sauf si c'est une couche qui a déjà été ajoutée dans le LayerSwitcher au préalable (si gpLayerId existe) if (!layer.hasOwnProperty("gpLayerId")) { id = this._layerId; layer.gpLayerId = id; this._layerId++; } else { id = layer.gpLayerId; } if (!this._layers[id]) { this.addLayer(layer); } } ); // add event listeners when a layer is removed from map, to remove it from LayerSwitcher control (and DOM) this._listeners.onRemoveListener = map.getLayers().on( "remove", (evt) => { logger.debug("LayerSwitcher:onRemoveListener", evt); var layer = evt.element; var id = layer.gpLayerId; if (this._layers[id]) { this.removeLayer(layer); } } ); } else { // we are in a setMap(null) case // we forget the listeners linked to the layerSwitcher olObservableUnByKey(this._listeners.onMoveListener); olObservableUnByKey(this._listeners.onAddListener); olObservableUnByKey(this._listeners.onRemoveListener); // we put all the layers at Zindex = 0, without changing the visual order // in order that the next added layers are not hidden by layers with Zindex > 0 for (var i = this._layersOrder.length - 1; i >= 0; i--) { // this._layersOrder[i].layer.setZIndex(0); } } // on appelle la méthode setMap originale d'OpenLayers super.setMap(map); // position if (this.options.position) { this.setPosition(this.options.position); } // reunion du bouton avec le précédent if (this.options.gutter === false) { this.getContainer().classList.add("gpf-button-no-gutter"); } } /** * Add a new layer to control (when added to map) or add new layer configuration * * @param {ol.layer.Layer} layer - layer to add to layer switcher * @param {Object} [config] - additional options for layer configuration * @param {Object} [config.title] - layer title (default is layer identifier) * @param {Object} [config.description] - layer description (default is null) * @param {Object} [config.legends] - layer legends (default is an empty array) * @param {Object} [config.metadata] - layer metadata (default is an empty array) * @param {Object} [config.quicklookUrl] - layer quicklookUrl (default is null) * @fires layerswitcher:add * @example * layerSwitcher.addLayer( * gpParcels, * { * title : "Parcelles cadastrales", * description : "description de la couche", * quicklookUrl : "http://quicklookUrl.fr" * } * ) */ addLayer (layer, config) { var map = this.getMap(); config = config || {}; if (!layer) { logger.log("[ERROR] LayerSwitcher:addLayer - missing layer parameter"); return; } var id = layer.gpLayerId; if (typeof id === "undefined") { logger.trace("[WARN] LayerSwitcher:addLayer - configuration cannot be set for this layer (layer id not found)", layer); return; } // make sure layer is in map layers var isLayerInMap = false; map.getLayers().forEach( (lyr) => { if (lyr.gpLayerId === id) { isLayerInMap = true; } } ); if (!isLayerInMap) { logger.log("[ERROR] LayerSwitcher:addLayer - configuration cannot be set for ", layer, " layer (layer is not in map.getLayers() )"); return; } // if layer is not already in layers list, add it to control (layers list and container div) if (!this._layers[id]) { // 1. add layer to layers list var layerInfos = this.getLayerInfo(layer) || {}; var opacity = layer.getOpacity(); var visibility = layer.getVisible(); var grayscale = layer.get("grayscale"); var isInRange = this.isInRange(layer, map); var layerOptions = { layer : layer, id : id, name : layer.name, // only geoportal layers service : layer.service, // only geoportal layers type : "", // only geoportal website : ie 'feature' opacity : opacity != null ? opacity : 1, visibility : visibility != null ? visibility : true, grayscale : grayscale, inRange : isInRange != null ? isInRange : true, title : config.title != null ? config.title : (layerInfos._title || id), description : config.description || layerInfos._description || null, legends : config.legends || layerInfos._legends || [], metadata : config.metadata || layerInfos._metadata || [], quicklookUrl : config.quicklookUrl || layerInfos._quicklookUrl || null }; this._layers[id] = layerOptions; // 2. create layer div (to be added to control main container) // Création de la div correspondante à cette couche var layerDiv = this._createLayerDiv(layerOptions); // on stocke la div dans les options de la couche, pour une éventuelle réorganisation (setZIndex par ex) this._layers[id].div = layerDiv; // 3. réorganisation des couches si un zIndex est spécifié // FIXME : // _forceNullzIndex !? // getZIndex() retourne undefined au lieu de 0 !? if ((layer.getZIndex && layer.getZIndex() !== 0 && typeof layer.getZIndex() !== "undefined") || layer._forceNullzIndex) { // réorganisation des couches si un zIndex est spécifié this._updateLayersOrder(); } else { // sinon on ajoute la couche au dessus des autres this._layersOrder.unshift(layerOptions); this._lastZIndex++; layer.setZIndex(this._lastZIndex); this._layerListContainer.insertBefore(layerDiv, this._layerListContainer.firstChild); // this._layerListContainer.insertBefore(layerDiv, // (this.options.panel) ? // this._layerListContainer.childNodes[1] : this._layerListContainer.firstChild); } // 3. Add listeners for opacity and visibility changes this._listeners.updateLayerOpacity = layer.on( "change:opacity", (e) => this._updateLayerOpacity(e) ); this._listeners.updateLayerVisibility = layer.on( "change:visible", (e) => this._updateLayerVisibility(e) ); this._listeners.updateLayerGrayScale = layer.on( "change:grayscale", (e) => this._updateLayerGrayScale(e) ); if (this._layers[id].onZIndexChangeEvent == null) { this._layers[id].onZIndexChangeEvent = layer.on( "change:zIndex", () => this._updateLayersOrder() ); } // user may also add a new configuration for an already added layer } else { // add new configuration parameters to layer informations for (var prop in config) { if (config.hasOwnProperty(prop)) { this._layers[id][prop] = config[prop]; } } // set new title in layer div if (config.title) { var nameDiv = document.getElementById(this._addUID("GPname_ID_" + id)); if (nameDiv) { nameDiv.innerHTML = config.title; nameDiv.title = config.description || config.title; } } // add layer info picto if necessary var infodiv = document.getElementById(this._addUID("GPinfo_ID_" + id)); if (!document.getElementById(this._addUID("GPinfo_ID_" + id)) && config.description) { var advancedTools = document.getElementById(this._addUID("GPadvancedTools_ID_" + id)); if (advancedTools) { advancedTools.appendChild( this._createAdvancedToolInformationElement({ id : id }) ); } } // close layer info element if open, to update information. if (infodiv && infodiv.className === "GPlayerInfoOpened") { document.getElementById(this._addUID("GPlayerInfoPanel")).classList.add("GPlayerInfoPanelClosed", "gpf-hidden"); // infodiv.className = "GPlayerInfo"; } } // on met à jour le compteur this._updateLayerCounter(); var self = this; setTimeout(() => { self._updateLayerGrayScale({ target : { gpLayerId : id } }); }, 0); /** * event triggered when a layer is added * * @event layerswitcher:add * @property {Object} type - event * @property {Object} layer - layer * @property {Object} target - instance LayerSwitcher * @example * LayerSwitcher.on("layerswitcher:add", function (e) { * console.log(e.layer); * }) */ this.dispatchEvent({ type : "layerswitcher:add", layer : this._layers[id] }); }; /** * Remove a layer from control * * @param {ol.layer.Layer} layer - layer. * @fires layerswitcher:remove * @deprecated on the future version ... */ removeLayer (layer) { if (!layer) { return; } olObservableUnByKey(this._listeners.updateLayerOpacity); olObservableUnByKey(this._listeners.updateLayerVisibility); olObservableUnByKey(this._listeners.updateLayerGrayScale); // olObservableUnByKey(this._listeners.updateLayersOrder); logger.trace(layer); var layerID = layer.gpLayerId; // var layerList = document.getElementById(this._addUID("GPlayersList")).firstChild; // close layer info element if open. var infodiv = document.getElementById(this._addUID("GPinfo_ID_" + layerID)); if (infodiv && infodiv.className === "GPlayerInfoOpened") { document.getElementById(this._addUID("GPlayerInfoPanel")).classList.add("GPlayerInfoPanelClosed", "gpf-hidden"); // infodiv.className = "GPlayerInfo"; } var stylediv = document.getElementById(this._addUID("GPedit_ID_" + layerID)); if (stylediv && stylediv.classList.contains("GPlayerStyleOpened")) { document.getElementById(this._addUID("GPlayerStylePanel")).classList.add("GPlayerStylePanelClosed", "gpf-hidden"); } // remove layer div var layerDiv = document.getElementById(this._addUID("GPlayerSwitcher_ID_" + layerID)); if (layerDiv) { this._layerListContainer.removeChild(layerDiv); } var layerIndex = Math.abs(layer.getZIndex() - this._lastZIndex); // on retire la couche de la liste ordonnée des layers this._layersOrder.splice(layerIndex, 1); this._lastZIndex--; // on met à jour les zindex des couches restantes var layerOrderTemp = this._layersOrder; for (var i = 0; i < layerOrderTemp.length; i++) { layerOrderTemp[i].layer.setZIndex(this._lastZIndex - i); } /** * event triggered when a layer is removed * * @event layerswitcher:remove * @property {Object} type - event * @property {Object} layer - layer * @property {Object} target - instance LayerSwitcher * @example * LayerSwitcher.on("layerswitcher:remove", function (e) { * console.log(e.layer); * }) */ this.dispatchEvent({ type : "layerswitcher:remove", layer : this._layers[layerID] }); // on retire la couche de la liste des layers delete this._layers[layerID]; // on met à jour le compteur this._updateLayerCounter(); } /** * Collapse or display control main container * * @param {Boolean} collapsed - True to collapse control, False to display it */ setCollapsed (collapsed) { if (collapsed === undefined) { logger.log("[ERROR] LayerSwitcher:setCollapsed - missing collapsed parameter"); return; } var isCollapsed = !document.getElementById(this._addUID("GPshowLayersList")).checked; if ((collapsed && isCollapsed) || (!collapsed && !isCollapsed)) { return; } // on simule l'ouverture du panneau après un click if (!isCollapsed) { // var layers = document.getElementsByClassName("GPlayerInfoOpened"); // for (var i = 0; i < layers.length; i++) { // layers[i].className = "GPlayerInfo"; // } document.getElementById(this._addUID("GPlayerInfoPanel")).classList.add("GPlayerInfoPanelClosed", "gpf-hidden"); document.getElementById(this._addUID("GPlayerStylePanel")).classList.add("GPlayerStylePanelClosed", "gpf-hidden"); } document.getElementById(this._addUID("GPshowLayersList")).checked = !collapsed; } /** * Returns true if widget is collapsed (minimize), false otherwise * @returns {Boolean} is collapsed */ getCollapsed () { return this.collapsed; } /** * Display or hide removeLayerPicto from layerSwitcher for this layer * * @param {ol.layer.Layer} layer - ol.layer to be configured * @param {Boolean} removable - specify if layer can be remove from layerSwitcher (true) or not (false). Default is true */ setRemovable (layer, removable) { if (!layer) { return; } var layerID = layer.gpLayerId; if (layerID == null) { // on teste si layerID est null ou undefined logger.log("[LayerSwitcher:setRemovable] layer should be added to map before calling setRemovable method"); return; } var removalDiv = document.getElementById(this._addUID("GPremove_ID_" + layerID)); if (removalDiv) { if (removable === false) { removalDiv.style.display = "none"; } else if (removable === true) { removalDiv.style.display = "block"; } else { } } } /** * Get container * * @returns {DOMElement} container */ getContainer () { return this.container; } /** * Forget add listener added to the control */ forget () { // on supprime les listeners d'ajout de couches olObservableUnByKey(this._listeners.onAddListener); } /** * Add listeners to catch map layers addition */ listen () { // on ajoute les listeners d'ajout de couches var map = this.getMap(); if (map) { this._listeners.onAddListener = map.getLayers().on( "add", (evt) => { logger.debug("LayerSwitcher:onAddListener", evt); var layer = evt.element; var id; // on attribue un nouvel identifiant à cette couche, // sauf si c'est une couche qui a déjà été ajoutée dans le LayerSwitcher au préalable (si gpLayerId existe) if (!layer.hasOwnProperty("gpLayerId")) { id = this._layerId; layer.gpLayerId = id; this._layerId++; } else { id = layer.gpLayerId; } if (!this._layers[id]) { this.addLayer(layer); } } ); } } // ################################################################### // // ##################### init component ############################## // // ################################################################### // /** * Initialize LayerSwitcher control (called by constructor) * * @param {Object} options - ol.control.Control options (see {@link http://openlayers.org/en/latest/apidoc/ol.control.Control.html ol.control.Control}) * @param {Array} layers - list of layers to be configured. Each array element is an object, with following properties : * @private */ _initialize (options, layers) { // options par defaut this.options = { id : "", collapsed : true, draggable : false, counter : false, panel : false, gutter : false, allowEdit : true, allowGrayScale : true }; // merge with user options Utils.assign(this.options, options); this.options.layers = layers; // identifiant du contrôle : utile pour suffixer les identifiants CSS (pour gérer le cas où il y en a plusieurs dans la même page) this._uid = this.options.id || SelectorID.generate(); // {Object} control layers list. Each key is a layer id, and its value is an object of layers options (layer, id, opacity, visibility, title, description...) this._layers = {}; // [Array] array of ordered control layers this._layersOrder = []; // [Object] associative array of layers ordered by zindex (keys are zindex values, and corresponding values are arrays of layers at this zindex) this._layersIndex = {}; // {Number} layers max z index, to order layers using their z index this._lastZIndex = 0; // {Number} layers max id, incremented when a new layer is added this._layerId = 0; /** {Boolean} true if widget is collapsed, false otherwise */ this.collapsed = (this.options.collapsed !== undefined) ? this.options.collapsed : true; // div qui contiendra les div des listes. this._layerListContainer = null; // [Object] listeners added to the layerSwitcher saved here in order to delete them if we remove the control from the map) this._listeners = {}; // add options layers to layerlist. // (seulement les couches configurées dans les options du layerSwitcher par l'utilisateur), // les autres couches de la carte seront ajoutées dans la méthode setMap for (var i = 0; i < layers.length; i++) { // recup la layer, son id, var layer = layers[i].layer; if (layer) { var id; // si elles ont déjà un identifiant (gpLayerId), on le récupère, sinon on en crée un nouveau, en incrémentant this_layerId. if (!layer.hasOwnProperty("gpLayerId")) { id = this._layerId; layer.gpLayerId = id; this._layerId++; } else { id = layer.gpLayerId; } // et les infos de la conf si elles existent (title, description, legends, quicklook, metadata) var conf = layers[i].config || {}; var opacity = layer.getOpacity(); var visibility = layer.getVisible(); var grayscale = layer.get("grayscale"); var layerOptions = { layer : layer, // la couche ol.layer concernée id : id, name : layer.name, // only geoportal layers service : layer.service, // only geoportal layers opacity : opacity != null ? opacity : 1, visibility : visibility != null ? visibility : true, grayscale : grayscale, title : conf.title != null ? conf.title : conf.id ? conf.id : id, description : conf.description || null, legends : conf.legends || [], metadata : conf.metadata || [], quicklookUrl : conf.quicklookUrl || null }; this._layers[id] = layerOptions; } } } /** * Create control main container (called by constructor) * * @returns {DOMElement} container - control container * @private */ _initContainer () { // creation du container principal var 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.collapsed) { input.checked = "checked"; this.collapsed = false; } else { this.collapsed = true; } // on ajoute un écouteur d'évènement sur le bouton (checkbox) de dépliement/repliement des couches, // pour modifier la propriété this.collapsed quand on clique dessus var context = this; // event listener var changeCollapsed = function (e) { this.collapsed = !e.target.checked; // on génère nous même l'evenement OpenLayers de changement de pté // (utiliser layerSwitcher.on("change:collapsed", function ) pour s'abonner à cet évènement) this.dispatchEvent("change:collapsed"); }; input.addEventListener( "click", function (e) { changeCollapsed.call(context, e); } ); // ajout dans le container principal du picto du controle var picto = this._showLayerSwitcherButton = this._createMainPictoElement(); container.appendChild(picto); // ajout du compteur de couches container.classList.add("GplayerSwitcher-counterRemoved"); if (this.options.counter) { container.classList.remove("GplayerSwitcher-counterRemoved"); container.classList.add("GplayerSwitcher-counterAdded"); var counter = this._layerSwitcherCounter = this._createMainCounterLayersElement(); picto.appendChild(counter); } // ajout dans le container principal de la liste des layers var divL = this._createMainLayersElement(); container.appendChild(divL); // header ? if (this.options.panel) { // header var panelHeader = this._createLayersPanelHeaderElement(); divL.appendChild(panelHeader); // icon var panelIcon = this._createLayersPanelIconElement(); panelHeader.appendChild(panelIcon); // title var panelTitle = this._createLayersPanelTitleElement(); panelHeader.appendChild(panelTitle); // close picto var panelClose = this._createLayersPanelCloseElement(); panelHeader.appendChild(panelClose); } var div = this._layerListContainer = this._createMainLayersDivElement(); divL.appendChild(div); // creation du mode draggable this._createDraggableElement(div, this); // ajout dans le container principal du panneau d'information var divI = this._createMainInfoElement(); var divD = this._createMainInfoDivElement(); divI.appendChild(divD); container.appendChild(divI); // ajout dans le container principal du panneau des styles var divS = this._createMainStyleElement(); var divSd = this._createMainStyleDivElement(); divS.appendChild(divSd); container.appendChild(divS); return container; } /** * Add all map layers to control main container * * @param {Object} map - ol.Map object, to which control is added * @private */ _addMapLayers (map) { this._layersIndex = {}; // on parcourt toutes les couches de la carte, pour les ajouter à la liste du controle si ce n'est pas déjà le cas. // idée : le layerSwitcher doit représenter l'ensemble des couches de la carte. map.getLayers().forEach((layer) => { // ajout des couches de la carte à la liste var id = null; // si elles ont déjà un identifiant (gpLayerId), on le récupère, sinon on en crée un nouveau, en incrémentant this_layerId. if (!layer.hasOwnProperty("gpLayerId")) { id = this._layerId; layer.gpLayerId = id; this._layerId++; } else { id = layer.gpLayerId; } var layerInfos = this.getLayerInfo(layer) || {}; if (!this._layers[id]) { // si la couche n'est pas encore dans la liste des layers (this._layers), on l'ajoute var opacity = layer.getOpacity(); var visibility = layer.getVisible(); var grayscale = layer.get("grayscale"); var isInRange = this.isInRange(layer, map); var layerOptions = { layer : layer, id : id, name : layer.name, // only geoportal layers service : layer.service, // only geoportal layers opacity : opacity != null ? opacity : 1, visibility : visibility != null ? visibility : true, grayscale : grayscale, inRange : isInRange != null ? isInRange : true, title : layerInfos._title || id, description : layerInfos._description || null, legends : layerInfos._legends || [], metadata : layerInfos._metadata || [], quicklookUrl : layerInfos._quicklookUrl || null }; this._layers[id] = layerOptions; } else { // si elle existe déjà, on met à jour ses informations (visibility, opacity, inRange) this._layers[id].opacity = layer.getOpacity(); this._layers[id].visibility = layer.getVisible(); this._layers[id].grayscale = layer.get("grayscale"); this._layers[id].inRange = this.isInRange(layer, map); } // on met à jour le compteur this._updateLayerCounter(); // Ajout de listeners sur les changements d'opacité, visibilité this._listeners.updateLayerOpacity = layer.on( "change:opacity", (e) => this._updateLayerOpacity(e) ); this._listeners.updateLayerVisibility = layer.on( "change:visible", (e) => this._updateLayerVisibility(e) ); this._listeners.updateLayerGrayScale = layer.on( "change:grayscale", (e) => this._updateLayerGrayScale(e) ); var self = this; setTimeout(() => { self._updateLayerGrayScale({ target : { gpLayerId : id } }); }, 0); // récupération des zindex des couches s'ils existent, pour les ordonner. if (layer.getZIndex !== undefined) { var layerIndex = layer.getZIndex() || 0; // FIXME le zIndex peut être undefined !? donc par defaut à 0 ! if (!this._layersIndex[layerIndex] || !Array.isArray(this._layersIndex[layerIndex])) { this._layersIndex[layerIndex] = []; } this._layersIndex[layerIndex].push(this._layers[id]); }; }); // on récupère l'ordre d'affichage des couches entre elles dans la carte, à partir de zindex. for (var zindex in this._layersIndex) { if (this._layersIndex.hasOwnProperty(zindex)) { var layers = this._layersIndex[zindex]; for (var l = 0; l < layers.length; l++) { // à ce stade layers[l] est une couche de this._layers. // on conserve l'ordre des couches : la première est celle qui se situe tout en haut, et la dernière est le "fond de carte" this._layersOrder.unshift(layers[l]); // et on réordonne les couches avec des zindex, uniques. this._lastZIndex++; layers[l].layer.setZIndex(this._lastZIndex); if (this._layers[layers[l].layer.gpLayerId].onZIndexChangeEvent == null) { this._layers[layers[l].layer.gpLayerId].onZIndexChangeEvent = layers[l].layer.on( "change:zIndex", () => this._updateLayersOrder() ); } } } } // on ajoute les div correspondantes aux différentes couches (dans l'ordre inverse d'affichage) dans le controle. for (var j = 0; j < this._layersOrder.length; j++) { var layerOptions = this._layersOrder[j]; var layerDiv = this._createLayerDiv(layerOptions); if (!this._layerListContainer.querySelector("#" + layerDiv.id)) { this._layerListContainer.appendChild(layerDiv); } // on stocke la div dans les options de la couche, pour une éventuelle réorganisation (setZIndex par ex) this._layers[layerOptions.id].div = layerDiv; } } /** * create layer div (to append to control DOM element). * * @param {Object} layerOptions - layer options (id, title, description, legends, metadata, quicklookUrl ...) * * @returns {DOMElement} DOM element * * @private */ _createLayerDiv (layerOptions) { var isLegends = layerOptions.legends && layerOptions.legends.length !== 0; var isMetadata = layerOptions.metadata && layerOptions.metadata.length !== 0; var isQuicklookUrl = layerOptions.quicklookUrl; // on n'affiche les informations que si elles sont renseignées // (pour ne pas avoir un panneau vide) if (isLegends || isMetadata || isQuicklookUrl) { layerOptions.displayInformationElement = true; } layerOptions.editable = false; // information sur le type de couche : vecteur if (this.options.allowEdit) { if (layerOptions.layer instanceof VectorLayer || layerOptions.layer instanceof VectorTileLayer) { layerOptions.editable = true; } } layerOptions.grayable = false; // information sur le type de couche : raster if (this.options.allowGrayScale) { if (layerOptions.layer instanceof TileLayer || layerOptions.layer instanceof VectorTileLayer) { layerOptions.grayable = true; } } // ajout d'une div pour cette layer dans le control var layerDiv = this._createContainerLayerElement(layerOptions); if (!layerOptions.inRange) { layerDiv.classList.add("outOfRange"); } return layerDiv; } // ################################################################### // // ######################### DOM events ############################## // // ################################################################### // /** * ... * * @method onShowLayerSwitcherClick * @param { event } e évènement associé au clic * @private */ onShowLayerSwitcherClick (e) { if (e.target.ariaPressed === "true") { this.onPanelOpen(); } var opened = this._showLayerSwitcherButton.ariaPressed; this.collapsed = !(opened === "true");// on génère nous même l'evenement OpenLayers de changement de propriété // (utiliser mousePosition.on("change:collapsed", function(e) ) pour s'abonner à cet évènement) this.dispatchEvent("change:collapsed"); // on recalcule la position if (this.options.position && !this.collapsed) { this.updatePosition(this.options.position); } } /** * update layer counter */ _updateLayerCounter () { if (this._layerSwitcherCounter) { this._layerSwitcherCounter.innerHTML = Object.keys(this._layers).length; } } /** * Change layer opacity on layer opacity picto click * * @param {Object} e - event * @private */ _onChangeLayerOpacity (e) { e.target.parentNode.style.setProperty("--progress-right", e.target.value + "%"); var divId = e.target.id; // ex GPvisibilityPicto_ID_26 var layerID = SelectorID.index(divId); // ex. 26 var layer = this._layers[layerID].layer; var opacityValue = e.target.value; var opacityId = document.getElementById(this._addUID("GPopacityValue_ID_" + layerID)); opacityId.innerHTML = opacityValue + "%"; layer.setOpacity(opacityValue / 100); } /** * Update picto opacity value on layer opacity change * * @param {Object} e - event * @fires layerswitcher:change:opacity * @private */ _updateLayerOpacity (e) { var opacity = e.target.getOpacity(); if (opacity > 1) { opacity = 1; } if (opacity < 0) { opacity = 0; } var id = e.target.gpLayerId; var layerOpacityInput = document.getElementById(this._addUID("GPopacityValueDiv_ID_" + id)); if (layerOpacityInput) { layerOpacityInput.value = Math.round(opacity * 100); } var layerOpacitySpan = document.getElementById(this._addUID("GPopacityValue_ID_" + id)); if (layerOpacitySpan) { layerOpacitySpan.innerHTML = Math.round(opacity * 100) + "%"; } /** * event triggered when an opacity layer is changed * * @event layerswitcher:change:opacity * @property {Object} type - event * @property {Object} opacity - opacity * @property {Object} layer - layer * @property {Object} target - instance LayerSwitcher * @example * LayerSwitcher.on("layerswitcher:change", function (e) { * console.log(e.opacity); * }) */ this.dispatchEvent({ type : "layerswitcher:change:opacity", opacity : opacity, layer : this._layers[id] }); } /** * Change layer visibility on layer visibility picto click * * @param {Object} e - event * @private */ _onVisibilityLayerClick (e) { var divId = e.target.id; // ex GPvisibilityPicto_ID_26 var layerID = SelectorID.index(divId); // ex. 26 var layer = this._layers[layerID].layer; layer.setVisible((e.target.ariaPressed === "true")); } /** * Change picto visibility on layer visibility change * * @param {Object} e - event * @fires layerswitcher:change:visibility * @private */ _updateLayerVisibility (e) { var visible = e.target.getVisible(); var id = e.target.gpLayerId; var layerVisibility = document.getElementById(this._addUID("GPvisibilityPicto_ID_" + id)); if (layerVisibility) { layerVisibility.ariaPressed = visible; } /** * event triggered when an visibility layer is changed * * @event layerswitcher:change:visibility * @property {Object} type - event * @property {Object} visibility - visibility * @property {Object} layer - layer * @property {Object} target - instance LayerSwitcher * @example * LayerSwitcher.on("layerswitcher:change:visibility", function (e) { * console.log(e.visibility); * }) */ this.dispatchEvent({ type : "layerswitcher:change:visibility", visibility : visible, layer : this._layers[id] }); } /** * Change layer style on mapbox layer dialog * * @param {Object} e - event * @private */ _onChangeStyleLayerClick (e) { var id = e.target.id; // ex GPvisibilityPicto_ID_26 var layerID = SelectorID.index(id); // ex. 26 var layer = this._layers[layerID].layer; layer.set("grayscale", false); var divId = e.target.id; // ex GPvisibilityPicto_ID_26 var layerID = SelectorID.index(divId); // ex. 26 var greyscaleBtn = document.getElementById(this._addUID("GPgreyscale_ID_" + layerID)); greyscaleBtn.classList.add("GPlayerGreyscaleOff"); greyscaleBtn.classList.remove("GPlayerGreyscaleOn"); layer.styleUrl = e.target.value; layer.styleName = e.target.dataset.name; layer.setStyleMapBox(); /** * event triggered when an select style is changed * * @event layerswitcher:change:style * @property {Object} type - event * @property {String} name - name * @property {String} url - url * @property {Object} layer - layer * @property {Object} target - instance LayerSwitcher * @example * LayerSwitcher.on("layerswitcher:change:style", function (e) { * console.log(e.url); * }) */ this.dispatchEvent({ type : "layerswitcher:change:style", name : layer.styleName, url : layer.styleUrl, layer : this._layers[layerID] }); } /** * Change layers order in layerswitcher (control container) on a layer index change (on map) or when a layer is added to a specific zindex * @todo fires layerswitcher:change:zindex * @private */ _updateLayersOrder () { // info : // 1. on récupère les zindex et les couches associées dans un tableau associatif (objet) // 2. on réordonne les couche selon leur index : on leur attribue de nouveaux zindex uniques // 3. on vide le container des layers, et rajoute les div des couches dans l'ordre décroissant des zindex var map = this.getMap(); if (!map) { return; } this._layersIndex = {}; var layerIndex; var id; // on parcourt toutes les couches pour récupérer leur ordre : // on stocke les couches dans un tableau associatif ou les clés sont les zindex, et les valeurs sont des tableaux des couches à ce zindex. map.getLayers().forEach( (layer) => { id = layer.gpLayerId; // on commence par désactiver temporairement l'écouteur d'événements sur le changement de zindex. olObservableUnByKey(this._layers[id].onZIndexChangeEvent); this._layers[id].onZIndexChangeEvent = null; // on ajoute la couche dans le tableau (de l'objet this._layersIndex) correspondant à son zindex layerIndex = null; if (layer.getZIndex !== undefined) { layerIndex = layer.getZIndex(); if (!this._layersIndex[layerIndex] || !Array.isArray(this._layersIndex[layerIndex])) { this._layersIndex[layerIndex] = []; } this._layersIndex[layerIndex].push(this._layers[id]); }; } ); // on réordonne les couches entre elles dans la carte, à partir des zindex stockés ci-dessus. this._lastZIndex = 0; this._layersOrder = []; for (var zindex in this._layersIndex) { if (this._layersIndex.hasOwnProperty(zindex)) { var layers = this._layersIndex[zindex]; for (var l = 0; l < layers.length; l++) { // à ce stade layers[l] est une couche de this._layers. // on conserve l'ordre des couches : la première est celle qui se situe tout en haut, et la dernière est le "fond de carte" this._layersOrder.unshift(layers[l]); // et on réordonne les couches avec des zindex, uniques. this._lastZIndex++; // layers[l].layer.setZIndex(lastZIndex); // et on réactive l'écouteur d'événement sur les zindex if (this._layers[layers[l].layer.gpLayerId].onZIndexChangeEvent == null) { this._layers[layers[l].layer.gpLayerId].onZIndexChangeEvent = layers[l].layer.on( "change:zIndex", () => this._updateLayersOrder()