/**
* @module nyc/ol/source/FilterAndSort
*/
import $ from 'jquery'
import {get as olProjGetProjection} from 'ol/proj'
import OlGeomLineString from 'ol/geom/LineString'
import AutoLoad from 'nyc/ol/source/AutoLoad'
import {get as olProjGet} from 'ol/proj'
const FEET_PRE_METER = 3.28084
/**
* @desc Class to filter and sort features
* @public
* @class
* @extends Autoload
*/
class FilterAndSort extends AutoLoad {
/**
* @desc Create an instance of FilterAndSort
* @public
* @constructor
* @param {olx.source.VectorOptions} options Constructor options
* @param {boolean} [metric=false] Use metric units
*/
constructor(options, metric) {
super(options)
/**
* @private
* @member {boolean}
*/
this.metric = metric
/**
* @private
* @member {Array<ol.Feature>}
*/
this.allFeatures = options.features || []
this.storeFeatures = this.storeFeatures.bind(this);
this.on('change:autoload-complete', this.storeFeatures)
}
/**
* @desc Filters the features of this source
* @public
* @method
* @param {Array<Array<module:nyc/ol/source/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 of the intersection of the applied filters
*/
filter(filters) {
const filteredFeatures = this.allFeatures.filter(feature => {
return filters.every(fltrs => {
return fltrs.some(flt => {
return $.inArray(feature.get(flt.property), flt.values) > -1
})
})
})
this.clear(true)
this.addFeatures(filteredFeatures)
return filteredFeatures
}
/**
* @private
* @method
* @param {ol.Coordinate} coordinate The coordinate from which to measure distance to features
* @param {Number=} count The number of features to return
* @return {Array<ol.Feature>} The features, each decorated with a getDistance function that returns a {@link module:nyc/ol/source/FilterAndSort~FilterAndSort.Distance} object
*/
doSort(coordinate, count) {
const features = this.getFeatures()
features.sort((f0, f1) => {
const dist0 = this.distance(coordinate, f0.getGeometry())
const dist1 = this.distance(coordinate, f1.getGeometry())
if (count === undefined) {
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
})
if (count > 0) {
return features.slice(0, count)
}
return features
}
/**
* @desc Sort features by distance from a coordinate
* @public
* @method
* @param {ol.Coordinate} coordinate The coordinate from which to measure distance to features
* @return {Array<ol.Feature>} The features, each decorated with a getDistance function that returns a {@link module:nyc/ol/source/FilterAndSort~FilterAndSort.Distance} object
*/
sort(coordinate) {
return this.doSort(coordinate)
}
/**
* @desc Get nearest n features from a coordinate
* @public
* @method
* @param {ol.Coordinate} coordinate The coordinate from which to measure distance to features
* @param {Number=} count The number of features to return
* @return {Array<ol.Feature>} The features
*/
nearest(coordinate, count) {
return this.doSort(coordinate, count)
}
/**
* @private
* @method
* @param {ol.Coordinate} coordinate The OpenLayers coordinate object
* @param {ol.geom.Geometry} geom The OpenLayers geometry object
* @return {module:nyc/FilterAndSort~FilterAndSort.Distance} Distance object
*/
distance(coordinate, geom) {
const line = new OlGeomLineString([coordinate, geom.getClosestPoint(coordinate)])
const projections = this.projections(this.getFormat())
let units = projections[1].getUnits()
if (projections[0] && projections[0].getUnits() !== 'degrees') {
line.transform(projections[1], projections[0])
units = projections[0].getUnits()
}
if (units !== 'ft' && !this.metric) {
return {distance: line.getLength() * FEET_PRE_METER, units: 'ft'}
}
return {distance: line.getLength(), units: units}
}
/**
* @private
* @method
* @param {ol.format.Feature} format OpenLayers format
* @return {Array<ol.proj.Projection>} Array of projections
*/
projections(format) {
const parentFormat = format ? format.parentFormat : null
let dataProj = 'EPSG:3857'
let featureProj = 'EPSG:3857'
if (parentFormat) {
dataProj = olProjGet(parentFormat.dataProjection)
featureProj = olProjGet(parentFormat.featureProjection || 'EPSG:3857')
} else if (format) {
dataProj = olProjGet(format.dataProjection)
featureProj = olProjGet(format.featureProjection || 'EPSG:3857')
}
return [olProjGetProjection(dataProj), olProjGetProjection(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 Argument object for {@link module:nyc/ol/source/FilterAndSort~FilterAndSort#filter}
* @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
* @return {module:nyc/FilterAndSort~FilterAndSort.Distance} Distance
*/
getDistance() {
return this.get('__distance')
}
}
export default FilterAndSort