geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
1,216 lines (1,135 loc) • 106 kB
JavaScript
// 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 Map from "ol/Map";
import Layer from "ol/layer/Layer";
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";
import ToolTips from "../../Utils/ToolTips";
// DOM
import LayerSwitcherDOM from "./LayerSwitcherDOM";
var logger = Logger.getLogger("layerswitcher");
/**
* @typedef {Object} LayerSwitcherOptions
* @property {string} [id] - Identifiant unique du widget.
* @property {boolean} [collapsed=true] - Définit si le widget est replié au chargement.
* @property {boolean} [draggable=false] - Permet de déplacer le panneau du LayerSwitcher.
* @property {boolean} [counter=false] - Affiche un compteur du nombre de couches visibles.
* @property {boolean} [panel=false] - Affiche un en-tête (header) dans le panneau du LayerSwitcher.
* @property {boolean} [gutter=false] - Ajoute ou retire l’espace autour du panneau.
* @property {boolean} [allowEdit=true] - Affiche le bouton d’édition pour les couches éditables (vecteur).
* @property {boolean} [allowGrayScale=true] - Affiche le bouton N&B (niveaux de gris) pour les couches compatibles.
* @property {boolean} [allowDraggable=true] - Permet de déplacer les couches.
* @property {boolean} [allowDelete=true] - Affiche le bouton de suppression de la couche.
* @property {boolean} [allowTooltips=false] - Active l’affichage des info-bulles (tooltips) sur les éléments du widget.
* @property {string} [position] - Position CSS du widget sur la carte.
* @property {Array<HeaderButton>} [headerButtons] - Liste d’outils personnalisés à afficher pour chaque couche.
* @property {Array<AdvancedToolOption>} [advancedTools] - Liste d’outils personnalisés à afficher pour chaque couche.
* Par défaut, les boutons d'info, de style, noir et blanc et recentrer sont ajoutés.
*/
/**
* Option d'un outil personnalisé
* @typedef {Object} AdvancedToolOption
* @property {String} [label] - Optionnel. Label du bouton
* @property {String} [key] - Optionnel. Mot clé indiquant qu'il s'agit d'une fonctionnalité native du layer switcher
* @property {String} [icon] - Optionnel. Icône de l'outil. Peut être un lien html, svg ou une classe.
* @property {String} [className] - Optionnel. Classes à appliquer en plus sur le bouton.
* @property {Object} [attributes] - Optionnel. Attributs additionnels à ajouter au bouton.
* Attributs ajoutés avec la méthode `setAttribute`.
* @property {Array<import("ol/layer/Base").default|String>} [accepted] - Optionnel. Définit les types de couche pour lesquelles l'outil fonctionne.
* Par défaut, ne filtre pas sur le type de couche.
* Le constructeur ou le nom du constructeur peut être donné en argument.
* Ne fonctionne pas pour les fonctionnalités déjà existantes.
* @property {AdvancedToolCallback} [cb] - Optionnel. Callback au click sur l'outil.
* @property {Object} [styles] - Optionnel. Styles à appliquer.
*/
/**
* Callback au clic sur un outil personnalisé.
* @callback AdvancedToolCallback
* @param {PointerEvent} e Événement générique au clic sur l'outil
* @param {LayerSwitcher} layerSwitcher instance du gestionnaire de couche
* @param {import('ol/layer').Layer} layer Couche associée à l'outil
* @param {Object} options Options de la couche associée
*/
/**
* Bouton pour le gestionnaire de couche
* @typedef {Object} HeaderButton
* @property {String} label - Label du bouton.
* @property {HeaderButtonCallback} cb - Callback au click sur l'outil.
* @property {String} [id] - Id à ajouter sur le bouton.
* @property {String} [className] - Optionnel. Classes à appliquer en plus sur le bouton.
* @property {Object} [attributes] - Optionnel. Attributs additionnels à ajouter au bouton.
* Attributs ajoutés avec la méthode `setAttribute`.
* @property {String} [title] - Optionnel. Titre du bouton. Aucun par défaut.
* @property {String} [icon] - Optionnel. Icône de l'outil. Classe à ajouter au bouton.
*/
/**
* Callback au clic sur un bouton du header.
* @callback HeaderButtonCallback
* @param {PointerEvent} e Événement générique au clic sur l'outil
* @param {LayerSwitcher} layerSwitcher instance du gestionnaire de couche
*/
/**
* @typedef {Object} LayerSwitcherLayersConfig
* @property {Layer} layer - Objet couche OpenLayers à gérer.
* @property {Object} [config] - Métadonnées associées à la couche.
* @property {string} [config.title] - Titre de la couche.
* @property {string} [config.producer] - Producteur de la couche.
* @property {string} [config.thumbnail] - Pictogramme de la couche.
* @property {string} [config.description] - Description de la couche.
* @property {string} [config.quicklookUrl] - URL d’aperçu rapide.
* @property {Array<Object>} [config.legends] - Légendes associées à la couche.
* @property {Array<Object>} [config.metadata] - Métadonnées associées à la couche.
* @property {boolean} [config.locked] - Indique si la couche est verrouillée.
*/
/**
* @classdesc
* OpenLayers Control to manage map layers : their order, visibility and opacity, and display their informations (title, description, legends, metadata...)
*
* @module LayerSwitcher
* @alias ol.control.LayerSwitcher
*/
class LayerSwitcher extends Control {
/*
* @param {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 {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.
* @param {Array} [options.options.advancedTools] - ...
* @param {String} [options.options.advancedTools.label] - Specify the label name of the button
* @param {String} [options.options.advancedTools.icon] - icon (optionnal)
* @param {Function} [options.options.advancedTools.cb] - callback (optionnal)
* @param {Object} [options.options.advancedTools.styles] - styles (optionnal)
*/
/**
* @constructor
* @param {Object} options - control options
* @param {Array<LayerSwitcherLayersConfig>} [options.layers] - list of layers to be configured. Each array element is an object, with following properties :
* @param {LayerSwitcherOptions} [options.options] - ol.control.Control options (see {@link http://openlayers.org/en/latest/apidoc/ol.control.Control.html ol.control.Control})
* @fires layerswitcher:add
* @fires layerswitcher:remove
* @fires layerswitcher:lock
* @fires layerswitcher:extent
* @fires layerswitcher:edit
* @fires layerswitcher:changeproperty
* @fires layerswitcher:change:selected
* @fires layerswitcher:change:opacity
* @fires layerswitcher:change:visibility
* @fires layerswitcher:change:position
* @fires layerswitcher:change:grayscale
* @fires layerswitcher:change:style
* @fires layerswitcher:change:locked
* @fires layerswitcher:custom
* @fires layerswitcher:header:button
* @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,
* headerButtons : [
* {
* label: 'Ajouter',
* title: 'Ajouter une couche',
* icon: "svg | http",
* cb: (e, switcher) => {},
* },
* ],
* advancedTools : [
* {
* label = 'Bouton',
* icon = "svg | http",
* cb = (e, LayerSwitcher, layer, options) => {},
* styles = {},
* }
* ]
* }
* ));
*
* 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:selected", function (e) {
* console.warn("layer", e.layer, e.previous);
* });
* 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);
* });
* LayerSwitcher.on("layerswitcher:change:locked", function (e) {
* console.warn("layer", e.layer, e.locked);
* });
* LayerSwitcher.on("layerswitcher:custom", function (e) {
* console.warn("layer", e.action, e.layer);
* })
* LayerSwitcher.on("layerswitcher:header:button", function (e) {
* console.warn("Action", e.action, e.target);
* })
* LayerSwitcher.on("layerswitcher:propertychange", function (e) {
* console.warn("layer", e.layer, e.key, e.value);
* });
*/
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;
}
static switcherButtons = {
INFO : "info",
EDIT : "edition",
GREYSCALE : "greyscale",
EXTENT : "extent",
};
// ################################################################### //
// ############## public methods (getters, setters) ################## //
// ################################################################### //
/**
* Overload setMap function, that enables to catch map events, such as movend events.
* @inheritdoc {@link https://openlayers.org/en/latest/apidoc/module-ol_control_Control-Control.html#setMap}
* @param {Map} map - Map.
*/
setMap (map) {
// INFO
// cette méthode est appelée
// 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");
}
// initialize tooltips
if (this.options.allowTooltips) {
ToolTips.init();
}
}
/**
* Add a new layer to control (when added to map) or add new layer configuration
*
* @param {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 {@link LayerSwitcher#ADD_LAYER_EVENT}
* @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 || layer.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 locked = layer.get("locked");
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,
locked : locked,
inRange : isInRange != null ? isInRange : true,
producer : config.producer != null ? config.producer : (layerInfos._producer || null),
title : config.title != null ? config.title : (layerInfos._title || id),
thumbnail : config.thumbnail != null ? config.thumbnail : (layerInfos._thumbnail || null),
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);
layerDiv.dataset.sortableId = this._layerId;
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)
);
this._listeners.updateLayerLocked = layer.on(
"change:locked",
(e) => this._updateLayerLocked(e)
);
this._listeners.updateProperties = layer.on(
"propertychange",
(e) => this._updateGenericProperty(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;
}
}
// set new title in layer div
if (config.producer) {
let producerDiv = document.getElementById(this._addUID("GPlayerProducer_ID_" + id));
if (producerDiv) {
producerDiv.innerHTML = config.producer;
} else {
this.setLayerProducer(id, config.producer);
}
}
// 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._createInformationElement({
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
*/
this.dispatchEvent({
type : this.ADD_LAYER_EVENT,
layer : this._layers[id]
});
};
/**
* Remove a layer from control
*
* @param {Layer} layer - layer.
* @fires layerswitcher:remove {@link LayerSwitcher#REMOVE_LAYER_EVENT}
* @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.updateLayerLocked);
olObservableUnByKey(this._listeners.updateProperties);
// 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
*/
this.dispatchEvent({
type : this.REMOVE_LAYER_EVENT,
layer : this._layers[layerID]
});
if (layer === this.getSelectedLayer()) {
// Réinitialise la couche sélectionnée
this.setSelectedLayer();
}
// on retire la couche de la liste des layers
delete this._layers[layerID];
// on met à jour le compteur
this._updateLayerCounter();
}
/**
* Lock a layer, so it cannot be removed or modified from layerSwitcher
* @param {Layer} layer - layer to be locked
* @param {Boolean} locked - true if locked
* @fires layerswitcher:lock {@link LayerSwitcher#LOCK_LAYER_EVENT}
*/
lockLayer (layer, locked) {
if (!layer) {
return;
}
var layerID = layer.gpLayerId;
var layerDiv = document.getElementById(this._addUID("GPlayerSwitcher_ID_" + layerID));
if (layerDiv) {
locked ? layerDiv.setAttribute("disabled", true) : layerDiv.removeAttribute("disabled");
}
layer.set("locked", locked);
/**
* event triggered when a layer is locked or unlocked
* @event layerswitcher:lock
*/
this.dispatchEvent({
type : this.LOCK_LAYER_EVENT,
layer : this._layers[layerID],
locked : locked
});
}
/**
* 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 {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 {HTMLElement} 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,
allowDraggable : true,
allowDelete : true,
allowTooltips : false,
headerButtons : [],
advancedTools : null,
};
// 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)
* @type {String}
* @private
*/
this._uid = this.options.id || SelectorID.generate();
/**
* Control layers list.
* ach key is a layer id, and its value is an object of layers options (layer, id, opacity, visibility, title, description...)
* @type {Object}
* @private
*/
this._layers = {};
/**
* array of ordered control layers
* @type {Array}
* @private
*/
this._layersOrder = [];
/**
* associative array of layers ordered by zindex (keys are zindex values, and corresponding values are arrays of layers at this zindex)
* @type {Object}
* @private
*/
this._layersIndex = {};
/**
* layers max z index, to order layers using their z index
* @type {Number}
* @private
*/
this._lastZIndex = 0;
/**
* layers max id, incremented when a new layer is added
* @type {Number}
* @private
*/
this._layerId = 0;
/**
* collapse mode
* true if widget is collapsed, false otherwise
*/
this.collapsed = (this.options.collapsed !== undefined) ? this.options.collapsed : true;
/**
* Layer list (DOM).
* @type {HTMLElement}
* @private
*/
this._layerListContainer = null;
/**
* listeners added to the layerSwitcher saved here in order to delete them if we remove the control from the map)
* @type {Object}
* @private
*/
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 layerInfo = this.getLayerInfo(layer);
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 || layerInfo._title,
description : conf.description || layerInfo._description,
legends : conf.legends || layerInfo._legends,
metadata : conf.metadata || layerInfo._metadata,
quicklookUrl : conf.quicklookUrl || layerInfo._quicklookUrl,
thumbnail : conf.thumbnail || layerInfo._thumbnail,
producer : conf.producer || layerInfo._producer
};
this._layers[id] = layerOptions;
}
}
/**
* div that will contain layers list
* @private
*/
this._layerListContainer = null;
/**
* counter of layers in layerSwitcher control
* @private
*/
this._layerSwitcherCounter = null;
/**
* button to show/hide layerSwitcher control
* @private
*/
this._showLayerSwitcherButton = null;
/**
* event triggered when a property is modified
* @event layerswitcher:propertychange
* @defaultValue "layerswitcher:propertychange"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:propertychange", function (e) {
* console.log(e.layer);
* })
*/
this.PROPERTY_CHANGE_EVENT = "layerswitcher:propertychange";
/**
* event triggered when a layer is added
* @event layerswitcher:add
* @defaultValue "layerswitcher:add"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:add", function (e) {
* console.log(e.layer);
* })
*/
this.ADD_LAYER_EVENT = "layerswitcher:add";
/**
* event triggered when a layer is removed
* @event layerswitcher:remove
* @defaultValue "layerswitcher:remove"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:remove", function (e) {
* console.log(e.layer);
* })
*/
this.REMOVE_LAYER_EVENT = "layerswitcher:remove";
/**
* event triggered when a layer is locked
* @event layerswitcher:lock
* @defaultValue "layerswitcher:lock"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:lock", function (e) {
* console.log(e.layer);
* })
*/
this.LOCK_LAYER_EVENT = "layerswitcher:lock";
/**
* event triggered when a layer extent is changed
* @event layerswitcher:extent
* @defaultValue "layerswitcher:extent"
* @group Events
* @param {Object} extent - extent (map projection)
* @param {Object} layer - layer
* @param {String} error - error
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:extent", function (e) {
* console.log(e.layer);
* })
*/
this.EXTENT_LAYER_EVENT = "layerswitcher:extent";
/**
* event triggered when a layer is edited
* @event layerswitcher:edit
* @defaultValue "layerswitcher:edit"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} options - layer options
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:edit", function (e) {
* console.log(e.layer);
* })
*/
this.EDIT_LAYER_EVENT = "layerswitcher:edit";
/**
* event triggered when a custom action is called
* @event layerswitcher:custom
* @defaultValue "layerswitcher:custom"
* @group Events
* @param {Object} type - event
* @param {String} action - label name
* @param {Object} layer - layer
* @param {Object} options - layer options
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:custom", function (e) {
* console.log(e.layer);
* })
*/
this.CUSTOM_LAYER_EVENT = "layerswitcher:custom";
/**
* event triggered when an header button is clicked
* @event layerswitcher:header:button
* @defaultValue "layerswitcher:header:button"
* @group Events
* @param {Object} type - event
* @param {String} action - label name
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:header:button", function (e) {
* console.log(e.action, e.target);
* })
*/
this.HEADER_BUTTON_EVENT = "layerswitcher:header:button";
/**
* event triggered when a layer opacity is changed
* @event layerswitcher:change:opacity
* @defaultValue "layerswitcher:change:opacity"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} opacity - new opacity value
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:change:opacity", function (e) {
* console.log(e.layer, e.opacity);
* })
*/
this.CHANGE_LAYER_OPACITY_EVENT = "layerswitcher:change:opacity";
/**
* event triggered when a layer visibility is changed
* @event layerswitcher:change:visibility
* @defaultValue "layerswitcher:change:visibility"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} visibility - new visibility value
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:change:visibility", function (e) {
* console.log(e.layer, e.visibility);
* })
*/
this.CHANGE_LAYER_VISIBILITY_EVENT = "layerswitcher:change:visibility";
/**
* event triggered when a layer grayscale is changed
* @event layerswitcher:change:grayscale
* @defaultValue "layerswitcher:change:grayscale"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} grayscale - new grayscale value
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:change:grayscale", function (e) {
* console.log(e.layer, e.grayscale);
* })
*/
this.CHANGE_LAYER_GRAYSCALE_EVENT = "layerswitcher:change:grayscale";
/**
* event triggered when a layer is locked or unlocked
* @event layerswitcher:change:locked
* @defaultValue "layerswitcher:change:locked"
* @group Events
* @param {Object} type - event
* @param {Object} layer - layer
* @param {Object} locked - new locked value
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:change:locked", function (e) {
* console.log(e.layer, e.locked);
* })
*/
this.CHANGE_LAYER_LOCKED_EVENT = "layerswitcher:change:locked";
/**
* event triggered when a layer is selected
* @event layerswitcher:change:selected
* @defaultValue "layerswitcher:change:selected"
* @group Events
* @param {Object} type - event
* @param {Object} layer - new selected layer
* @param {Object} previous - old selected layer (null if there was no selected layer before)
* @param {Object} target - instance LayerSwitcher
* @public
* @example
* LayerSwitcher.on("layerswitcher:change:selected", function (e) {
* console.log(e, e.layer, e.previous);
* })
*/
this.CHANGE_LAYER_SELECTED_EVENT = "layerswitcher:change:selected";
}
/**
* Create control main container (called by constructor)
*
* @returns {HTMLElement} 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(panelTitl