Source: labelFeature/label-feature.js

/* 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)
  }
}