/* eslint
vars-on-top: 0,
newline-after-var: 0,
consistent-return:0,
no-throw-literal:0,
no-else-return:0,
space-before-function-paren: [2, "always"] */
import Overlay from 'ol/Overlay'
import { unByKey } from 'ol/Observable'
import * as Extent from 'ol/extent'
/**
* Module de gestion des label overlay sur les features
* des couches de données vectorielles
* @module labelFeature
*/
import { getCentroid } from '../tools/services/centroid'
const libNamespace = 'labelFeature'
const Constants = {
LABEL_CLASS: 'mapviewer_label',
}
class LabelFeature {
constructor (viewer, options) {
viewer.LABELFEATURE_LOADED = true
this.ctx = viewer
/* Contient tous les paramètres pour des layers attachés */
this._currentAttached = {}
this._tagsVisibles = true
Object.assign(this, options)
}
/**
* Permet d'utiliser des labels sur les features
*
* @param {Object} options
* @param {string} options.idLayer Identifiant du layer à utiliser
* @param {number} options.renderBuffer Ratio de l'extent de rendu par rapport à la vue
* @param {function} options.templateFunction Fonction appelée pour la génération du template
* @param {number} options.minZoom Zoom minimal pour afficher les labels
* @param {number} options.maxZoom Zoom maximal pour afficher les labels
*/
attachLayer ({
idLayer,
renderBuffer = 1,
templateFunction,
minZoom,
maxZoom,
}) {
// Si le layer est déjà attaché, on fait rien
if (this._currentAttached[idLayer]) {
return null
}
const layer = this.ctx.dataLayer.getDataLayers(idLayer)
const map = this.ctx.Map
// Si le layer existe, on l'attache
if (layer) {
const onEvent = () => {
this._clearLabels(idLayer, map)
this._generateLabels({
idLayer,
layer,
map,
renderBuffer,
templateFunction,
minZoom,
maxZoom,
})
}
const idEvCenter = map.on('moveend', onEvent)
this._currentAttached[idLayer] = {
name: idLayer,
events: [idEvCenter],
overlays: [],
visible: true,
onEvent,
map,
}
// On l'appel une première fois lors de l'attache
this._currentAttached[idLayer].onEvent()
}
}
isVisible (idLayer) {
return this._currentAttached[idLayer] && this._currentAttached[idLayer].visible
}
setVisible (idLayer, visibility) {
if (this._currentAttached[idLayer] && this._currentAttached[idLayer].visible !== visibility) {
this._currentAttached[idLayer].visible = visibility
this._currentAttached[idLayer].onEvent()
}
return this
}
isAllVisible () {
return this._tagsVisibles
}
setAllVisible (visibility) {
if (this._tagsVisibles !== visibility) {
this._tagsVisibles = visibility
Object.values(this._currentAttached).forEach((tag) => {
tag.onEvent()
})
}
}
/**
* Permet de désactiver les labels sur un layer
*
* @param {string} idLayer Identifiant du layer
*/
detachLayer (idLayer) {
// S'il y a bien un layer attaché
if (this._currentAttached[idLayer]) {
// Supprime tous les labels existants
this._clearLabels(idLayer, this._currentAttached[idLayer].map)
// Retire les events de génération de labels
this._currentAttached[idLayer].events.forEach(item => unByKey(item))
// Retire les settings du layer à détacher
delete this._currentAttached[idLayer]
}
}
/**
* Permet de supprimer tous les labels sur un layer
* @param {string} idLayer Identifiant du layer
* @param {ol.Map} map Carte OpenLayers contenant le layer
*/
_clearLabels (idLayer, map) {
// Supprime l'ensemble des overlay
// @TODO: Ne pas tout supprimer ?
this._currentAttached[idLayer].overlays.forEach(item => map.removeOverlay(item))
// Vide le tableau de référence
this._currentAttached[idLayer].overlays = []
}
/**
* Peremt de générer les labels sur le layers (appelé par l'event de la carte)
* @param {Object} options Options de création
* @param {string} options.idLayer Identifiant de création du layer
* @param {ol.layer} options.layer Layer sur lequel générer les labels
* @param {ol.Map} options.map Carte OpenLayers contenant le layer
* @param {number} options.renderBuffer Ratio de l'extent de rendu par rapport à la vue
* @param {function} options.templateFunction Fonction de génération du template pour une feature
* @param {number} options.minZoom Zoom minimal pour afficher les labels
* @param {number} options.maxZoom Zoom maximal pour afficher les labels
*/
_generateLabels ({
idLayer,
layer,
map,
renderBuffer,
templateFunction,
minZoom,
maxZoom,
}) {
const source = layer.getSource()
const view = map.getView()
const zoom = view.getZoom()
// Si la couche ne doit pas être affichée
if (!this._tagsVisibles || !this._currentAttached[idLayer].visible) {
return null
}
// Si on est hors des zoom d'affichage
if ((minZoom && minZoom > zoom) || (maxZoom && maxZoom < zoom)) {
return null
}
const extent = this._calculateExtentWithBufferRatio(
view.calculateExtent(map.getSize()),
renderBuffer,
)
source.forEachFeatureInExtent(extent, feature => {
// Récupère le centre de la feature
const position = getCentroid(feature)
// On s'assure que le centre est toujours dans l'extent
if (position && Extent.containsCoordinate(extent, position)) {
const template = templateFunction(feature, view.getResolution())
// Si on a un élément, on l'ajoute sur la carte
if (template) {
// Création de l'element avec le template
const element = document.createElement('div')
element.classList.add(Constants.LABEL_CLASS)
element.innerHTML = template
const overlay = new Overlay({
element,
position,
positioning: 'center-center',
stopEvent: false,
})
map.addOverlay(overlay)
this._currentAttached[idLayer].overlays.push(overlay)
}
}
})
}
/**
* Permet d'ajouter un buffer de type ratio sur une extent
* @param {*} extent Extent d'origine
* @param {*} buffer Buffer à appliquer
*/
_calculateExtentWithBufferRatio (extent, buffer) {
if (buffer !== 1) {
// Calcule le buffer à appliquer par rapport à l'extent
const widthExtent = extent[2] - extent[0]
const heightExtent = extent[3] - extent[1]
const widthBuffer = widthExtent * (buffer - 1) / 2
const heightBuffer = heightExtent * (buffer - 1) / 2
return [
extent[0] - widthBuffer,
extent[1] - heightBuffer,
extent[2] + widthBuffer,
extent[3] + heightBuffer,
]
}
return [...extent]
}
}
export default function extendCoreLib (options) {
return function patch (viewer) {
const functions = { }
functions[libNamespace] = new LabelFeature(viewer, options)
return Object.assign(viewer, functions)
}
}