Source: ol/source/FilterAndSort.js

/**
 * @module nyc/ol/source/FilteringAndSorting
 */

import $ from 'jquery'

import OlGeomLineString from 'ol/geom/linestring'
import OlProjProjection from 'ol/proj/projection'

import nyc from 'nyc'
import AutoLoad from 'nyc/ol/source/AutoLoad'

/**
 * @desc Class to auto load all features from a URL
 * @public
 * @class
 * @extends ol.source.Vector
 * @see http://openlayers.org/en/latest/apidoc/ol.source.Vector.html
 */
class FilterAndSort extends AutoLoad {
  /**
   * @desc Create an instance of FilterAndSort
   * @public
   * @constructor
   * @param {olx.source.VectorOptions} options Constructor optionss
   */
  constructor(options) {
    super(options)
    /**
     * @private
     * @member {Array<ol.Feature>}
     */
    this.allFeatures = options.features || null
    this.on('change:autoload-complete', $.proxy(this.storeFeatures, this))
  }
  /**
   * @desc Filters the features of this source
   * @public
   * @method
   * @param {Array<module:nyc/FilterAndSort~FilterAndSort.Filter>} filters Used to filter features by attributes
   * @return {Array<ol.Feature>} An array of features contained in this source that are the result is the intersection of the applied filters
   */
  filter(filters) {
    const filteredFeatures = []
    const filtered = {}
    this.allFeatures.forEach(feature => {
  		let incl = true
  		filters.every(filter => {
  			incl = $.inArray(feature.get(filter.property), filter.values) > -1
  			return incl
  		})
  		if (incl) {
				filtered[feature.getId()] = true
				filteredFeatures.push(feature)
  		}
  	})
  	this.clear(true)
  	this.addFeatures(filteredFeatures)
    return filteredFeatures
  }
  /**
   * @desc Sort features by distance from a coordinate
   * @public
   * @method
   * @return {Array<ol.Feature>} features
   */
  sort(coordinate) {
    const features = this.getFeatures()
    features.sort((f0, f1) => {
      const geom0 = f0.getGeometry()
      const geom1 = f1.getGeometry()
      const dist0 = this.distance(coordinate, f0.getGeometry())
      const dist1 = this.distance(coordinate, f1.getGeometry())
      f0.set('__distance', dist0)
      f1.set('__distance', dist1)
      $.extend(f0, FilterAndSort.DistanceDecoration)
      $.extend(f1, FilterAndSort.DistanceDecoration)
      if (dist0.distance < dist1.distance) {
        return -1
      }else if (dist0.distance > dist1.distance) {
        return 1
      }
			return 0
    })
    return features
  }
  /**
   * @private
   * @method
   * @param {ol.Coordinate} coordinate 
   * @param {ol.geom.Geometry} geom 
   * @returns {module:nyc/FilterAndSort~FilterAndSort.Distance}
   */
  distance(coordinate, geom) {
    const line = new OlGeomLineString([coordinate, geom.getClosestPoint(coordinate)])
    const projections = this.projections(this.getFormat())
    if (projections[0]) {
      line.transform(projections[1], projections[0])
    }
    return {
      distance: line.getLength(),
      units: projections[0] ? projections[0].getUnits() : undefined
    }
  }
  /**
   * @private
   * @method
   * @param {ol.format.Feature} format 
   * @returns {Array<ol.proj.Projection>}
   */
  projections(format) {
    const parentFormat = format ? format.parentFormat : null
    let dataProj
    let featureProj
    if (parentFormat) {
      dataProj = parentFormat.defaultDataProjection
      featureProj = parentFormat.defaultFeatureProjection || 'EPSG:3857'
    } else if (format) {
      dataProj = format.defaultDataProjection
      featureProj = format.defaultFeatureProjection || 'EPSG:3857'
    }
    if (dataProj) {
      dataProj = dataProj.getCode ? dataProj : new OlProjProjection({code: dataProj})
    }
    return [dataProj, featureProj]
  }
  /**
   * @private
   * @method
   */
  storeFeatures() {
    this.allFeatures = this.getFeatures()
  }
}

/**
 * @public
 * @typedef {Object}
 * @property {number} distance The distance
 * @property {string} units The distance units
 */
FilterAndSort.Distance

/**
 * @desc Object to use for filtering the features of an instance of {@see FilterAndSort}
 * @public
 * @typedef {Object}
 * @property {string} property The property name on which to filter features
 * @property {Array<string>} values The values used to filter features 
 */
FilterAndSort.Filter

/**
 * @desc Mixin for features
 * @private
 * @typedef {Object<string, function>}
 */
FilterAndSort.DistanceDecoration = {
  /**
   * @desc Mixin for features
   * @private
   * @method 
   * @returns {module:nyc/FilterAndSort~FilterAndSort.Distance}
   */
  getDistance() {
    return this.get('__distance')
  }
}

export default FilterAndSort