forked from MapComplete/MapComplete
Add level selector and global filters
This commit is contained in:
parent
5504d49d59
commit
7fd7a3722e
19 changed files with 401 additions and 253 deletions
|
@ -1,8 +1,7 @@
|
|||
import { FeatureSource, FeatureSourceForLayer } from "./FeatureSource"
|
||||
import { FeatureSource, IndexedFeatureSource } from "./FeatureSource"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import SimpleFeatureSource from "./Sources/SimpleFeatureSource"
|
||||
import { Feature } from "geojson"
|
||||
import { Utils } from "../../Utils"
|
||||
import { UIEventSource } from "../UIEventSource"
|
||||
|
||||
/**
|
||||
|
@ -10,9 +9,7 @@ import { UIEventSource } from "../UIEventSource"
|
|||
* 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 FeatureSourceForLayer = SimpleFeatureSource
|
||||
> {
|
||||
export default class PerLayerFeatureSourceSplitter<T extends FeatureSource = FeatureSource> {
|
||||
public readonly perLayer: ReadonlyMap<string, T>
|
||||
constructor(
|
||||
layers: FilteredLayer[],
|
||||
|
@ -23,6 +20,11 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
}
|
||||
) {
|
||||
const knownLayers = new Map<string, T>()
|
||||
/**
|
||||
* 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>())
|
||||
this.perLayer = knownLayers
|
||||
const layerSources = new Map<string, UIEventSource<Feature[]>>()
|
||||
const constructStore =
|
||||
|
@ -41,6 +43,12 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
// We try to figure out (for each feature) in which feature store it should be saved.
|
||||
|
||||
const featuresPerLayer = new Map<string, Feature[]>()
|
||||
/**
|
||||
* 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: Feature[] = []
|
||||
|
||||
for (const layer of layers) {
|
||||
|
@ -49,9 +57,14 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
|
||||
for (const f of features) {
|
||||
let foundALayer = false
|
||||
for (const layer of layers) {
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
const layer = layers[i]
|
||||
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) {
|
||||
|
@ -67,7 +80,8 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
|
||||
// At this point, we have our features per layer as a list
|
||||
// We assign them to the correct featureSources
|
||||
for (const layer of layers) {
|
||||
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) {
|
||||
|
@ -75,14 +89,17 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
continue
|
||||
}
|
||||
|
||||
const src = layerSources.get(id)
|
||||
|
||||
if (Utils.sameList(src.data, features)) {
|
||||
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
|
||||
}
|
||||
src.setData(features)
|
||||
|
||||
layerSources.get(id).setData(features)
|
||||
}
|
||||
|
||||
layerIndexes = newIndices
|
||||
|
||||
// AT last, the leftovers are handled
|
||||
if (options?.handleLeftovers !== undefined && noLayerFound.length > 0) {
|
||||
options.handleLeftovers(noLayerFound)
|
||||
|
@ -90,7 +107,7 @@ export default class PerLayerFeatureSourceSplitter<
|
|||
})
|
||||
}
|
||||
|
||||
public forEach(f: (featureSource: FeatureSourceForLayer) => void) {
|
||||
public forEach(f: (featureSource: FeatureSource) => void) {
|
||||
for (const fs of this.perLayer.values()) {
|
||||
f(fs)
|
||||
}
|
||||
|
|
|
@ -122,7 +122,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
|
|||
throw "This is an absurd high zoom level"
|
||||
}
|
||||
|
||||
if (z < 14) {
|
||||
if (z < 15) {
|
||||
throw `Zoom ${z} is too much for OSM to handle! Use a higher zoom level!`
|
||||
}
|
||||
const index = Tiles.tile_index(z, x, y)
|
||||
|
|
|
@ -35,7 +35,18 @@ export default class MetaTagging {
|
|||
continue
|
||||
}
|
||||
const featureSource = state.perLayer.get(layer.id)
|
||||
featureSource.features?.addCallbackAndRunD((features) => {
|
||||
featureSource.features?.stabilized(1000)?.addCallbackAndRunD((features) => {
|
||||
if (!(features?.length > 0)) {
|
||||
// No features to handle
|
||||
return
|
||||
}
|
||||
console.trace(
|
||||
"Recalculating metatags for layer ",
|
||||
layer.id,
|
||||
"due to a change in the upstream features. Contains ",
|
||||
features.length,
|
||||
"items"
|
||||
)
|
||||
MetaTagging.addMetatags(
|
||||
features,
|
||||
params,
|
||||
|
@ -71,7 +82,6 @@ export default class MetaTagging {
|
|||
return
|
||||
}
|
||||
|
||||
console.debug("Recalculating metatags...")
|
||||
const metatagsToApply: SimpleMetaTagger[] = []
|
||||
for (const metatag of SimpleMetaTaggers.metatags) {
|
||||
if (metatag.includesDates) {
|
||||
|
|
|
@ -3,6 +3,8 @@ import { GlobalFilter } from "../../Models/GlobalFilter"
|
|||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { OsmConnection } from "../Osm/OsmConnection"
|
||||
import { Tag } from "../Tags/Tag"
|
||||
import Translations from "../../UI/i18n/Translations"
|
||||
|
||||
/**
|
||||
* The layer state keeps track of:
|
||||
|
@ -41,6 +43,45 @@ export default class LayerState {
|
|||
}
|
||||
this.filteredLayers = filteredLayers
|
||||
layers.forEach((l) => LayerState.linkFilterStates(l, filteredLayers))
|
||||
|
||||
this.globalFilters.data.push({
|
||||
id: "level",
|
||||
osmTags: undefined,
|
||||
state: undefined,
|
||||
onNewPoint: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global filter which looks to the 'level'-tag.
|
||||
* Only features with the given 'level' will be shown.
|
||||
*
|
||||
* If undefined is passed, _all_ levels will be shown
|
||||
* @param level
|
||||
*/
|
||||
public setLevelFilter(level?: string) {
|
||||
// Remove all previous
|
||||
const l = this.globalFilters.data.length
|
||||
this.globalFilters.data = this.globalFilters.data.filter((f) => f.id !== "level")
|
||||
if (!level) {
|
||||
if (l !== this.globalFilters.data.length) {
|
||||
this.globalFilters.ping()
|
||||
}
|
||||
return
|
||||
}
|
||||
const t = Translations.t.general.levelSelection
|
||||
this.globalFilters.data.push({
|
||||
id: "level",
|
||||
state: level,
|
||||
osmTags: new Tag("level", level),
|
||||
onNewPoint: {
|
||||
tags: [new Tag("level", level)],
|
||||
icon: "./assets/svg/elevator.svg",
|
||||
confirmAddNew: t.confirmLevel.PartialSubs({ level }),
|
||||
safetyCheck: t.addNewOnLevel.Subs({ level }),
|
||||
},
|
||||
})
|
||||
this.globalFilters.ping()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue