MapComplete/src/Logic/State/LayerState.ts

190 lines
6.9 KiB
TypeScript
Raw Normal View History

import { Store, UIEventSource } from "../UIEventSource"
2023-03-25 02:48:24 +01:00
import { GlobalFilter } from "../../Models/GlobalFilter"
2023-03-28 05:13:48 +02:00
import FilteredLayer from "../../Models/FilteredLayer"
2023-03-25 02:48:24 +01:00
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { OsmConnection } from "../Osm/OsmConnection"
2023-04-26 18:04:42 +02:00
import { Tag } from "../Tags/Tag"
import Translations from "../../UI/i18n/Translations"
2023-05-01 01:12:39 +02:00
import { RegexTag } from "../Tags/RegexTag"
import { Or } from "../Tags/Or"
2024-08-26 13:09:46 +02:00
import FilterConfig from "../../Models/ThemeConfig/FilterConfig"
2024-09-11 01:46:55 +02:00
import Constants from "../../Models/Constants"
2023-03-25 02:48:24 +01:00
2024-08-26 13:09:46 +02:00
export type ActiveFilter = {
layer: LayerConfig,
filter: FilterConfig,
control: UIEventSource<string | number | undefined>
}
2023-03-25 02:48:24 +01:00
/**
* The layer state keeps track of:
* - Which layers are enabled
* - Which filters are used, including 'global' filters
*/
export default class LayerState {
/**
* Filters which apply onto all layers
*/
public readonly globalFilters: UIEventSource<GlobalFilter[]> = new UIEventSource(
[],
"globalFilters"
)
/**
* Which layers are enabled in the current theme and what filters are applied onto them
*/
public readonly filteredLayers: ReadonlyMap<string, FilteredLayer>
2024-08-26 13:09:46 +02:00
private readonly _activeFilters: UIEventSource<ActiveFilter[]> = new UIEventSource([])
public readonly activeFilters: Store<ActiveFilter[]> = this._activeFilters
2024-09-11 01:46:55 +02:00
private readonly _activeLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(undefined)
public readonly activeLayers: Store<FilteredLayer[]> = this._activeLayers
private readonly _nonactiveLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(undefined)
public readonly nonactiveLayers: Store<FilteredLayer[]> = this._nonactiveLayers
2023-03-25 02:48:24 +01:00
private readonly osmConnection: OsmConnection
/**
*
* @param osmConnection
* @param layers
* @param context
* @param layersEnabledByDefault
2023-03-25 02:48:24 +01:00
*/
2024-07-09 13:42:08 +02:00
constructor(
osmConnection: OsmConnection,
layers: LayerConfig[],
context: string,
layersEnabledByDefault: Store<boolean>
) {
2023-03-25 02:48:24 +01:00
this.osmConnection = osmConnection
const filteredLayers = new Map()
2023-03-25 02:48:24 +01:00
for (const layer of layers) {
filteredLayers.set(
2023-03-28 05:13:48 +02:00
layer.id,
2024-07-09 13:42:08 +02:00
FilteredLayer.initLinkedState(
layer,
context,
this.osmConnection,
layersEnabledByDefault
)
2023-03-25 02:48:24 +01:00
)
}
this.filteredLayers = filteredLayers
layers.forEach((l) => LayerState.linkFilterStates(l, filteredLayers))
2024-08-26 13:09:46 +02:00
this.filteredLayers.forEach(fl => {
fl.isDisplayed.addCallback(() => this.updateActiveFilters())
for (const [_, appliedFilter] of fl.appliedFilters) {
appliedFilter.addCallback(() => this.updateActiveFilters())
}
})
this.updateActiveFilters()
}
private updateActiveFilters(){
const filters: ActiveFilter[] = []
2024-09-11 01:46:55 +02:00
const activeLayers: FilteredLayer[] = []
const nonactiveLayers: FilteredLayer[] = []
2024-08-26 13:09:46 +02:00
this.filteredLayers.forEach(fl => {
if(!fl.isDisplayed.data){
2024-09-11 01:46:55 +02:00
nonactiveLayers.push(fl)
return
}
activeLayers.push(fl)
if(fl.layerDef.filterIsSameAs){
2024-08-26 13:09:46 +02:00
return
}
for (const [filtername, appliedFilter] of fl.appliedFilters) {
if (appliedFilter.data === undefined) {
continue
}
const filter = fl.layerDef.filters.find(f => f.id === filtername)
if(typeof appliedFilter.data === "number"){
if(filter.options[appliedFilter.data].osmTags === undefined){
// This is probably the first, generic option which doesn't _actually_ filter
continue
}
}
filters.push({
layer: fl.layerDef,
control: appliedFilter,
filter,
})
}
})
2024-09-11 01:46:55 +02:00
this._activeLayers.set(activeLayers)
this._nonactiveLayers.set(nonactiveLayers)
2024-08-26 13:09:46 +02:00
this._activeFilters.set(filters)
2023-04-26 18:04:42 +02:00
}
/**
* 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
const conditionsOrred = [
new Tag("_level", "" + level),
new RegexTag("_level", new RegExp("(.*;)?" + level + "(;.*)?")),
]
if (level === "0") {
conditionsOrred.push(new Tag("_level", "")) // No level tag is the same as level '0'
}
console.log("Setting levels filter to", conditionsOrred)
2023-04-26 18:04:42 +02:00
this.globalFilters.data.push({
id: "level",
state: level,
osmTags: new Or(conditionsOrred),
2023-04-26 18:04:42 +02:00
onNewPoint: {
tags: [new Tag("level", level)],
icon: "./assets/svg/elevator.svg",
confirmAddNew: t.confirmLevel.PartialSubs({ level }),
safetyCheck: t.addNewOnLevel.Subs({ level }),
},
})
this.globalFilters.ping()
2023-03-25 02:48:24 +01:00
}
/**
* Some layers copy the filter state of another layer - this is quite often the case for 'sibling'-layers,
* (where two variations of the same layer are used, e.g. a specific type of shop on all zoom levels and all shops on high zoom).
*
* This methods links those states for the given layer
*/
private static linkFilterStates(
layer: LayerConfig,
filteredLayers: Map<string, FilteredLayer>
) {
2023-03-25 02:48:24 +01:00
if (layer.filterIsSameAs === undefined) {
return
}
const toReuse = filteredLayers.get(layer.filterIsSameAs)
2023-03-25 02:48:24 +01:00
if (toReuse === undefined) {
throw (
"Error in layer " +
layer.id +
": it defines that it should be use the filters of " +
layer.filterIsSameAs +
", but this layer was not loaded"
)
}
console.warn(
"Linking filter and isDisplayed-states of " + layer.id + " and " + layer.filterIsSameAs
)
2023-04-14 17:53:08 +02:00
const copy = new FilteredLayer(layer, toReuse.appliedFilters, toReuse.isDisplayed)
filteredLayers.set(layer.id, copy)
2023-03-25 02:48:24 +01:00
}
}