Home Reference Source

src/ShowWMSFeatureInfo.js

import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import VectorSource from 'ol/source/Vector'
import { FeaturePopup } from './FeaturePopup'
import { MapEventInteraction } from './interactions/MapEventInteraction'

import { VectorLayer } from './layers/VectorLayer'

/**
 * @typedef {Object} ShowWMSFeatureInfoOptions
 * @property {StyleLike} [style='#defaultStyle']
 * @property {String} [seperator='<br>']
 * @property {String} [showClickable='layerAtPixel'] can be 'everywhere', 'layerAtPixel' or 'nowhere'
 * @property {boolean} [animated]
 */

export class ShowWMSFeatureInfo {
  /**
   * @param {ShowWMSFeatureInfoOptions} [options={}]
   */
  constructor (options = {}) {
    this.separator_ = options.hasOwnProperty('seperator') ? options.seperator : '<br>'
    this.styleConf_ = options.style || '#defaultStyle'

    this.animated_ = options.animated
    this.centerOnPopup_ = options.hasOwnProperty('centerOnPopup') ? options.centerOnPopup : true
    this.showClickable_ = options.hasOwnProperty('showClickable') ? options.showClickable : 'layerAtPixel'
    // this.centerIfNoData_ = this.centerOnPopup_ && options.hasOwnProperty('centerIfNoData')
    //   ? options.centerIfNoData : false

    this.centerOnPopupInitial_ = this.centerOnPopup_
  }

  handleClickEvent (e) {
    const map = this.getMap()

    const projection = map.getView().getProjection()

    const layers = this.layers_.filter(l => l.getVisible())

    const shouldShow = map.forEachLayerAtPixel(e.mapEvent.pixel, layer => layers.indexOf(layer) > -1) &&
      !map.forEachFeatureAtPixel(e.mapEvent.pixel, FeaturePopup.canDisplay)

    if (shouldShow) {
      const coordinate = e.mapEvent.coordinate
      this.utilitySource_.clear()
      let feature
      for (const layer of layers) {
        layer.getSource().getFeatureInfo(coordinate, map.getView().getResolution(), projection)
          .then(data => this.handleData(feature, coordinate, layer, data))
      }
    }
  }

  handleData (feature, coordinate, layer, data) {
    if (data !== '') {
      const featurePopup = this.getMap().get('featurePopup')
      if (!feature) {
        this.utilitySource_.clear()
        feature = new Feature({
          geometry: new Point(coordinate),
          description: data,
          style: this.style_
        })
        this.getMap().get('styling').manageFeature(feature)
        this.utilitySource_.addFeature(feature)

        featurePopup.setFeature(feature, layer, this.style_, coordinate)
        featurePopup.setVisible(true)

        this.setPointVisible(true)
        if (this.centerOnPopup_) {
          featurePopup.centerMapOnPopup(this.animated_)
        }
        featurePopup.once('change:visible', () => this.setPointVisible(false))
      } else {
        feature.set('description', feature.get('description') + this.separator_ + data)
      }
      layer.on('change:visible', () => {
        featurePopup.setVisible(false)
      })
    }
  }

  layerQueryable (layer) {
    return this.layers_.some(l => {
      return l.getVisible() &&
        this.layers_.indexOf(layer) > -1 &&
        layer.getSource().getQueryable()
    })
  }

  clickableFilter (e) {
    if (this.showClickable_ === 'everywhere') {
      return this.layers_.some(l => l.getVisible())
    } else if (this.showClickable_ === 'layerAtPixel') {
      return this.getMap().forEachLayerAtPixel(e.pixel, layer => this.layerQueryable(layer))
    } else {
      return false
    }
  }

  setMap (map) {
    const onMapChangeMobile = () => {
      if (map.get('mobile')) {
        this.centerOnPopup_ = false
      } else {
        this.centerOnPopup_ = this.centerOnPopupInitial_
      }
    }

    if (this.getMap()) {
      this.getMap().un('change:mobile', onMapChangeMobile)
    }

    this.map_ = map
    if (map) {
      this.style_ = map.get('styling').getStyle(this.styleConf_)

      this.utilitySource_ = new VectorSource()
      this.utilityLayer_ = new VectorLayer({
        visible: false,
        source: this.utilitySource_
      })
      map.addLayer(this.utilityLayer_)

      this.layers_ = []

      const interaction = new MapEventInteraction({ type: 'singleclick' })
      interaction.on('mapevent', e => this.handleClickEvent(e))

      map.addDefaultInteraction('singleclick', interaction)

      map.get('clickableInteraction').addFilter(e => this.clickableFilter(e))

      onMapChangeMobile()
      map.on('change:mobile', onMapChangeMobile)
    }
  }

  getPointVisible () {
    return this.utilityLayer_.getVisible()
  }

  setPointVisible (visible) {
    this.utilityLayer_.setVisible(visible)
  }

  getMap () {
    return this.map_
  }

  addLayer (layer) {
    this.layers_.push(layer)
  }
}