Home Reference Source

src/sources/ImageWMSSource.js

import $ from 'jquery'
import { concat, difference, intersection } from 'lodash/array'
import { isEmpty } from 'lodash/lang'
import ImageWMS from 'ol/source/ImageWMS'
import TileWMS from 'ol/source/TileWMS'

import { asyncImageLoad, mixin } from '../utilities'
import { Debug } from '../Debug'

/**
 * @typedef {object} WMSFeatureInfoOptions
 * @property {object.<string, string>} [params] the params to be used with the GetFeatureInfo call.
 *    Needs to include INFO_FORMAT.
 *    If the layer does not use the buttons options than needs to include QUERY_LAYERS.
 * @property {boolean} [checkable=false] If the layer does not use the buttons options, this options specifies if an
 *    extra button on the layer button appears to toggle the feature info.
 * @property {boolean} [checked=true] If the layer does not use the buttons options and checkable is true, this option
 *    specifies if the feature info button appears activated or not.
 * @property {[number, number]} [iframe=false] load in iframe of this size
 */

export class WMSMixin {
  /**
   * @param {object} options
   * @param {WMSFeatureInfoOptions} options.featureInfo
   */
  initialize (options) {
    this.featureInfo_ = options.featureInfo !== undefined
    this.featureInfoParams_ = {
      QUERY_LAYERS: []
    }

    if (this.featureInfo_) {
      this.featureInfoIframe = options.featureInfo.iframe
      Object.assign(this.featureInfoParams_, options.featureInfo.params)
      this.featureInfoMutators_ = options.featureInfo.mutators
    }

    if (!this.getParams().LAYERS) {
      this.updateParams({
        LAYERS: []
      })
    }

    this.set('legends', options.legends || false)
  }

  getQueryable () {
    return this.featureInfo_ && this.featureInfoParams_.QUERY_LAYERS &&
      this.featureInfoParams_.QUERY_LAYERS.length > 0 &&
      intersection(this.featureInfoParams_.QUERY_LAYERS, this.getParams().LAYERS).length > 0
  }

  getFeatureInfoMutators () {
    return this.featureInfoMutators_ || []
  }

  hasFeatureInfo () {
    return this.featureInfo_
  }

  activateLayers (layers) {
    this.updateParams({
      LAYERS: concat(this.getParams().LAYERS, layers)
    })
    this.dispatchEvent('change:layers')
  }

  deactivateLayers (layers) {
    this.updateParams({
      LAYERS: difference(this.getParams().LAYERS, layers)
    })
    this.dispatchEvent('change:layers')
  }

  areLayersActive (layers) {
    return isEmpty(difference(layers, this.getParams().LAYERS))
  }

  anyLayerActive () {
    return !isEmpty(this.getParams().LAYERS)
  }

  activateQueryLayers (layers) {
    this.updateFeatureInfoParams({
      QUERY_LAYERS: concat(this.getFeatureInfoParams().QUERY_LAYERS, layers)
    })
    this.dispatchEvent('change:queryLayers')
  }

  deactivateQueryLayers (layers) {
    this.updateFeatureInfoParams({
      QUERY_LAYERS: difference(this.getFeatureInfoParams().QUERY_LAYERS, layers)
    })
    this.dispatchEvent('change:queryLayers')
  }

  areQueryLayersActive (layers) {
    return isEmpty(difference(layers, this.getFeatureInfoParams().QUERY_LAYERS))
  }

  getFeatureInfoParams () {
    return this.featureInfoParams_
  }

  updateFeatureInfoParams (newParams) {
    Object.assign(this.featureInfoParams_, newParams)
    this.changed()
  }

  getFeatureInfo (coordinate, resolution, projection) {
    return new Promise((resolve, reject) => {
      const params = Object.assign({}, this.featureInfoParams_)
      if (!params.QUERY_LAYERS) {
        resolve(undefined)
      }
      params.QUERY_LAYERS = intersection(params.QUERY_LAYERS, this.getParams().LAYERS)
      if (params.QUERY_LAYERS.length === 0) {
        resolve(undefined)
      } else {
        const gfiExt = this.getGetFeatureInfoUrl(coordinate, resolution, projection, params).slice(1)
        const url = this.originalUrlObject.extend(gfiExt).finalize()
        if (this.featureInfoIframe) {
          resolve($('<iframe>')
            .width(this.featureInfoIframe[0])
            .height(this.featureInfoIframe[1])
            .attr('src', url))
        } else {
          $.ajax({
            url,
            success: resolve,
            error: reject,
            dataType: 'text'
          })
        }
      }
    })
  }

  // toggleArrayEntries_ (obj, prop, names, toggle) {
  //   let arr = obj[prop] || []
  //   if (toggle) {
  //     // the call of toggleWMSLayers in TemplatePrintControl causes an polyfill error in
  //     // internet explorer / microsoft edge related to Symbol.iterator which is used for
  //     // the for-of loop
  //     names.forEach(name => {
  //       if (arr.indexOf(name) < 0) {
  //         arr.push(name)
  //       }
  //     })
  //     // for (let name of names) {
  //     //   if (arr.indexOf(name) < 0) {
  //     //     arr.push(name)
  //     //   }
  //     // }
  //   } else {
  //     for (let name of names) {
  //       let index = arr.indexOf(name)
  //       if (index >= 0) {
  //         arr.splice(index, 1)
  //       }
  //     }
  //   }
  //   obj[prop] = arr
  //   return obj
  // }
  //
  // toggleWMSLayers (names, toggle) {
  //   this.updateParams(this.toggleArrayEntries_(this.getParams(), 'LAYERS', names, toggle))
  // }
  //
  // toggleWMSQueryLayers (names, toggle) {
  //   this.updateFeatureInfoParams(this.toggleArrayEntries_(this.featureInfoParams_, 'QUERY_LAYERS', names, toggle))
  // }
  //
  // arrayContainsAll (arr, contains) {
  //   return contains.every(needle => arr.indexOf(needle) >= 0)
  // }
  //
  // getWMSLayersVisible (names) {
  //   return this.arrayContainsAll(this.getParams().LAYERS || [], names)
  // }
  //
  // getWMSQueryLayersVisible (names) {
  //   return this.arrayContainsAll(this.featureInfoParams_.QUERY_LAYERS || [], names)
  // }

  // TODO: remove with ol version 6
  getGetLegendGraphicUrl (resolution, layer) {
    const params = Object.assign({}, this.getParams())

    const baseParams = {
      SERVICE: 'WMS',
      VERSION: '1.3.0',
      REQUEST: 'GetLegendGraphic',
      FORMAT: 'image/png',
      LAYER: layer
    }

    if (resolution !== undefined) {
      const mpu = this.getProjection() ? this.getProjection().getMetersPerUnit() : 1
      const dpi = 25.4 / 0.28
      const inchesPerMeter = 39.37
      baseParams.SCALE = resolution * mpu * inchesPerMeter * dpi
    }

    Object.assign(baseParams, params)

    const url = this.originalUrlObject.clone()

    // Skip any null or undefined parameter values
    Object.keys(baseParams).forEach(k => {
      if (baseParams[k] !== null && baseParams[k] !== undefined) {
        url.addParam(k + '=' + encodeURIComponent(baseParams[k]))
      }
    })

    return url.finalize()
  }
}

/**
 * A wms source config.
 * @typedef {SourceConfig} WMSSSourceConfig
 * @property {object} params required. needs to contain a `LAYERS` parameter. For other
 *    parameters see: http://openlayers.org/en/latest/apidoc/ol.source.ImageWMS.html -> Constructor options -> params
 * @property {WMSFeatureInfoOptions} featureInfo
 */

export class ImageWMSSource extends mixin(ImageWMS, WMSMixin) {
  constructor (options) {
    const originalUrl = options.url
    options.url = '_' // dummy value that gets sliced out
    options.imageLoadFunction = (image, src) => {
      asyncImageLoad(image.getImage(), this.originalUrlObject.extend(src.slice(1)))
        .catch(err => Debug.error(err))
    }
    super(options)
    this.originalUrlObject = originalUrl
  }
}

export class TileWMSSource extends mixin(TileWMS, WMSMixin) {
  constructor (options) {
    const origUrl = options.url
    options.url = '_' // dummy value that gets sliced out
    options.tileLoadFunction = (tile, src) => {
      asyncImageLoad(tile.getImage(), this.originalUrlObject.extend(src.slice(1)))
        .catch(err => Debug.error(err))
    }
    super(options)
    this.originalUrlObject = origUrl
  }
}