import { FeatureSource } from "./FeatureSource" import FilteredLayer from "../../Models/FilteredLayer" import SimpleFeatureSource from "./Sources/SimpleFeatureSource" import { Feature } from "geojson" import { UIEventSource } from "../UIEventSource" /** * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) * If this is the case, multiple objects with a different _matching_layer_id are generated. * In any case, this featureSource marks the objects with _matching_layer_id */ export default class PerLayerFeatureSourceSplitter { public readonly perLayer: ReadonlyMap constructor( layers: FilteredLayer[], upstream: FeatureSource, options?: { constructStore?: (features: UIEventSource, layer: FilteredLayer) => T handleLeftovers?: (featuresWithoutLayer: Feature[]) => void } ) { const knownLayers = new Map() /** * Keeps track of the ids that are included per layer. * Used to know if the downstream feature source needs to be pinged */ let layerIndexes: ReadonlySet[] = layers.map((_) => new Set()) this.perLayer = knownLayers const layerSources = new Map>() const constructStore = options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store)) for (const layer of layers) { const src = new UIEventSource([]) layerSources.set(layer.layerDef.id, src) knownLayers.set(layer.layerDef.id, constructStore(src, layer)) } upstream.features.addCallbackAndRunD((features) => { if (layers === undefined) { return } // We try to figure out (for each feature) in which feature store it should be saved. const featuresPerLayer = new Map() /** * Indexed on layer-position * Will be true if a new id pops up */ const hasChanged: boolean[] = layers.map((_) => false) const newIndices: Set[] = layers.map((_) => new Set()) const noLayerFound: Feature[] = [] for (const layer of layers) { featuresPerLayer.set(layer.layerDef.id, []) } for (const f of features) { let foundALayer = false for (let i = 0; i < layers.length; i++) { const layer = layers[i] if (!layer.layerDef?.source) { console.error( "PerLayerFeatureSourceSplitter got a layer without a source:", layer.layerDef.id ) continue } if (layer.layerDef.source.osmTags.matchesProperties(f.properties)) { const id = f.properties.id // We have found our matching layer! const previousIndex = layerIndexes[i] hasChanged[i] = hasChanged[i] || !previousIndex.has(id) newIndices[i].add(id) featuresPerLayer.get(layer.layerDef.id).push(f) foundALayer = true if (!layer.layerDef.passAllFeatures) { // If not 'passAllFeatures', we are done for this feature break } } } if (!foundALayer) { noLayerFound.push(f) } } // At this point, we have our features per layer as a list // We assign them to the correct featureSources for (let i = 0; i < layers.length; i++) { const layer = layers[i] const id = layer.layerDef.id const features = featuresPerLayer.get(id) if (features === undefined) { // No such features for this layer continue } if (!hasChanged[i] && layerIndexes[i].size === newIndices[i].size) { // No new id has been added and the sizes are the same (thus: nothing has been removed as well) // We can safely assume that no changes were made continue } layerSources.get(id).setData(features) } layerIndexes = newIndices // AT last, the leftovers are handled if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) { options.handleLeftovers(noLayerFound) } }) } public forEach(f: (featureSource: T) => void) { for (const fs of this.perLayer.values()) { f(fs) } } }