/* eslint vars-on-top: 0, newline-after-var: 0, consistent-return:0, no-throw-literal:0 */
import { Group as LayerGroup } from 'ol/layer'
import { getZoomForScale } from '../services/scales-service'
/**
* Module de gestion générique des couches et groupes
* @module commonLayer
*/
const libNamespace = 'commonLayer'
/** Les groupes sont ajoutés au */
const GROUP_LAYER_TYPE = 'group'
/** Service de gestion générique des couches */
class CommonLayer {
/** propriétés additionelles des calques */
propertiesName = {
/** Propriété définissant l'id du layer */
ID_LAYER: '__gmapv_id',
/** Propriété définissant le type du layer */
TYPE_LAYER: '__gmapv_type',
/** Propriété pour une ihm du layer */
UI_LAYER: '__gmapv_ui',
/** Propriété pour le groupe du layer */
GROUP_LAYER: '__gmapv_group',
/** on l'affiche dans le controle layerManager? */
DISPLAY_IN_LAYER_SWITCHER: 'displayInLayerSwitcher',
/** considéré comme "baseLayer" dans layerManager, un seul peut être afficher a la fois (dans un même groupe) */
BASE_LAYER: 'baseLayer',
/** pour background, si plusieurs background affichés, determine la projection prioritataire a utiliser */
PROJECTION_PRIORITY: 'projectionPriority',
// propriété pour sauvegarder la projection courante de la feature
GEOMETRY_PROJECTION: '__gmapv_projection',
}
layerList = []
mapModesAvailable = {}
currentMapMode = null
defaultMapMode = null
/** instance du viewer
* @type {import("../core/core").default}
*/
ctx = null
constructor (viewer, options) {
viewer.COMMONLAYER_LOADED = true
this.ctx = viewer
Object.assign(this, options)
// actions de maintenance lors de la suppression de calque
this.ctx.Map.getLayers().on('remove', (event) => {
const idLayer = event.element.get(this.propertiesName.ID_LAYER)
if (idLayer) {
// retire le calque des ressources utilisant un token (si il y en a)
this.ctx.tokenManagerPool.removeResource('layer', idLayer)
}
})
}
/**
* renvois les infos de type et d'id du calque
* @param {ol.layer} layer
* @returns {{type:string, id:string}} Type et Id du calque
*/
getLayerInfos (layer) {
return {
type: layer.get(this.propertiesName.TYPE_LAYER),
id: layer.get(this.propertiesName.ID_LAYER),
}
}
/**
* Récupère la liste des layers existant
*
* @param {String} type Type du layer
* @returns {Array<ol.Layer>} Liste de layers
*/
getLayers (type) {
if (type) {
return this.layerList.filter(layer =>
layer.get(this.propertiesName.TYPE_LAYER) === type)
}
return this.layerList.slice()
}
/**
* Récupère un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
* @returns {ol.layer.Layer} Layer
*/
getLayer (id, type) {
return this.getLayers(type).find(layer =>
layer.get(this.propertiesName.ID_LAYER) === id)
}
/**
* Permet de récupérer la source d'un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Layer
* @return {ol.source.Source} Source du layer
*/
getSource (id, type) {
const layer = this.getLayer(id, type)
return layer && layer.getSource && layer.getSource()
}
/**
* Retourne la liste des identifiants des layers
*
* @param {String} type Type du layer
* @returns {Array<String>} Liste des identifiants des layers
*/
getLayersId (type) {
return this.getLayers(type).map(layer =>
layer.get(this.propertiesName.ID_LAYER))
}
/**
* Permet de savoir si un layer existe
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
* @returns {Boolean} True si le layer existe
*/
layerExist (id, type) {
return this.getLayersId(type).includes(id)
}
/**
* Ajout un layer sur la carte
*
* @param {ol.Layer} layer Layer à ajouter dans la carte
* @param {String} type Type du layer
* @param {Object} ui élement pour l'IHM si on utilise la layervisibilitybar
* @param {string} ui.title tooltip du calque
* @param {Element|string} ui.html contenu html du bouton a afficher
* @param {string} ui.icon icone si html non saisie (utilisé comme <i class='icon'/>)
*/
addLayer (layer, type, ui) {
const layerName = layer.get(this.propertiesName.ID_LAYER)
if (this.layerExist(layerName, type)) {
throw new Error(`Layer ${layerName} already exists`)
}
// Ajoute le type du layer
layer.set(this.propertiesName.TYPE_LAYER, type)
if (ui) { layer.set(this.propertiesName.UI_LAYER, ui) }
// Ajoute le layer à la carte et à la liste du module
const idGroup = layer.get(this.propertiesName.GROUP_LAYER)
const group = this.getGroup(idGroup)
this.layerList.push(layer)
if (group) {
// si on ajoute dans un groupe tout en ayant le layermanager actif et "baseLayer"
// on masque les autres calques du groupe (bugfix pour ol-ext)
if (layer.getVisible() && layer.get(this.propertiesName.BASE_LAYER)) {
group.getLayers().forEach(gLayer => {
if (gLayer.get(this.propertiesName.BASE_LAYER)) {
gLayer.setVisible(false)
}
})
}
group.getLayers().push(layer)
} else {
this.ctx.Map.addLayer(layer)
}
}
/**
* Retire un layer de la carte
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
* @fires removeLayer:id Lancé avant la suppression
* @fires removedLayer:id Lancé après la suppression
*/
removeLayer (id, type) {
if (this.layerExist(id, type)) {
this.ctx.dispatchEvent(`removeLayer:${id}`)
const layer = this.getLayer(id, type)
this.layerList.splice(this.layerList.indexOf(layer), 1)
const idGroup = layer.get(this.propertiesName.GROUP_LAYER)
const group = this.getGroup(idGroup)
if (group) {
group.getLayers().remove(layer)
} else {
this.ctx.Map.removeLayer(layer)
}
//
this.ctx.dispatchEvent(`removedLayer:${id}`)
}
}
/**
* Permet de masquer un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
*/
hideLayer (id, type) {
if (this.layerExist(id, type)) {
this.getLayer(id, type).setVisible(false)
}
}
/**
* Permet d'afficher un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
*/
showLayer (id, type) {
if (this.layerExist(id, type)) {
const layer = this.getLayer(id, type)
const idGroup = layer.get(this.propertiesName.GROUP_LAYER)
const group = this.getGroup(idGroup)
if (group && layer.get(this.propertiesName.BASE_LAYER)) {
// si on ajoute dans un groupe tout en ayant le layermanager actif et "baseLayer"
// on masque les autres calques du groupe (bugfix pour ol-ext)
group.getLayers().forEach(gLayer => {
if (gLayer.get(this.propertiesName.BASE_LAYER)) {
gLayer.setVisible(false)
}
})
}
layer.setVisible(true)
}
}
/**
* Permet de remonter d'un niveau un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
* @returns {Boolean} Retourne true si le layer n'est pas au plus haut
*/
upLayer (id, type) {
if (this.layerExist(id, type)) {
const mapLayers = this.ctx.Map.getLayers()
const index = mapLayers.getArray().indexOf(this.getLayer(id, type))
// si il n'est pas déja au plus haut
if (index + 1 < mapLayers.getLength()) {
mapLayers.insertAt(index + 1, mapLayers.removeAt(index))
return index + 1 !== mapLayers.length
}
}
return false
}
/**
* Permet de descendre d'un niveau un layer
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
* @return {Boolean} Retourne True si le layer n'est pas au plus bas
*/
downLayer (id, type) {
if (this.layerExist(id, type)) {
const mapLayers = this.ctx.Map.getLayers()
const index = mapLayers.getArray().indexOf(this.getLayer(id, type))
if (index > 0) {
mapLayers.insertAt(index - 1, mapLayers.removeAt(index))
return index - 1 !== 0
}
}
return false
}
/**
* Permet de notifier au layer qu'il à changé
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
*/
layerChanged (id, type) {
if (this.layerExist(id, type)) {
this.getLayer(id, type).changed()
}
}
/**
* Force un nouveau rendu du layer sur la carte
* Refreshes the source. The source will be cleared, and data from the server will be reloaded.
*
* @param {String|Number} id Identifiant du layer
* @param {String} type Type du layer
*/
refreshLayer (id, type) {
if (this.layerExist(id, type)) {
this.getLayer(id, type).getSource().refresh()
}
}
/**
* Permet de connaitre la visibilité d'un layer (en fonction de min/max zoom, propriété "visible" & group "visible")
*
* @param {String|Number} id Identifiant du layer
* @return {Boolean} True si visible
*/
isVisible (id, type) {
if (this.layerExist(id, type)) {
return this.getLayer(id, type).isVisible()
}
}
/**
* ajoute un groupe a la carte
* @param {Object} options Paramètres compatibles ol.layer.group
*/
addGroup (idGroup, options) {
const group = new LayerGroup({
[this.propertiesName.ID_LAYER]: idGroup,
...options,
})
// Ajoute le type du layer car un groupe est ajouté aux layers
// group.set(this.propertiesName.TYPE_LAYER, GROUP_LAYER_TYPE)
this.addLayer(group, GROUP_LAYER_TYPE)
}
/** Renvoit tout les groupes présents dans la carte */
getGroups () {
return this.getLayersId(GROUP_LAYER_TYPE)
}
/** Renvoit un groupe par son id
* @param {string} idGroup Id du groupe
*/
getGroup (idGroup) {
return this.getLayer(idGroup, GROUP_LAYER_TYPE)
}
/**
* Est ce que l'entrée est un calque ou un groupe?
* @param {ol.layer|ol.layer.group} groupOrLayer
* @returns {boolean} l'entrée est un calque ou un groupe ?
*/
isGroup (groupOrLayer) {
return groupOrLayer.get(this.propertiesName.TYPE_LAYER) === GROUP_LAYER_TYPE
}
/**
* renvoit le groupe d'un calque
*
* @param {ol.layer} layer Calque OpenLayer
* @returns group
*/
getLayerGroup (layer) {
// const layer = this.getLayer(id, type)
if (!layer) { return null }
const idGroup = layer.get(this.propertiesName.GROUP_LAYER)
const group = this.getGroup(idGroup)
return group || null
}
/**
* Mets à jours les propriétés OpenLayer ou Karteis Mapviewer d'un calque
* @param {ol.layer} layer Calque OpenLayer
* @param {Object} options Options possibles pour les calques
*/
updateCommonProperties (layer, options) {
if (options.visible !== undefined) {
layer.setVisible(options.visible)
}
if (options.opacity !== undefined) {
layer.setOpacity(options.opacity)
}
if (options.minZoom !== undefined) {
layer.setMinZoom(options.minZoom)
}
if (options.maxZoom !== undefined) {
layer.setMaxZoom(options.maxZoom)
}
// les notions de zoom & scale/résolutions sont inversées (max zoom = proche du sol)
if (options.minScale !== undefined) {
layer.setMaxZoom(getZoomForScale(this.ctx.Map.getView(), options.minScale))
}
if (options.maxScale !== undefined) {
layer.setMinZoom(getZoomForScale(this.ctx.Map.getView(), options.maxScale))
}
if (options.idGroup !== undefined) {
layer.set(this.propertiesName.GROUP_LAYER, options.idGroup)
}
if (options.baseLayer !== undefined) {
layer.set(this.propertiesName.BASE_LAYER, options.baseLayer)
}
if (options.displayInLayerSwitcher !== undefined) {
layer.set(this.propertiesName.DISPLAY_IN_LAYER_SWITCHER, options.displayInLayerSwitcher)
}
if (options.projectionPriority !== undefined) {
layer.set(this.propertiesName.PROJECTION_PRIORITY, options.projectionPriority)
}
if (options.ui !== undefined) {
layer.set(this.propertiesName.UI_LAYER, options.ui)
}
}
/**
* Ajoute un mode de carte
* @param {Object} options
* @param {string} options.name nom
* @param {Array<ol.interaction>} options.interactions
* @param {Array<ol.control>} options.controls
* @param {function} options.onactive
* @param {function} options.oninactive
* @param {Boolean} options.isDefault
*/
createMapMode ({
name,
interactions = [],
controls = [],
onactive = () => {},
oninactive = () => {},
isDefault = false,
} = {}) {
if (this.mapModesAvailable[name]) {
throw new Error(`Mode ${name} already exists`)
}
this.mapModesAvailable[name] = {
interactions: Array.isArray(interactions) ? interactions : [interactions],
controls: Array.isArray(controls) ? controls : [controls],
onactive,
oninactive,
}
// S'il doit-être utilisé en tant que mode par défaut
if (isDefault) {
this.defaultMapMode = name
this.setMapMode(this.defaultMapMode)
}
}
removeMapMode (name) {
if (this.mapModesAvailable[name]) {
// Si c'est le mode actf, le désactive avant tout
if (this.currentMapMode.name === name) {
this.disableCurrentMapMode()
}
this.mapModesAvailable[name] = undefined
}
}
upsertMapMode ({ name, ...options }) {
if (!this.mapModesAvailable[name]) {
return this.createMapMode({ name, ...options })
}
// S'il s'agit du mode actif, on le relance en même temps
if (this.currentMapMode.name === name) {
this.disableCurrentMapMode({ silent: true })
this.mapModesAvailable[name] = { name, ...options }
this.setMapMode(name, { silent: true })
} else {
this.mapModesAvailable[name] = { name, ...options }
}
}
disableCurrentMapMode ({ silent } = {}) {
if (this.currentMapMode) {
// Supprime les interactions
this.currentMapMode.interactions.forEach(interaction => {
// ndhe: avec la nouvelle version d'ol-ext une interraction peut activer/desactiver des contrôles lorsqu'on l'active/desactive.
// du coup on la desactive avant de la supprimer
interaction.setActive(false)
this.ctx.Map.removeInteraction(interaction)
})
this.currentMapMode.controls.forEach(control => {
this.ctx.Map.removeControl(control)
})
// Lance les traitements lors de la désactivation du mode
this.currentMapMode.oninactive()
this.currentMapMode = null
if (!silent) {
this.ctx.dispatchEvent('change:mapmode', { name: null, toolId: null })
}
}
}
applyMapMode ({
name,
interactions = [],
controls = [],
onactive = () => {},
oninactive = () => {},
silent,
toolId,
}) {
// Désactive l'ancien mode
this.disableCurrentMapMode({ silent: true })
// Initialise le nouveau mode
this.currentMapMode = { name, interactions, controls, onactive, oninactive, toolId }
interactions.forEach(interaction => {
// ndhe: avec la nouvelle version d'ol-ext une interraction peut activer/desactiver des contrôles lorsqu'on l'active/desactive.
// du coup on l'active avant de la supprimer
interaction.setActive(true)
this.ctx.Map.addInteraction(interaction)
})
this.currentMapMode.controls.forEach(control => {
this.ctx.Map.addControl(control)
})
// Lance les traitements lors de la désactivation du mode
onactive()
// Envoie un event hors de gmapv
if (!silent) {
this.ctx.dispatchEvent('change:mapmode', { name, toolId })
}
}
setMapMode (name = this.defaultMapMode, { silent, toolId } = {}) {
// Pas de nouveau mode de carte
if (!name) {
return this.disableCurrentMapMode({ silent })
}
// Mode non disponible
if (!this.mapModesAvailable[name]) {
throw new Error(`Mode ${name} dosen't exists`)
}
// Applique le nouveau mode sur la carte
if (!this.currentMapMode || this.currentMapMode.name !== name || (toolId && this.currentMapMode.toolId !== toolId)) {
this.applyMapMode({ name, ...this.mapModesAvailable[name], silent, toolId })
}
}
getCurrentMapMode () {
return this.currentMapMode
}
getCurrentToolId () {
return this.currentMapMode && this.currentMapMode.toolId
}
isCurrentMapMode (name) {
return this.currentMapMode && this.currentMapMode.name === name
}
}
// Permet d'etendre le module
export default function extendCoreLib (options) {
return function patch (viewer) {
const functions = { }
functions[libNamespace] = new CommonLayer(viewer, options)
return Object.assign(viewer, functions)
}
}