mapcomplete/src/Models/ThemeViewState.ts

626 lines
26 KiB
TypeScript
Raw Normal View History

2023-10-06 03:34:26 +02:00
import LayoutConfig from "./ThemeConfig/LayoutConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource"
2023-07-18 01:26:04 +02:00
import {
2023-10-06 03:34:26 +02:00
FeatureSource,
IndexedFeatureSource,
WritableFeatureSource,
} from "../Logic/FeatureSource/FeatureSource"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import { ExportableMap, MapProperties } from "./MapProperties"
import LayerState from "../Logic/State/LayerState"
import { Feature, Point, Polygon } from "geojson"
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource"
import { Map as MlMap } from "maplibre-gl"
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning"
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LayerConfig from "./ThemeConfig/LayerConfig"
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { AvailableRasterLayers, RasterLayerPolygon, RasterLayerUtils } from "./RasterLayers"
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
import StaticFeatureSource from "../Logic/FeatureSource/Sources/StaticFeatureSource"
import FeaturePropertiesStore from "../Logic/FeatureSource/Actors/FeaturePropertiesStore"
import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
import FilteringFeatureSource from "../Logic/FeatureSource/Sources/FilteringFeatureSource"
import ShowDataLayer from "../UI/Map/ShowDataLayer"
import TitleHandler from "../Logic/Actors/TitleHandler"
import ChangeToElementsActor from "../Logic/Actors/ChangeToElementsActor"
import PendingChangesUploader from "../Logic/Actors/PendingChangesUploader"
import SelectedElementTagsUpdater from "../Logic/Actors/SelectedElementTagsUpdater"
import { BBox } from "../Logic/BBox"
import Constants from "./Constants"
import Hotkeys from "../UI/Base/Hotkeys"
import Translations from "../UI/i18n/Translations"
import { GeoIndexedStoreForLayer } from "../Logic/FeatureSource/Actors/GeoIndexedStore"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import { MenuState } from "./MenuState"
import MetaTagging from "../Logic/MetaTagging"
import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator"
import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource"
import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader"
import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer"
import { Utils } from "../Utils"
import { EliCategory } from "./RasterLayerProperties"
import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter"
import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage"
import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
import NoElementsInViewDetector, {
FeatureViewState,
} from "../Logic/Actors/NoElementsInViewDetector"
import FilteredLayer from "./FilteredLayer"
import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector"
import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager"
import { Imgur } from "../Logic/ImageProviders/Imgur"
2023-03-28 05:13:48 +02:00
/**
*
* The themeviewState contains all the state needed for the themeViewGUI.
*
* This is pretty much the 'brain' or the HQ of MapComplete
*
* It ties up all the needed elements and starts some actors.
*/
export default class ThemeViewState implements SpecialVisualizationState {
2023-10-06 03:34:26 +02:00
readonly layout: LayoutConfig
readonly map: UIEventSource<MlMap>
readonly changes: Changes
readonly featureSwitches: FeatureSwitchState
readonly featureSwitchIsTesting: Store<boolean>
readonly featureSwitchUserbadge: Store<boolean>
readonly featureProperties: FeaturePropertiesStore
readonly osmConnection: OsmConnection
readonly selectedElement: UIEventSource<Feature>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
readonly mapProperties: MapProperties & ExportableMap
readonly osmObjectDownloader: OsmObjectDownloader
readonly dataIsLoading: Store<boolean>
/**
* Indicates if there is _some_ data in view, even if it is not shown due to the filters
*/
2023-10-06 03:34:26 +02:00
readonly hasDataInView: Store<FeatureViewState>
2023-10-06 03:34:26 +02:00
readonly guistate: MenuState
readonly fullNodeDatabase?: FullNodeDatabaseSource
2023-10-06 03:34:26 +02:00
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>
readonly indexedFeatures: IndexedFeatureSource & LayoutSource
readonly currentView: FeatureSource<Feature<Polygon>>
readonly featuresInView: FeatureSource
readonly newFeatures: WritableFeatureSource
readonly layerState: LayerState
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>
2023-10-06 03:34:26 +02:00
readonly availableLayers: Store<RasterLayerPolygon[]>
readonly selectedLayer: UIEventSource<LayerConfig>
readonly userRelatedState: UserRelatedState
readonly geolocation: GeoLocationHandler
2023-10-06 03:34:26 +02:00
readonly imageUploadManager: ImageUploadManager
2023-10-06 03:34:26 +02:00
readonly lastClickObject: WritableFeatureSource
2023-04-21 01:53:24 +02:00
readonly overlayLayerStates: ReadonlyMap<
string,
{ readonly isDisplayed: UIEventSource<boolean> }
2023-10-06 03:34:26 +02:00
>
2023-04-26 18:04:42 +02:00
/**
* All 'level'-tags that are available with the current features
*/
2023-10-06 03:34:26 +02:00
readonly floors: Store<string[]>
2023-03-28 05:13:48 +02:00
constructor(layout: LayoutConfig) {
2023-10-06 03:34:26 +02:00
Utils.initDomPurify()
this.layout = layout
this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id
2023-10-06 03:34:26 +02:00
)
this.map = new UIEventSource<MlMap>(undefined)
const initial = new InitialMapPositioning(layout)
this.mapProperties = new MapLibreAdaptor(this.map, initial)
const geolocationState = new GeoLocationState()
2023-03-28 05:13:48 +02:00
2023-10-06 03:34:26 +02:00
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
2023-03-28 05:13:48 +02:00
this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting,
fakeUser: this.featureSwitches.featureSwitchFakeUser.data,
oauth_token: QueryParameters.GetQueryParameter(
"oauth_token",
undefined,
"Used to complete the login"
2023-10-06 03:34:26 +02:00
),
})
2023-04-14 17:53:08 +02:00
this.userRelatedState = new UserRelatedState(
this.osmConnection,
layout?.language,
layout,
this.featureSwitches,
this.mapProperties
2023-10-06 03:34:26 +02:00
)
2023-07-18 01:26:04 +02:00
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
2023-10-06 03:34:26 +02:00
this.mapProperties.allowRotating.setData(fixated !== "yes")
})
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
2023-07-18 01:26:04 +02:00
this.selectedElementAndLayer = this.selectedElement.mapD(
(feature) => {
2023-10-06 03:34:26 +02:00
const layer = this.selectedLayer.data
2023-07-18 01:26:04 +02:00
if (!layer) {
2023-10-06 03:34:26 +02:00
return undefined
2023-07-18 01:26:04 +02:00
}
2023-10-06 03:34:26 +02:00
return { layer, feature }
2023-07-18 01:26:04 +02:00
},
[this.selectedLayer]
2023-10-06 03:34:26 +02:00
)
2023-07-18 01:26:04 +02:00
2023-03-28 05:13:48 +02:00
this.geolocation = new GeoLocationHandler(
geolocationState,
this.selectedElement,
this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime
2023-10-06 03:34:26 +02:00
)
2023-10-06 03:34:26 +02:00
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
2023-03-28 05:13:48 +02:00
2023-10-06 03:34:26 +02:00
const self = this
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
2023-04-21 01:53:24 +02:00
{
2023-10-06 03:34:26 +02:00
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>()
2023-04-21 01:53:24 +02:00
for (const rasterInfo of this.layout.tileLayerSources) {
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
2023-10-06 03:34:26 +02:00
)
const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state)
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state)
2023-04-21 01:53:24 +02:00
}
2023-10-06 03:34:26 +02:00
this.overlayLayerStates = overlayLayerStates
2023-04-21 01:53:24 +02:00
}
{
/* Setup the layout source
* A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too
*/
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
2023-10-06 03:34:26 +02:00
this.fullNodeDatabase = new FullNodeDatabaseSource()
2023-06-01 02:52:21 +02:00
}
const layoutSource = new LayoutSource(
layout.layers,
this.featureSwitches,
this.mapProperties,
this.osmConnection.Backend(),
2023-06-01 02:52:21 +02:00
(id) => self.layerState.filteredLayers.get(id).isDisplayed,
this.fullNodeDatabase
2023-10-06 03:34:26 +02:00
)
2023-08-23 20:39:06 +02:00
2023-10-06 03:34:26 +02:00
this.indexedFeatures = layoutSource
2023-10-06 03:34:26 +02:00
const empty = []
let currentViewIndex = 0
2023-05-05 01:25:37 +02:00
this.currentView = new StaticFeatureSource(
2023-06-01 02:52:21 +02:00
this.mapProperties.bounds.map((bbox) => {
if (!bbox) {
2023-10-06 03:34:26 +02:00
return empty
2023-06-01 02:52:21 +02:00
}
2023-10-06 03:34:26 +02:00
currentViewIndex++
return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
2023-10-06 03:34:26 +02:00
id: "current_view",
}),
]
})
2023-10-06 03:34:26 +02:00
)
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading
2023-10-06 03:34:26 +02:00
const indexedElements = this.indexedFeatures
this.featureProperties = new FeaturePropertiesStore(indexedElements)
this.changes = new Changes(
{
dryRun: this.featureSwitches.featureSwitchIsTesting,
allElements: indexedElements,
featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection,
2023-10-06 03:34:26 +02:00
historicalUserLocations: this.geolocation.historicalUserLocations,
},
layout?.isLeftRightSensitive() ?? false
2023-10-06 03:34:26 +02:00
)
this.historicalUserLocations = this.geolocation.historicalUserLocations
this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes,
indexedElements,
this.featureProperties
2023-10-06 03:34:26 +02:00
)
layoutSource.addSource(this.newFeatures)
2023-03-28 05:13:48 +02:00
const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter(
(l) => l.layerDef?.source !== null
),
new ChangeGeometryApplicator(this.indexedFeatures, this.changes),
{
constructStore: (features, layer) =>
new GeoIndexedStoreForLayer(features, layer),
handleLeftovers: (features) => {
console.warn(
"Got ",
features.length,
"leftover features, such as",
features[0].properties
2023-10-06 03:34:26 +02:00
)
},
}
2023-10-06 03:34:26 +02:00
)
this.perLayer = perLayer.perLayer
}
2023-03-28 05:13:48 +02:00
this.perLayer.forEach((fs) => {
2023-04-21 20:50:38 +02:00
new SaveFeatureSourceToLocalStorage(
this.osmConnection.Backend(),
2023-04-18 23:44:49 +02:00
fs.layer.layerDef.id,
15,
fs,
this.featureProperties,
fs.layer.layerDef.maxAgeOfCache
2023-10-06 03:34:26 +02:00
)
})
2023-03-28 05:13:48 +02:00
this.floors = this.featuresInView.features.stabilized(500).map((features) => {
2023-04-26 18:04:42 +02:00
if (!features) {
2023-10-06 03:34:26 +02:00
return []
2023-04-26 18:04:42 +02:00
}
2023-10-06 03:34:26 +02:00
const floors = new Set<string>()
2023-04-26 18:04:42 +02:00
for (const feature of features) {
2023-10-06 03:34:26 +02:00
let level = feature.properties["_level"]
2023-04-26 18:04:42 +02:00
if (level) {
2023-10-06 03:34:26 +02:00
const levels = level.split(";")
for (const l of levels) {
2023-10-06 03:34:26 +02:00
floors.add(l)
}
2023-06-01 02:52:21 +02:00
} else {
2023-10-06 03:34:26 +02:00
floors.add("0") // '0' is the default and is thus _always_ present
2023-04-26 18:04:42 +02:00
}
}
2023-10-06 03:34:26 +02:00
const sorted = Array.from(floors)
2023-04-26 18:04:42 +02:00
// Sort alphabetically first, to deal with floor "A", "B" and "C"
2023-10-06 03:34:26 +02:00
sorted.sort()
2023-04-26 18:04:42 +02:00
sorted.sort((a, b) => {
// We use the laxer 'parseInt' to deal with floor '1A'
2023-10-06 03:34:26 +02:00
const na = parseInt(a)
const nb = parseInt(b)
2023-04-26 18:04:42 +02:00
if (isNaN(na) || isNaN(nb)) {
2023-10-06 03:34:26 +02:00
return 0
2023-04-26 18:04:42 +02:00
}
2023-10-06 03:34:26 +02:00
return na - nb
})
sorted.reverse(/* new list, no side-effects */)
return sorted
})
2023-04-26 18:04:42 +02:00
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
2023-10-06 03:34:26 +02:00
))
2023-03-28 05:13:48 +02:00
2023-04-20 18:58:31 +02:00
this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(),
this.changes
2023-10-06 03:34:26 +02:00
)
2023-10-06 03:34:26 +02:00
this.perLayerFiltered = this.showNormalDataOn(this.map)
2023-10-06 03:34:26 +02:00
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView
this.imageUploadManager = new ImageUploadManager(
layout,
Imgur.singleton,
this.featureProperties,
this.osmConnection,
this.changes
2023-10-06 03:34:26 +02:00
)
2023-10-06 03:34:26 +02:00
this.initActors()
this.addLastClick(lastClick)
this.drawSpecialLayers()
this.initHotkeys()
this.miscSetup()
2023-06-01 02:52:21 +02:00
if (!Utils.runningFromConsole) {
2023-10-06 03:34:26 +02:00
console.log("State setup completed", this)
2023-05-18 15:44:54 +02:00
}
2023-03-28 05:13:48 +02:00
}
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
2023-10-06 03:34:26 +02:00
const filteringFeatureSource = new Map<string, FilteringFeatureSource>()
this.perLayer.forEach((fs, layerName) => {
2023-06-04 22:52:13 +02:00
const doShowLayer = this.mapProperties.zoom.map(
(z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
[fs.layer.isDisplayed]
2023-10-06 03:34:26 +02:00
)
2023-06-04 22:52:13 +02:00
2023-08-23 20:39:06 +02:00
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
2023-06-04 22:52:13 +02:00
/* This layer is hidden and there is no way to enable it (filterview is disabled or this layer doesn't show up in the filter view as the name is not defined)
*
* This means that we don't have to filter it, nor do we have to display it
2023-08-23 20:39:06 +02:00
*
* Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden.
* However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer!
2023-06-04 22:52:13 +02:00
* */
2023-10-06 03:34:26 +02:00
return
2023-06-04 22:52:13 +02:00
}
const filtered = new FilteringFeatureSource(
fs.layer,
fs,
(id) => this.featureProperties.getStore(id),
this.layerState.globalFilters
2023-10-06 03:34:26 +02:00
)
filteringFeatureSource.set(layerName, filtered)
2023-06-04 22:52:13 +02:00
new ShowDataLayer(map, {
layer: fs.layer.layerDef,
features: filtered,
doShowLayer,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
2023-10-06 03:34:26 +02:00
fetchStore: (id) => this.featureProperties.getStore(id),
})
})
return filteringFeatureSource
2023-06-04 22:52:13 +02:00
}
2023-03-28 05:13:48 +02:00
/**
* Various small methods that need to be called
*/
private miscSetup() {
2023-10-06 03:34:26 +02:00
this.userRelatedState.markLayoutAsVisited(this.layout)
this.selectedElement.addCallbackAndRunD((feature) => {
// As soon as we have a selected element, we clear the selected element
// This is to work around maplibre, which'll _first_ register the click on the map and only _then_ on the feature
// The only exception is if the last element is the 'add_new'-button, as we don't want it to disappear
if (feature.properties.id === "last_click") {
2023-10-06 03:34:26 +02:00
return
}
2023-10-06 03:34:26 +02:00
this.lastClickObject.features.setData([])
})
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
2023-10-06 03:34:26 +02:00
Utils.LoadCustomCss(this.layout.customCss)
}
2023-03-28 05:13:48 +02:00
}
private initHotkeys() {
Hotkeys.RegisterHotkey(
2023-07-18 01:26:04 +02:00
{ nomod: "Escape", onUp: true },
2023-03-28 05:13:48 +02:00
Translations.t.hotkeyDocumentation.closeSidebar,
() => {
2023-10-06 03:34:26 +02:00
this.selectedElement.setData(undefined)
this.guistate.closeAll()
2023-03-28 05:13:48 +02:00
}
2023-10-06 03:34:26 +02:00
)
Hotkeys.RegisterHotkey(
{
2023-10-06 03:34:26 +02:00
nomod: "b",
},
Translations.t.hotkeyDocumentation.openLayersPanel,
() => {
if (this.featureSwitches.featureSwitchFilter.data) {
2023-10-06 03:34:26 +02:00
this.guistate.openFilterView()
}
}
2023-10-06 03:34:26 +02:00
)
Hotkeys.RegisterHotkey(
2023-07-18 01:26:04 +02:00
{ shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik,
() => {
2023-10-06 03:34:26 +02:00
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
}
2023-10-06 03:34:26 +02:00
)
const setLayerCategory = (category: EliCategory) => {
2023-10-06 03:34:26 +02:00
const available = this.availableLayers.data
const current = this.mapProperties.rasterLayer
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available,
category,
current.data
2023-10-06 03:34:26 +02:00
)
console.log("Best layer for category", category, "is", best.properties.id)
current.setData(best)
}
Hotkeys.RegisterHotkey(
2023-07-18 01:26:04 +02:00
{ nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap")
2023-10-06 03:34:26 +02:00
)
2023-07-18 01:26:04 +02:00
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map")
2023-10-06 03:34:26 +02:00
)
Hotkeys.RegisterHotkey(
2023-07-18 01:26:04 +02:00
{ nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo")
2023-10-06 03:34:26 +02:00
)
2023-03-28 05:13:48 +02:00
}
private addLastClick(last_click: LastClickFeatureSource) {
// The last_click gets a _very_ special treatment as it interacts with various parts
2023-10-06 03:34:26 +02:00
const last_click_layer = this.layerState.filteredLayers.get("last_click")
this.featureProperties.trackFeatureSource(last_click)
this.indexedFeatures.addSource(last_click)
last_click.features.addCallbackAndRunD((features) => {
if (this.selectedLayer.data?.id === "last_click") {
// The last-click location moved, but we have selected the last click of the previous location
// So, we update _after_ clearing the selection to make sure no stray data is sticking around
2023-10-06 03:34:26 +02:00
this.selectedElement.setData(undefined)
this.selectedElement.setData(features[0])
}
2023-10-06 03:34:26 +02:00
})
new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(last_click_layer, last_click),
doShowLayer: this.featureSwitches.featureSwitchEnableLogin,
layer: last_click_layer.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
onClick: (feature: Feature) => {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
2023-10-06 03:34:26 +02:00
center: this.mapProperties.lastClickLocation.data,
})
return
}
// We first clear the selection to make sure no weird state is around
2023-10-06 03:34:26 +02:00
this.selectedLayer.setData(undefined)
this.selectedElement.setData(undefined)
2023-10-06 03:34:26 +02:00
this.selectedElement.setData(feature)
this.selectedLayer.setData(last_click_layer.layerDef)
},
})
}
2023-03-28 05:13:48 +02:00
/**
* Add the special layers to the map
*/
private drawSpecialLayers() {
2023-06-14 20:44:01 +02:00
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
2023-10-06 03:34:26 +02:00
const empty = []
2023-03-28 05:13:48 +02:00
/**
* A listing which maps the layerId onto the featureSource
*/
const specialLayers: Record<
Exclude<AddedByDefaultTypes, "last_click"> | "current_view",
FeatureSource
> = {
2023-03-28 05:13:48 +02:00
home_location: this.userRelatedState.homeLocation,
gps_location: this.geolocation.currentUserLocation,
gps_location_history: this.geolocation.historicalUserLocations,
gps_track: this.geolocation.historicalUserLocationsTrack,
selected_element: new StaticFeatureSource(
this.selectedElement.map((f) => (f === undefined ? empty : [f]))
),
range: new StaticFeatureSource(
this.mapProperties.maxbounds.map((bbox) =>
2023-07-18 01:26:04 +02:00
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
2023-03-28 05:13:48 +02:00
)
),
2023-10-06 03:34:26 +02:00
current_view: this.currentView,
}
2023-03-28 05:13:48 +02:00
if (this.layout?.lockLocation) {
2023-10-06 03:34:26 +02:00
const bbox = new BBox(this.layout.lockLocation)
this.mapProperties.maxbounds.setData(bbox)
2023-03-28 05:13:48 +02:00
ShowDataLayer.showRange(
this.map,
2023-10-06 03:34:26 +02:00
new StaticFeatureSource([bbox.asGeoJson({ id: "range" })]),
2023-03-28 05:13:48 +02:00
this.featureSwitches.featureSwitchIsTesting
2023-10-06 03:34:26 +02:00
)
2023-03-28 05:13:48 +02:00
}
2023-10-06 03:34:26 +02:00
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view")
2023-06-01 02:52:21 +02:00
if (currentViewLayer?.tagRenderings?.length > 0) {
2023-10-06 03:34:26 +02:00
const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view)
specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags(
features,
params,
currentViewLayer,
this.layout,
this.osmObjectDownloader,
this.featureProperties
2023-10-06 03:34:26 +02:00
)
})
2023-06-01 02:52:21 +02:00
}
2023-03-28 05:13:48 +02:00
2023-10-06 03:34:26 +02:00
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range")
2023-07-16 02:08:43 +02:00
2023-10-06 03:34:26 +02:00
const rangeIsDisplayed = rangeFLayer?.isDisplayed
2023-07-16 02:08:43 +02:00
2023-07-18 01:26:04 +02:00
if (
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
) {
2023-10-06 03:34:26 +02:00
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true)
2023-07-16 02:08:43 +02:00
}
2023-03-28 05:13:48 +02:00
this.layerState.filteredLayers.forEach((flayer) => {
2023-10-06 03:34:26 +02:00
const id = flayer.layerDef.id
const features: FeatureSource = specialLayers[id]
2023-03-28 05:13:48 +02:00
if (features === undefined) {
2023-10-06 03:34:26 +02:00
return
2023-03-28 05:13:48 +02:00
}
2023-04-20 18:58:31 +02:00
2023-10-06 03:34:26 +02:00
this.featureProperties.trackFeatureSource(features)
2023-03-28 05:13:48 +02:00
new ShowDataLayer(this.map, {
features,
doShowLayer: flayer.isDisplayed,
layer: flayer.layerDef,
selectedElement: this.selectedElement,
2023-10-06 03:34:26 +02:00
selectedLayer: this.selectedLayer,
})
})
2023-03-28 05:13:48 +02:00
}
/**
* Setup various services for which no reference are needed
*/
private initActors() {
// Unselect the selected element if it is panned out of view
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
2023-10-06 03:34:26 +02:00
const selected = this.selectedElement.data
if (selected === undefined) {
2023-10-06 03:34:26 +02:00
return
}
2023-10-06 03:34:26 +02:00
const bbox = BBox.get(selected)
if (!bbox.overlapsWith(bounds)) {
2023-10-06 03:34:26 +02:00
this.selectedElement.setData(undefined)
}
2023-10-06 03:34:26 +02:00
})
this.selectedElement.addCallback((selected) => {
if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object
2023-10-06 03:34:26 +02:00
this.lastClickObject.features.setData([])
this.selectedLayer.setData(undefined)
}
2023-10-06 03:34:26 +02:00
})
new ThemeViewStateHashActor(this)
new MetaTagging(this)
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this)
new ChangeToElementsActor(this.changes, this.featureProperties)
new PendingChangesUploader(this.changes, this.selectedElement)
new SelectedElementTagsUpdater(this)
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers)
new PreferredRasterLayerSelector(
this.mapProperties.rasterLayer,
this.availableLayers,
this.featureSwitches.backgroundLayerId,
this.userRelatedState.preferredBackgroundLayer
2023-10-06 03:34:26 +02:00
)
2023-03-28 05:13:48 +02:00
}
}