MapComplete/src/Logic/FeatureSource/PerLayerFeatureSourceSplitter.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

124 lines
5.1 KiB
TypeScript
Raw Normal View History

import { FeatureSource } from "./FeatureSource"
import FilteredLayer from "../../Models/FilteredLayer"
import SimpleFeatureSource from "./Sources/SimpleFeatureSource"
import { Feature } from "geojson"
import { UIEventSource } from "../UIEventSource"
/**
* Constructs multiple featureStores based on the given layers, where every constructed feature source will contain features only matching the given layer
*
* 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<T extends Feature, SRC extends FeatureSource<T>> {
public readonly perLayer: ReadonlyMap<string, SRC>
constructor(
2023-03-28 05:13:48 +02:00
layers: FilteredLayer[],
upstream: FeatureSource<T>,
2021-11-07 16:34:51 +01:00
options?: {
constructStore?: (features: UIEventSource<T[]>, layer: FilteredLayer) => SRC
handleLeftovers?: (featuresWithoutLayer: T[]) => void
}
) {
const knownLayers = new Map<string, SRC>()
2023-04-26 18:04:42 +02:00
/**
* 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<string>[] = layers.map(() => new Set<string>())
2023-03-28 05:13:48 +02:00
this.perLayer = knownLayers
const layerSources = new Map<string, UIEventSource<Feature[]>>()
const constructStore =
options?.constructStore ?? ((store, layer) => new SimpleFeatureSource(layer, store))
for (const layer of layers) {
const src = new UIEventSource<T[]>([])
2023-03-28 05:13:48 +02:00
layerSources.set(layer.layerDef.id, src)
knownLayers.set(layer.layerDef.id, <SRC>constructStore(src, layer))
2023-03-28 05:13:48 +02:00
}
upstream.features.addCallbackAndRunD((features: T[]) => {
2023-03-28 05:13:48 +02:00
if (layers === undefined) {
return
}
// We try to figure out (for each feature) in which feature store it should be saved.
const featuresPerLayer = new Map<string, Feature[]>()
2023-04-26 18:04:42 +02:00
/**
* Indexed on layer-position
* Will be true if a new id pops up
*/
const hasChanged: boolean[] = layers.map(() => false)
const newIndices: Set<string>[] = layers.map(() => new Set<string>())
const noLayerFound: T[] = []
2021-11-07 16:34:51 +01:00
2023-03-28 05:13:48 +02:00
for (const layer of layers) {
featuresPerLayer.set(layer.layerDef.id, [])
}
for (const f of features) {
let foundALayer = false
2023-04-26 18:04:42 +02:00
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)) {
2023-04-26 18:04:42 +02:00
const id = f.properties.id
// We have found our matching layer!
2023-04-26 18:04:42 +02:00
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
2023-04-26 18:04:42 +02:00
for (let i = 0; i < layers.length; i++) {
const layer = layers[i]
const id = layer.layerDef.id
2025-05-04 02:30:46 +02:00
const featuresForLayer = featuresPerLayer.get(id)
if (featuresForLayer === undefined) {
// No such features for this layer
continue
}
2023-04-26 18:04:42 +02:00
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
}
2025-05-04 02:30:46 +02:00
layerSources.get(id).setData(featuresForLayer)
}
2021-11-07 16:34:51 +01:00
2023-04-26 18:04:42 +02:00
layerIndexes = newIndices
// AT last, the leftovers are handled
2021-11-07 16:34:51 +01:00
if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) {
options.handleLeftovers(noLayerFound)
}
2023-03-28 05:13:48 +02:00
})
}
public forEach(f: ((src: SRC) => void)) {
2023-03-28 05:13:48 +02:00
for (const fs of this.perLayer.values()) {
f(fs)
}
}
}