Home Reference Source

src/sources/ArcGISRESTFeatureSource.js

import $ from 'jquery'
import EsriJSON from 'ol/format/EsriJSON'

import VectorSource from 'ol/source/Vector'
import { get as getProj, transformExtent } from 'ol/proj'
import { Debug } from '../Debug'
import { take } from '../utilitiesObject'

/**
 * Please note that a 'BBOX' or 'TILE' loadingStrategy is required
 * @typedef {module:ol/source/Vector~Options} ArcGISRESTFeatureSourceOptions
 * @property {object} [params] the params to be passed to the ArcGIS REST Feature Service. The params f, returnGeometry,
 *    geometry, geometryType, inSR and outSr can not be set as they are filled automatically. The SR parameters will
 *    be set to the projection of the source if one is given. geometry and geometryType will always be an
 *    'esriGeometryEnvelope'. f will be 'json' and returnGeometry 'true'.
 *    The parameters spatialRel and outFields can be altered and default to 'esriSpatialRelIntersects' and '*'.
 * @property {string} [dataType=json] can be 'json' or 'jsonp'
 */

export class ArcGISRESTFeatureSource extends VectorSource {
  constructor (options) {
    if (options.loadingStrategyType === 'ALL') {
      Debug.error('ArcRESTFeatureSource does not support loading strategy "ALL". Please use BBOX or TILE')
    }

    const url = take(options, 'url')
    const params = take(options, 'params')
    // const geometrySR = take(options, 'geometrySR')

    options.loader = (...args) => this.loader(...args)

    super(options)

    this.url_ = url
    this.params_ = params || {}
    this.projection_ = options.projection ? getProj(options.projection) : undefined
    this.format_ = new EsriJSON()
    this.dataType_ = options.dataType || 'json'
  }

  /**
   * @param {module:ol/extent~Extent} extent
   * @param {number} resolution
   * @param {module:ol/proj/Projection} projection
   */
  loader (extent, resolution, projection) {
    let sr
    if (this.projection_) {
      extent = transformExtent(extent, projection, this.projection_)
      sr = parseInt(this.projection_.getCode().split(':')[1])
    }

    const geometry = {
      xmin: extent[0],
      ymin: extent[1],
      xmax: extent[2],
      ymax: extent[3]
    }
    if (this.projection_) {
      geometry.spatialReference = {
        wkid: sr
      }
    }

    const params = Object.assign({
      spatialRel: 'esriSpatialRelIntersects',
      outFields: '*'
    }, this.params_, {
      f: 'json',
      returnGeometry: 'true',
      geometry: JSON.stringify(geometry),
      geometryType: 'esriGeometryEnvelope'
    })

    if (this.projection_) {
      params.inSR = sr
      params.outSR = sr
    }

    const url = this.url_.clone()
    for (const key in params) {
      if (params.hasOwnProperty(key)) {
        url.addParam(`${key}=${params[key]}`)
      }
    }

    $.ajax({
      url: url.finalize(),
      dataType: this.dataType_,
      success: response => {
        if (response.error) {
          Debug.error(response.error.message + '\n' +
            response.error.details.join('\n'))
        } else {
          // dataProjection will be read from document
          const features = this.format_.readFeatures(response, {
            dataProjection: this.projection_,
            featureProjection: projection
          })
          if (features.length > 0) {
            this.addFeatures(features)
          }
        }
      }
    })
  }
}