import Interaction from 'ol/interaction/Interaction'
import Collection from 'ol/Collection'
import { unByKey } from 'ol/Observable'
import compact from 'lodash/compact'
/**
* Evalue une condition (prédicat) pour une valeur donnée
* Renvoie true ou false
* @param predicateType <String> Type de prédicat (opérateur)
* @param predicateValues Valeurs possibles pour le type de prédicat (simple valeur ou tableau de valeurs)
* @param value Valeur à tester
*/
const matchPredicate = function (predicateType, predicateValues, value) {
switch (predicateType.toLowerCase()) {
case 'invalues': // vrai si la valeur de la propriété est présente dans le tableau
return predicateValues.some((val) => val === value)
case 'notinvalues': // vrai si la valeur de la propriété est absente du tableau
return !(predicateValues.some((val) => val === value))
case 'equal': // vrai si la valeur de la propriété est égale à une valeur donnée
return (predicateValues === value)
case 'notequal': // vrai si la valeur de la propriété est différente d'une valeur donnée
return (predicateValues !== value)
default:
console.warn(`type non identifié ${predicateType.toLowerCase()}`)
return false
}
}
function isFeatureVisible (feature, rules) {
const properties = feature.getProperties()
const isVisible = rules.every(({ type, values, field }) => {
return matchPredicate(type, values, properties[field])
})
return isVisible
}
class LayersFilters extends Interaction {
/**
* @type {ol.Collection}
* */
filtersOrGroups = new Collection()
/**
* référence des features ayant une filtre appliqué
*/
hiddenFeatures = []
constructor (options) {
options = options || {}
// on utilise self car on ne peut pas utiliser this avant la méthode super()
// const self = null
super(options = options || {})
this.viewer = options.viewer
this.filtersOrGroups.on('add', this.handleFilterChanged)
this.filtersOrGroups.on('remove', this.handleFilterChanged)
}
handleFilterChanged () {
this.dispatchEvent({ type: 'filters:change' })
this.changed()
}
/**
* Ajoute un groupe
* @param {string} idGroup
* @param {Object} param1
*/
addGroup (idGroup, { label, sublabel }) {
const group = {
type: 'group',
id: idGroup,
label,
sublabel,
filters: new Collection(),
}
this.filtersOrGroups.push(group)
group.filters.on('add', this.handleFilterChanged)
group.filters.on('remove', this.handleFilterChanged)
return group
}
/**
* ajoute un filtre
* @param {string} idFilter
* @param {Object} param1
*/
addFilter (idFilter, { idGroup, label, sublabel }) {
if (this.filterExists(idFilter)) {
throw new Error(`[LayersFilters:addFilter] Le filtre ${idFilter} existe déjà`)
}
// attention a ne jamais remplacer les instances de filters par une copie, on se sert d'une égalité de réference dans ControlLayerFilter
const filter = {
type: 'filter',
id: idFilter,
label,
sublabel,
applied: false,
layerFilters: {},
listeners: [], // écouteurs sur les sources de calques pour vérifier si on a des ajout/suppression sur le calque
}
if (idGroup) {
const group = this.filtersOrGroups.getArray().find(({ type, id }) => type === 'group' && id === idGroup)
if (!group) {
throw new Error(`[LayersFilters:addFilter] Le groupe ${idGroup} n'existe pas`)
}
group.filters.push(filter)
return filter
}
this.filtersOrGroups.push(filter)
return filter
}
removeAllGroupsAndFilters () {
this.clear()
this.filtersOrGroups.clear()
this.handleFilterChanged()
}
/**
*
* @param {string | object} filterOrIdFilter
* @param {object} layerFilters @example
{
"cans": [
{
"type": "inValues", // equal | notequal | invalues | notinvalues
"field": "TYPRES", // nom du champ de la feature openlayers
"values": [ // valeur ou liste de valeurs
"EU"
]
}
],
"ouvs": [
{
"type": "inValues",
"field": "TYPRES",
"values": [
"EU"
]
}
]
}
*/
addLayers (filterOrIdFilter, layerFilters) {
const { idFilter, ...options } = filterOrIdFilter
const filter = typeof filterOrIdFilter === 'object' ? this.addFilter(idFilter, options) : this.getFilter(filterOrIdFilter)
Object.keys(layerFilters).forEach(layerName => {
filter.layerFilters[layerName] = compact([].concat(filter.layerFilters[layerName], layerFilters[layerName]))
})
this.handleFilterChanged()
}
/**
* Est-ce que cette sublabel de filtre existe déjà ?
* @param {string} idFilter
* @returns {boolean}
*/
filterExists (idFilter) {
return this.filtersOrGroups.getArray().some(({ type, id, filters }) => {
if (type === 'filter' && id === idFilter) {
return true
}
if (type === 'group') {
return filters.getArray().some(({ id }) => id === idFilter)
}
return false
})
}
/**
*
* @param {string} idFilter
* @returns {object}
*/
getFilter (idFilter) {
return this.filtersOrGroups.getArray().reduce((acc, filterOrGroup) => {
if (acc) {
return acc // déjà trouvé
}
const { type, id, filters } = filterOrGroup
if (type === 'filter' && id === idFilter) {
return filterOrGroup
}
if (type === 'group') {
return filters.getArray().find(({ id }) => id === idFilter)
}
return undefined
}, null)
}
getAllFilters () {
return this.filtersOrGroups.getArray().reduce((acc, filterOrGroup) => {
const { type, filters } = filterOrGroup
if (type === 'filter') {
acc.push(filterOrGroup)
}
if (type === 'group') {
acc = acc.concat(filters.getArray())
}
return acc
}, [])
}
/**
*
* @returns filtre appliqué
*/
getCurrentFilter () {
return this.getAllFilters().find(({ applied }) => applied)
}
clear () {
this.removeCurrentFilter()
this.viewer.refreshMap()
}
removeCurrentFilter () {
this.hiddenFeatures.forEach(feature => this.viewer.dataLayer.showFeature(feature, null, true))
this.hiddenFeatures = []
const filter = this.getCurrentFilter()
if (!filter) {
return
}
filter.listeners.forEach(unByKey)
filter.applied = false
this.dispatchEvent({ type: 'change:visible', filter, visible: false })
}
applyFilter (idFilter) {
this.removeCurrentFilter()
const filter = this.getFilter(idFilter)
if (!filter) {
console.warn(`[LayersFilters:addFilter] Filtre ${idFilter} non trouvé`)
return
}
// on va chercher les features a masquer grâce aux rules sur le layer
const newHiddenFeatures = []
Object.keys(filter.layerFilters).forEach(layerName => {
const rules = filter.layerFilters[layerName]
this.viewer.dataLayer.getFeatures(layerName).forEach(feature => {
const isVisible = isFeatureVisible(feature, rules)
if (!isVisible) {
this.viewer.dataLayer.hideFeature(feature, null, true)
newHiddenFeatures.push(feature)
}
})
// on vérifi si il y a des nouvelles features sur le calque pour appliquer le filtre
const source = this.viewer.dataLayer.getSourceLayer(layerName)
filter.listeners.push(source.on('addfeature', ({ feature }) => {
const isVisible = isFeatureVisible(feature, rules)
if (!isVisible) {
this.viewer.dataLayer.hideFeature(feature, null, false)
this.hiddenFeatures.push(feature)
}
}))
filter.listeners.push(source.on('changefeature', ({ feature }) => {
const isVisible = isFeatureVisible(feature, rules)
const hiddenFeature = this.hiddenFeatures.find(hiddenFeature => hiddenFeature === feature)
if (!isVisible && !hiddenFeature) {
// vérifi si on ne l'a pas déjà masquée avant son changement de valeur
this.viewer.dataLayer.hideFeature(feature, null, false)
this.hiddenFeatures.push(feature)
} else if (isVisible && hiddenFeature) {
this.viewer.dataLayer.showFeature(feature, null, false)
this.hiddenFeatures = this.hiddenFeatures.filter(hiddenFeature => hiddenFeature !== feature)
}
}))
filter.listeners.push(source.on('removefeature', ({ feature }) => {
this.hiddenFeatures = this.hiddenFeatures.filter(hiddenFeature => hiddenFeature !== feature)
}))
})
this.hiddenFeatures = newHiddenFeatures
this.viewer.refreshMap()
filter.applied = true
this.dispatchEvent({ type: 'change:visible', filter, visible: true })
}
getGroupAndFilters () {
return this.filtersOrGroups
}
}
export default LayersFilters