Feature: be able to set a default background layer, fix background layer query parameter

This commit is contained in:
Pieter Vander Vennet 2023-09-24 16:34:36 +02:00
parent 93a30fdba5
commit 456da1b6c3
7 changed files with 401 additions and 280 deletions

View file

@ -23,7 +23,8 @@
"_d=feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? ''", "_d=feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? ''",
"_mastodon_candidate_a=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ", "_mastodon_candidate_a=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ",
"_mastodon_link=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.getAttribute(\"rel\")?.indexOf('me') >= 0)[0]?.href})(feat) ", "_mastodon_link=(feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName(\"a\")).filter(a => a.getAttribute(\"rel\")?.indexOf('me') >= 0)[0]?.href})(feat) ",
"_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a" "_mastodon_candidate=feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a",
"__current_background='some value to let the validation run through'"
], ],
"tagRenderings": [ "tagRenderings": [
{ {
@ -103,6 +104,54 @@
"*": "{logout()}" "*": "{logout()}"
} }
}, },
{
"id": "background-layer",
"question": {
"en": "What background layer should be shown by default?"
},
"mappings": [
{
"if": "mapcomplete-preferred-background-layer=",
"then": {
"en": "Use the default background layer"
}
},
{
"if": "mapcomplete-preferred-background-layer=osm",
"then": {
"en": "Use OpenStreetMap-carto as default layer"
}
},
{
"if": "mapcomplete-preferred-background-layer=photo",
"then": {
"en": "Use aerial imagery as default background"
}
},
{
"if": "mapcomplete-preferred-background-layer=map",
"then": {
"en": "Use a non-openstreetmap based map as default background"
}
},
{
"if": "mapcomplete-preferred-background-layer:={__current_background}",
"then": {
"en": "Use the current background layer (<span class='code'>{__current_background}</span>) as default background"
},
"hideInAnswer": {
"or": ["__current_background=","__current_background=osm"]
}
},
{
"if": "mapcomplete-preferred-background-layer~*",
"then": {
"en": "Use background layer <span class='code'>{mapcomplete-preferred-background-layer}</span> as default background"
},
"hideInAnswer": true
}
]
},
{ {
"id": "picture-license", "id": "picture-license",
"description": "This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants", "description": "This question is not meant to be placed on an OpenStreetMap-element; however it is used in the user information panel to ask which license the user wants",

View file

@ -0,0 +1,67 @@
import { Store, UIEventSource } from "../UIEventSource";
import { RasterLayerPolygon } from "../../Models/RasterLayers";
/**
* Selects the appropriate raster layer as background for the given query parameter, theme setting, user preference or default value.
*
* It the requested layer is not available, a layer of the same type will be selected.
*/
export class PreferredRasterLayerSelector {
private readonly _rasterLayerSetting: UIEventSource<RasterLayerPolygon>;
private readonly _availableLayers: Store<RasterLayerPolygon[]>;
private readonly _preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined>;
private readonly _queryParameter: UIEventSource<string>;
constructor(rasterLayerSetting: UIEventSource<RasterLayerPolygon>, availableLayers: Store<RasterLayerPolygon[]>, queryParameter: UIEventSource<string>, preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined>) {
this._rasterLayerSetting = rasterLayerSetting;
this._availableLayers = availableLayers;
this._queryParameter = queryParameter;
this._preferredBackgroundLayer = preferredBackgroundLayer;
const self = this;
this._rasterLayerSetting.addCallbackD(layer => {
if (layer.properties.id !== this._queryParameter.data) {
this._queryParameter.setData(undefined);
return true;
}
});
this._queryParameter.addCallbackAndRunD(_ => {
const isApplied = self.updateLayer();
if (!isApplied) {
// A different layer was set as background
// We remove this queryParameter instead
self._queryParameter.setData(undefined);
return true; // Unregister
}
});
this._preferredBackgroundLayer.addCallbackD(_ => self.updateLayer());
this._availableLayers.addCallbackD(_ => self.updateLayer());
}
/**
* Returns 'true' if the target layer is set or is the current layer
* @private
*/
private updateLayer() {
// What is the ID of the layer we have to (try to) load?
const targetLayerId = this._queryParameter.data ?? this._preferredBackgroundLayer.data;
const available = this._availableLayers.data;
const isCategory = targetLayerId === "photo" || targetLayerId === "osmbasedmap" || targetLayerId === "map"
const foundLayer = isCategory ? available.find(l => l.properties.category === targetLayerId) : available.find(l => l.properties.id === targetLayerId);
if (foundLayer) {
this._rasterLayerSetting.setData(foundLayer);
return true;
}
// The current layer is not in view
}
}

View file

@ -198,7 +198,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
this.backgroundLayerId = QueryParameters.GetQueryParameter( this.backgroundLayerId = QueryParameters.GetQueryParameter(
"background", "background",
layoutToUse?.defaultBackgroundId ?? "osm", layoutToUse?.defaultBackgroundId,
"The id of the background layer to start with" "The id of the background layer to start with"
) )
} }

View file

@ -17,6 +17,7 @@ import FeatureSwitchState from "./FeatureSwitchState"
import Constants from "../../Models/Constants" import Constants from "../../Models/Constants"
import { QueryParameters } from "../Web/QueryParameters" import { QueryParameters } from "../Web/QueryParameters"
import { ThemeMetaTagging } from "./UserSettingsMetaTagging" import { ThemeMetaTagging } from "./UserSettingsMetaTagging"
import { MapProperties } from "../../Models/MapProperties";
/** /**
* The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection,
@ -41,6 +42,7 @@ export default class UserRelatedState {
public readonly fixateNorth: UIEventSource<undefined | "yes"> public readonly fixateNorth: UIEventSource<undefined | "yes">
public readonly homeLocation: FeatureSource public readonly homeLocation: FeatureSource
public readonly language: UIEventSource<string> public readonly language: UIEventSource<string>
public readonly preferredBackgroundLayer: UIEventSource<string | "photo" | "map" | "osmbasedmap" | undefined>
/** /**
* The number of seconds that the GPS-locations are stored in memory. * The number of seconds that the GPS-locations are stored in memory.
* Time in seconds * Time in seconds
@ -52,16 +54,23 @@ export default class UserRelatedState {
/** /**
* Preferences as tags exposes many preferences and state properties as record. * Preferences as tags exposes many preferences and state properties as record.
* This is used to bridge the internal state with the usersettings.json layerconfig file * This is used to bridge the internal state with the usersettings.json layerconfig file
*
* Some metainformation that should not be edited starts with a single underscore
* Constants and query parameters start with two underscores
* Note: these are linked via OsmConnection.preferences which exports all preferences as UIEventSource
*/ */
public readonly preferencesAsTags: UIEventSource<Record<string, string>> public readonly preferencesAsTags: UIEventSource<Record<string, string>>
private readonly _mapProperties: MapProperties;
constructor( constructor(
osmConnection: OsmConnection, osmConnection: OsmConnection,
availableLanguages?: string[], availableLanguages?: string[],
layout?: LayoutConfig, layout?: LayoutConfig,
featureSwitches?: FeatureSwitchState featureSwitches?: FeatureSwitchState,
mapProperties?: MapProperties
) { ) {
this.osmConnection = osmConnection this.osmConnection = osmConnection
this._mapProperties = mapProperties;
{ {
const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> = const translationMode: UIEventSource<undefined | "true" | "false" | "mobile" | string> =
this.osmConnection.GetPreference("translation-mode", "false") this.osmConnection.GetPreference("translation-mode", "false")
@ -90,10 +99,13 @@ export default class UserRelatedState {
) )
this.language = this.osmConnection.GetPreference("language") this.language = this.osmConnection.GetPreference("language")
this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags") this.showTags = <UIEventSource<any>>this.osmConnection.GetPreference("show_tags")
this.fixateNorth = <any>this.osmConnection.GetPreference("fixate-north") this.fixateNorth = <UIEventSource<"yes">>this.osmConnection.GetPreference("fixate-north")
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")
) )
this.preferredBackgroundLayer= this.osmConnection.GetPreference("preferred-background-layer", undefined, {
documentation: "The ID of a layer or layer category that MapComplete uses by default"
})
this.installedUserThemes = this.InitInstalledUserThemes() this.installedUserThemes = this.InitInstalledUserThemes()
@ -260,6 +272,7 @@ export default class UserRelatedState {
amendedPrefs.data["__url_parameter_initialized:" + key] = "yes" amendedPrefs.data["__url_parameter_initialized:" + key] = "yes"
} }
const osmConnection = this.osmConnection const osmConnection = this.osmConnection
osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => { osmConnection.preferencesHandler.preferences.addCallback((newPrefs) => {
for (const k in newPrefs) { for (const k in newPrefs) {
@ -280,7 +293,6 @@ export default class UserRelatedState {
amendedPrefs.ping() amendedPrefs.ping()
console.log("Amended prefs are:", amendedPrefs.data) console.log("Amended prefs are:", amendedPrefs.data)
}) })
const usersettingsConfig = UserRelatedState.usersettingsConfig
const translationMode = osmConnection.GetPreference("translation-mode") const translationMode = osmConnection.GetPreference("translation-mode")
Locale.language.mapD( Locale.language.mapD(
@ -335,25 +347,6 @@ export default class UserRelatedState {
} }
usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data }) usersettingMetaTagging.metaTaggging_for_usersettings({ properties: amendedPrefs.data })
/*for (const [name, code, _] of usersettingsConfig.calculatedTags) {
try {
let result = new Function("feat", "return " + code + ";")({
properties: amendedPrefs.data,
})
if (result !== undefined && result !== "" && result !== null) {
if (typeof result !== "string") {
result = JSON.stringify(result)
}
amendedPrefs.data[name] = result
}
} catch (e) {
console.error(
"Calculating a tag for userprofile-settings failed for variable",
name,
e
)
}
}*/
const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "") const simplifiedName = userDetails.name.toLowerCase().replace(/\s+/g, "")
const isTranslator = translators.contributors.find( const isTranslator = translators.contributors.find(
@ -407,6 +400,13 @@ export default class UserRelatedState {
} }
} }
this._mapProperties?.rasterLayer?.addCallbackAndRun(l => {
amendedPrefs.data["__current_background"] = l?.properties?.id
amendedPrefs.ping()
})
return amendedPrefs return amendedPrefs
} }
} }

View file

@ -9,5 +9,6 @@ export class ThemeMetaTagging {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) ) Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a ) Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
Utils.AddLazyProperty(feat.properties, '__backgroundLayerId', () => 'some value to let the validation run through' )
} }
} }

View file

@ -1,59 +1,56 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig" import LayoutConfig from "./ThemeConfig/LayoutConfig";
import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { SpecialVisualizationState } from "../UI/SpecialVisualization";
import { Changes } from "../Logic/Osm/Changes" import { Changes } from "../Logic/Osm/Changes";
import { ImmutableStore, Store, UIEventSource } from "../Logic/UIEventSource" import { Store, UIEventSource } from "../Logic/UIEventSource";
import { 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 { import {
FeatureSource, NewGeometryFromChangesFeatureSource
IndexedFeatureSource, } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource";
WritableFeatureSource, import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader";
} from "../Logic/FeatureSource/FeatureSource" import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer";
import { OsmConnection } from "../Logic/Osm/OsmConnection" import { Utils } from "../Utils";
import { ExportableMap, MapProperties } from "./MapProperties" import { EliCategory } from "./RasterLayerProperties";
import LayerState from "../Logic/State/LayerState" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter";
import { Feature, Point, Polygon } from "geojson" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage";
import FullNodeDatabaseSource from "../Logic/FeatureSource/TiledFeatureSource/FullNodeDatabaseSource" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource";
import { Map as MlMap } from "maplibre-gl" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor";
import InitialMapPositioning from "../Logic/Actors/InitialMapPositioning" import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector";
import { MapLibreAdaptor } from "../UI/Map/MapLibreAdaptor" import FilteredLayer from "./FilteredLayer";
import { GeoLocationState } from "../Logic/State/GeoLocationState" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector";
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"
/** /**
* *
@ -64,69 +61,69 @@ import FilteredLayer from "./FilteredLayer"
* It ties up all the needed elements and starts some actors. * It ties up all the needed elements and starts some actors.
*/ */
export default class ThemeViewState implements SpecialVisualizationState { export default class ThemeViewState implements SpecialVisualizationState {
readonly layout: LayoutConfig readonly layout: LayoutConfig;
readonly map: UIEventSource<MlMap> readonly map: UIEventSource<MlMap>;
readonly changes: Changes readonly changes: Changes;
readonly featureSwitches: FeatureSwitchState readonly featureSwitches: FeatureSwitchState;
readonly featureSwitchIsTesting: Store<boolean> readonly featureSwitchIsTesting: Store<boolean>;
readonly featureSwitchUserbadge: Store<boolean> readonly featureSwitchUserbadge: Store<boolean>;
readonly featureProperties: FeaturePropertiesStore readonly featureProperties: FeaturePropertiesStore;
readonly osmConnection: OsmConnection readonly osmConnection: OsmConnection;
readonly selectedElement: UIEventSource<Feature> readonly selectedElement: UIEventSource<Feature>;
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }> readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>;
readonly mapProperties: MapProperties & ExportableMap readonly mapProperties: MapProperties & ExportableMap;
readonly osmObjectDownloader: OsmObjectDownloader readonly osmObjectDownloader: OsmObjectDownloader;
readonly dataIsLoading: Store<boolean> readonly dataIsLoading: Store<boolean>;
/** /**
* Indicates if there is _some_ data in view, even if it is not shown due to the filters * Indicates if there is _some_ data in view, even if it is not shown due to the filters
*/ */
readonly hasDataInView: Store<FeatureViewState> readonly hasDataInView: Store<FeatureViewState>;
readonly guistate: MenuState readonly guistate: MenuState;
readonly fullNodeDatabase?: FullNodeDatabaseSource readonly fullNodeDatabase?: FullNodeDatabaseSource;
readonly historicalUserLocations: WritableFeatureSource<Feature<Point>> readonly historicalUserLocations: WritableFeatureSource<Feature<Point>>;
readonly indexedFeatures: IndexedFeatureSource & LayoutSource readonly indexedFeatures: IndexedFeatureSource & LayoutSource;
readonly currentView: FeatureSource<Feature<Polygon>> readonly currentView: FeatureSource<Feature<Polygon>>;
readonly featuresInView: FeatureSource readonly featuresInView: FeatureSource;
readonly newFeatures: WritableFeatureSource readonly newFeatures: WritableFeatureSource;
readonly layerState: LayerState readonly layerState: LayerState;
readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> readonly perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer>;
readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource> readonly perLayerFiltered: ReadonlyMap<string, FilteringFeatureSource>;
readonly availableLayers: Store<RasterLayerPolygon[]> readonly availableLayers: Store<RasterLayerPolygon[]>;
readonly selectedLayer: UIEventSource<LayerConfig> readonly selectedLayer: UIEventSource<LayerConfig>;
readonly userRelatedState: UserRelatedState readonly userRelatedState: UserRelatedState;
readonly geolocation: GeoLocationHandler readonly geolocation: GeoLocationHandler;
readonly lastClickObject: WritableFeatureSource readonly lastClickObject: WritableFeatureSource;
readonly overlayLayerStates: ReadonlyMap< readonly overlayLayerStates: ReadonlyMap<
string, string,
{ readonly isDisplayed: UIEventSource<boolean> } { readonly isDisplayed: UIEventSource<boolean> }
> >;
/** /**
* All 'level'-tags that are available with the current features * All 'level'-tags that are available with the current features
*/ */
readonly floors: Store<string[]> readonly floors: Store<string[]>;
constructor(layout: LayoutConfig) { constructor(layout: LayoutConfig) {
Utils.initDomPurify() Utils.initDomPurify();
this.layout = layout this.layout = layout;
this.featureSwitches = new FeatureSwitchState(layout) this.featureSwitches = new FeatureSwitchState(layout);
this.guistate = new MenuState( this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data, this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id layout.id
) );
this.map = new UIEventSource<MlMap>(undefined) this.map = new UIEventSource<MlMap>(undefined);
const initial = new InitialMapPositioning(layout) const initial = new InitialMapPositioning(layout);
this.mapProperties = new MapLibreAdaptor(this.map, initial) this.mapProperties = new MapLibreAdaptor(this.map, initial);
const geolocationState = new GeoLocationState() const geolocationState = new GeoLocationState();
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting;
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin;
this.osmConnection = new OsmConnection({ this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting, dryRun: this.featureSwitches.featureSwitchIsTesting,
@ -136,65 +133,67 @@ export default class ThemeViewState implements SpecialVisualizationState {
undefined, undefined,
"Used to complete the login" "Used to complete the login"
), ),
osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data, osmConfiguration: <"osm" | "osm-test">this.featureSwitches.featureSwitchApiURL.data
}) });
this.userRelatedState = new UserRelatedState( this.userRelatedState = new UserRelatedState(
this.osmConnection, this.osmConnection,
layout?.language, layout?.language,
layout, layout,
this.featureSwitches this.featureSwitches,
) this.mapProperties
);
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => { this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
this.mapProperties.allowRotating.setData(fixated !== "yes") this.mapProperties.allowRotating.setData(fixated !== "yes");
}) });
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element") this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element");
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer") this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer");
this.selectedElementAndLayer = this.selectedElement.mapD( this.selectedElementAndLayer = this.selectedElement.mapD(
(feature) => { (feature) => {
const layer = this.selectedLayer.data const layer = this.selectedLayer.data;
if (!layer) { if (!layer) {
return undefined return undefined;
} }
return { layer, feature } return { layer, feature };
}, },
[this.selectedLayer] [this.selectedLayer]
) );
this.geolocation = new GeoLocationHandler( this.geolocation = new GeoLocationHandler(
geolocationState, geolocationState,
this.selectedElement, this.selectedElement,
this.mapProperties, this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime this.userRelatedState.gpsLocationHistoryRetentionTime
) );
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location) this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location);
const self = this const self = this;
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id);
{ {
const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>() const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>();
for (const rasterInfo of this.layout.tileLayerSources) { for (const rasterInfo of this.layout.tileLayerSources) {
const isDisplayed = QueryParameters.GetBooleanQueryParameter( const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id, "overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true, rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown" "Wether or not overlayer layer " + rasterInfo.id + " is shown"
) );
const state = { isDisplayed } const state = { isDisplayed };
overlayLayerStates.set(rasterInfo.id, state) overlayLayerStates.set(rasterInfo.id, state);
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state);
} }
this.overlayLayerStates = overlayLayerStates this.overlayLayerStates = overlayLayerStates;
} }
{ {
/* Setup the layout source /* 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 * 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)) { if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
this.fullNodeDatabase = new FullNodeDatabaseSource() this.fullNodeDatabase = new FullNodeDatabaseSource();
} }
const layoutSource = new LayoutSource( const layoutSource = new LayoutSource(
@ -204,49 +203,49 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection.Backend(), this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed, (id) => self.layerState.filteredLayers.get(id).isDisplayed,
this.fullNodeDatabase this.fullNodeDatabase
) );
this.indexedFeatures = layoutSource this.indexedFeatures = layoutSource;
const empty = [] const empty = [];
let currentViewIndex = 0 let currentViewIndex = 0;
this.currentView = new StaticFeatureSource( this.currentView = new StaticFeatureSource(
this.mapProperties.bounds.map((bbox) => { this.mapProperties.bounds.map((bbox) => {
if (!bbox) { if (!bbox) {
return empty return empty;
} }
currentViewIndex++ currentViewIndex++;
return <Feature[]>[ return <Feature[]>[
bbox.asGeoJson({ bbox.asGeoJson({
zoom: this.mapProperties.zoom.data, zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data, ...this.mapProperties.location.data,
id: "current_view", id: "current_view"
}), })
] ];
}) })
) );
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds) this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds);
this.dataIsLoading = layoutSource.isLoading this.dataIsLoading = layoutSource.isLoading;
const indexedElements = this.indexedFeatures const indexedElements = this.indexedFeatures;
this.featureProperties = new FeaturePropertiesStore(indexedElements) this.featureProperties = new FeaturePropertiesStore(indexedElements);
this.changes = new Changes( this.changes = new Changes(
{ {
dryRun: this.featureSwitches.featureSwitchIsTesting, dryRun: this.featureSwitches.featureSwitchIsTesting,
allElements: indexedElements, allElements: indexedElements,
featurePropertiesStore: this.featureProperties, featurePropertiesStore: this.featureProperties,
osmConnection: this.osmConnection, osmConnection: this.osmConnection,
historicalUserLocations: this.geolocation.historicalUserLocations, historicalUserLocations: this.geolocation.historicalUserLocations
}, },
layout?.isLeftRightSensitive() ?? false layout?.isLeftRightSensitive() ?? false
) );
this.historicalUserLocations = this.geolocation.historicalUserLocations this.historicalUserLocations = this.geolocation.historicalUserLocations;
this.newFeatures = new NewGeometryFromChangesFeatureSource( this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes, this.changes,
indexedElements, indexedElements,
this.featureProperties this.featureProperties
) );
layoutSource.addSource(this.newFeatures) layoutSource.addSource(this.newFeatures);
const perLayer = new PerLayerFeatureSourceSplitter( const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter( Array.from(this.layerState.filteredLayers.values()).filter(
@ -262,11 +261,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
features.length, features.length,
"leftover features, such as", "leftover features, such as",
features[0].properties features[0].properties
) );
}, }
} }
) );
this.perLayer = perLayer.perLayer this.perLayer = perLayer.perLayer;
} }
this.perLayer.forEach((fs) => { this.perLayer.forEach((fs) => {
new SaveFeatureSourceToLocalStorage( new SaveFeatureSourceToLocalStorage(
@ -276,73 +275,73 @@ export default class ThemeViewState implements SpecialVisualizationState {
fs, fs,
this.featureProperties, this.featureProperties,
fs.layer.layerDef.maxAgeOfCache fs.layer.layerDef.maxAgeOfCache
) );
}) });
this.floors = this.featuresInView.features.stabilized(500).map((features) => { this.floors = this.featuresInView.features.stabilized(500).map((features) => {
if (!features) { if (!features) {
return [] return [];
} }
const floors = new Set<string>() const floors = new Set<string>();
for (const feature of features) { for (const feature of features) {
const level = feature.properties["level"] const level = feature.properties["level"];
if (level) { if (level) {
const levels = level.split(";") const levels = level.split(";");
for (const l of levels) { for (const l of levels) {
floors.add(l) floors.add(l);
} }
} else { } else {
floors.add("0") // '0' is the default and is thus _always_ present floors.add("0"); // '0' is the default and is thus _always_ present
} }
} }
const sorted = Array.from(floors) const sorted = Array.from(floors);
// Sort alphabetically first, to deal with floor "A", "B" and "C" // Sort alphabetically first, to deal with floor "A", "B" and "C"
sorted.sort() sorted.sort();
sorted.sort((a, b) => { sorted.sort((a, b) => {
// We use the laxer 'parseInt' to deal with floor '1A' // We use the laxer 'parseInt' to deal with floor '1A'
const na = parseInt(a) const na = parseInt(a);
const nb = parseInt(b) const nb = parseInt(b);
if (isNaN(na) || isNaN(nb)) { if (isNaN(na) || isNaN(nb)) {
return 0 return 0;
} }
return na - nb return na - nb;
}) });
sorted.reverse(/* new list, no side-effects */) sorted.reverse(/* new list, no side-effects */);
return sorted return sorted;
}) });
const lastClick = (this.lastClickObject = new LastClickFeatureSource( const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation, this.mapProperties.lastClickLocation,
this.layout this.layout
)) ));
this.osmObjectDownloader = new OsmObjectDownloader( this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(), this.osmConnection.Backend(),
this.changes this.changes
) );
this.perLayerFiltered = this.showNormalDataOn(this.map) this.perLayerFiltered = this.showNormalDataOn(this.map);
this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView;
this.initActors() this.initActors();
this.addLastClick(lastClick) this.addLastClick(lastClick);
this.drawSpecialLayers() this.drawSpecialLayers();
this.initHotkeys() this.initHotkeys();
this.miscSetup() this.miscSetup();
if (!Utils.runningFromConsole) { if (!Utils.runningFromConsole) {
console.log("State setup completed", this) console.log("State setup completed", this);
} }
} }
public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> { public showNormalDataOn(map: Store<MlMap>): ReadonlyMap<string, FilteringFeatureSource> {
const filteringFeatureSource = new Map<string, FilteringFeatureSource>() const filteringFeatureSource = new Map<string, FilteringFeatureSource>();
this.perLayer.forEach((fs, layerName) => { this.perLayer.forEach((fs, layerName) => {
const doShowLayer = this.mapProperties.zoom.map( const doShowLayer = this.mapProperties.zoom.map(
(z) => (z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0), (fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
[fs.layer.isDisplayed] [fs.layer.isDisplayed]
) );
if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) { if (!doShowLayer.data && this.featureSwitches.featureSwitchFilter.data === false) {
/* 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 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)
@ -352,15 +351,15 @@ export default class ThemeViewState implements SpecialVisualizationState {
* Note: it is tempting to also permanently disable the layer if it is not visible _and_ the layer name is hidden. * 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! * However, this is _not_ correct: the layer might be hidden because zoom is not enough. Zooming in more _will_ reveal the layer!
* */ * */
return return;
} }
const filtered = new FilteringFeatureSource( const filtered = new FilteringFeatureSource(
fs.layer, fs.layer,
fs, fs,
(id) => this.featureProperties.getStore(id), (id) => this.featureProperties.getStore(id),
this.layerState.globalFilters this.layerState.globalFilters
) );
filteringFeatureSource.set(layerName, filtered) filteringFeatureSource.set(layerName, filtered);
new ShowDataLayer(map, { new ShowDataLayer(map, {
layer: fs.layer.layerDef, layer: fs.layer.layerDef,
@ -368,30 +367,30 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer, doShowLayer,
selectedElement: this.selectedElement, selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer, selectedLayer: this.selectedLayer,
fetchStore: (id) => this.featureProperties.getStore(id), fetchStore: (id) => this.featureProperties.getStore(id)
}) });
}) });
return filteringFeatureSource return filteringFeatureSource;
} }
/** /**
* Various small methods that need to be called * Various small methods that need to be called
*/ */
private miscSetup() { private miscSetup() {
this.userRelatedState.markLayoutAsVisited(this.layout) this.userRelatedState.markLayoutAsVisited(this.layout);
this.selectedElement.addCallbackAndRunD((feature) => { this.selectedElement.addCallbackAndRunD((feature) => {
// As soon as we have a selected element, we clear the selected element // 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 // 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 // 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") { if (feature.properties.id === "last_click") {
return return;
} }
this.lastClickObject.features.setData([]) this.lastClickObject.features.setData([]);
}) });
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) { if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
Utils.LoadCustomCss(this.layout.customCss) Utils.LoadCustomCss(this.layout.customCss);
} }
} }
@ -400,74 +399,74 @@ export default class ThemeViewState implements SpecialVisualizationState {
{ nomod: "Escape", onUp: true }, { nomod: "Escape", onUp: true },
Translations.t.hotkeyDocumentation.closeSidebar, Translations.t.hotkeyDocumentation.closeSidebar,
() => { () => {
this.selectedElement.setData(undefined) this.selectedElement.setData(undefined);
this.guistate.closeAll() this.guistate.closeAll();
} }
) );
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ {
nomod: "b", nomod: "b"
}, },
Translations.t.hotkeyDocumentation.openLayersPanel, Translations.t.hotkeyDocumentation.openLayersPanel,
() => { () => {
if (this.featureSwitches.featureSwitchFilter.data) { if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView() this.guistate.openFilterView();
} }
} }
) );
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ shift: "O" }, { shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik, Translations.t.hotkeyDocumentation.selectMapnik,
() => { () => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto) this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto);
} }
) );
const setLayerCategory = (category: EliCategory) => { const setLayerCategory = (category: EliCategory) => {
const available = this.availableLayers.data const available = this.availableLayers.data;
const current = this.mapProperties.rasterLayer const current = this.mapProperties.rasterLayer;
const best = RasterLayerUtils.SelectBestLayerAccordingTo( const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available, available,
category, category,
current.data current.data
) );
console.log("Best layer for category", category, "is", best.properties.id) console.log("Best layer for category", category, "is", best.properties.id);
current.setData(best) current.setData(best);
} };
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "O" }, { nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap, Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap") () => setLayerCategory("osmbasedmap")
) );
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () => Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map") setLayerCategory("map")
) );
Hotkeys.RegisterHotkey( Hotkeys.RegisterHotkey(
{ nomod: "P" }, { nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial, Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo") () => setLayerCategory("photo")
) );
} }
private addLastClick(last_click: LastClickFeatureSource) { private addLastClick(last_click: LastClickFeatureSource) {
// The last_click gets a _very_ special treatment as it interacts with various parts // The last_click gets a _very_ special treatment as it interacts with various parts
const last_click_layer = this.layerState.filteredLayers.get("last_click") const last_click_layer = this.layerState.filteredLayers.get("last_click");
this.featureProperties.trackFeatureSource(last_click) this.featureProperties.trackFeatureSource(last_click);
this.indexedFeatures.addSource(last_click) this.indexedFeatures.addSource(last_click);
last_click.features.addCallbackAndRunD((features) => { last_click.features.addCallbackAndRunD((features) => {
if (this.selectedLayer.data?.id === "last_click") { if (this.selectedLayer.data?.id === "last_click") {
// The last-click location moved, but we have selected the last click of the previous location // 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 // So, we update _after_ clearing the selection to make sure no stray data is sticking around
this.selectedElement.setData(undefined) this.selectedElement.setData(undefined);
this.selectedElement.setData(features[0]) this.selectedElement.setData(features[0]);
} }
}) });
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(last_click_layer, last_click), features: new FilteringFeatureSource(last_click_layer, last_click),
@ -479,18 +478,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) { if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({ this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint, zoom: Constants.minZoomLevelToAddNewPoint,
center: this.mapProperties.lastClickLocation.data, center: this.mapProperties.lastClickLocation.data
}) });
return return;
} }
// We first clear the selection to make sure no weird state is around // We first clear the selection to make sure no weird state is around
this.selectedLayer.setData(undefined) this.selectedLayer.setData(undefined);
this.selectedElement.setData(undefined) this.selectedElement.setData(undefined);
this.selectedElement.setData(feature) this.selectedElement.setData(feature);
this.selectedLayer.setData(last_click_layer.layerDef) this.selectedLayer.setData(last_click_layer.layerDef);
}, }
}) });
} }
/** /**
@ -498,7 +497,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/ */
private drawSpecialLayers() { private drawSpecialLayers() {
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number] type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
const empty = [] const empty = [];
/** /**
* A listing which maps the layerId onto the featureSource * A listing which maps the layerId onto the featureSource
*/ */
@ -518,21 +517,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })] bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
) )
), ),
current_view: this.currentView, current_view: this.currentView
} };
if (this.layout?.lockLocation) { if (this.layout?.lockLocation) {
const bbox = new BBox(this.layout.lockLocation) const bbox = new BBox(this.layout.lockLocation);
this.mapProperties.maxbounds.setData(bbox) this.mapProperties.maxbounds.setData(bbox);
ShowDataLayer.showRange( ShowDataLayer.showRange(
this.map, this.map,
new StaticFeatureSource([bbox.asGeoJson({})]), new StaticFeatureSource([bbox.asGeoJson({})]),
this.featureSwitches.featureSwitchIsTesting this.featureSwitches.featureSwitchIsTesting
) );
} }
const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view") const currentViewLayer = this.layout.layers.find((l) => l.id === "current_view");
if (currentViewLayer?.tagRenderings?.length > 0) { if (currentViewLayer?.tagRenderings?.length > 0) {
const params = MetaTagging.createExtraFuncParams(this) const params = MetaTagging.createExtraFuncParams(this);
this.featureProperties.trackFeatureSource(specialLayers.current_view) this.featureProperties.trackFeatureSource(specialLayers.current_view);
specialLayers.current_view.features.addCallbackAndRunD((features) => { specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags( MetaTagging.addMetatags(
features, features,
@ -541,37 +540,37 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.layout, this.layout,
this.osmObjectDownloader, this.osmObjectDownloader,
this.featureProperties this.featureProperties
) );
}) });
} }
const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range") const rangeFLayer: FilteredLayer = this.layerState.filteredLayers.get("range");
const rangeIsDisplayed = rangeFLayer?.isDisplayed const rangeIsDisplayed = rangeFLayer?.isDisplayed;
if ( if (
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef)) !QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
) { ) {
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true) rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true);
} }
this.layerState.filteredLayers.forEach((flayer) => { this.layerState.filteredLayers.forEach((flayer) => {
const id = flayer.layerDef.id const id = flayer.layerDef.id;
const features: FeatureSource = specialLayers[id] const features: FeatureSource = specialLayers[id];
if (features === undefined) { if (features === undefined) {
return return;
} }
this.featureProperties.trackFeatureSource(features) this.featureProperties.trackFeatureSource(features);
// this.indexedFeatures.addSource(features) // this.indexedFeatures.addSource(features)
new ShowDataLayer(this.map, { new ShowDataLayer(this.map, {
features, features,
doShowLayer: flayer.isDisplayed, doShowLayer: flayer.isDisplayed,
layer: flayer.layerDef, layer: flayer.layerDef,
selectedElement: this.selectedElement, selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer, selectedLayer: this.selectedLayer
}) });
}) });
} }
/** /**
@ -580,29 +579,30 @@ export default class ThemeViewState implements SpecialVisualizationState {
private initActors() { private initActors() {
// Unselect the selected element if it is panned out of view // Unselect the selected element if it is panned out of view
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
const selected = this.selectedElement.data const selected = this.selectedElement.data;
if (selected === undefined) { if (selected === undefined) {
return return;
} }
const bbox = BBox.get(selected) const bbox = BBox.get(selected);
if (!bbox.overlapsWith(bounds)) { if (!bbox.overlapsWith(bounds)) {
this.selectedElement.setData(undefined) this.selectedElement.setData(undefined);
} }
}) });
this.selectedElement.addCallback((selected) => { this.selectedElement.addCallback((selected) => {
if (selected === undefined) { if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object // We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([]) this.lastClickObject.features.setData([]);
this.selectedLayer.setData(undefined) this.selectedLayer.setData(undefined);
} }
}) });
new ThemeViewStateHashActor(this) new ThemeViewStateHashActor(this);
new MetaTagging(this) new MetaTagging(this);
new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this);
new ChangeToElementsActor(this.changes, this.featureProperties) new ChangeToElementsActor(this.changes, this.featureProperties);
new PendingChangesUploader(this.changes, this.selectedElement) new PendingChangesUploader(this.changes, this.selectedElement);
new SelectedElementTagsUpdater(this) new SelectedElementTagsUpdater(this);
new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers) new BackgroundLayerResetter(this.mapProperties.rasterLayer, this.availableLayers);
new PreferredRasterLayerSelector(this.mapProperties.rasterLayer, this.availableLayers, this.featureSwitches.backgroundLayerId, this.userRelatedState.preferredBackgroundLayer)
} }
} }

View file

@ -92,7 +92,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
maplibreMap.addCallbackAndRunD((map) => { maplibreMap.addCallbackAndRunD((map) => {
map.on("load", () => { map.on("load", () => {
self.setBackground() map.resize()
self.MoveMapToCurrentLoc(self.location.data) self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data) self.SetZoom(self.zoom.data)
self.setMaxBounds(self.maxbounds.data) self.setMaxBounds(self.maxbounds.data)
@ -102,8 +102,10 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data) self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data) self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data) self.setBounds(self.bounds.data)
self.setBackground()
this.updateStores(true) this.updateStores(true)
}) })
map.resize()
self.MoveMapToCurrentLoc(self.location.data) self.MoveMapToCurrentLoc(self.location.data)
self.SetZoom(self.zoom.data) self.SetZoom(self.zoom.data)
self.setMaxBounds(self.maxbounds.data) self.setMaxBounds(self.maxbounds.data)
@ -113,6 +115,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
self.setMinzoom(self.minzoom.data) self.setMinzoom(self.minzoom.data)
self.setMaxzoom(self.maxzoom.data) self.setMaxzoom(self.maxzoom.data)
self.setBounds(self.bounds.data) self.setBounds(self.bounds.data)
self.setBackground()
this.updateStores(true) this.updateStores(true)
map.on("moveend", () => this.updateStores()) map.on("moveend", () => this.updateStores())
map.on("click", (e) => { map.on("click", (e) => {
@ -126,7 +129,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
}) })
}) })
this.rasterLayer.addCallback((_) => this.rasterLayer.addCallbackAndRun((_) =>
self.setBackground().catch((_) => { self.setBackground().catch((_) => {
console.error("Could not set background") console.error("Could not set background")
}) })
@ -417,7 +420,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
if (!map.getSource(background.id)) { if (!map.getSource(background.id)) {
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
} }
map.resize()
if (!map.getLayer(background.id)) { if (!map.getLayer(background.id)) {
map.addLayer( map.addLayer(
{ {
@ -430,7 +432,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
) )
} }
await this.awaitStyleIsLoaded() await this.awaitStyleIsLoaded()
this.removeCurrentLayer(map) if(this._currentRasterLayer !== background?.id){
this.removeCurrentLayer(map)
}
this._currentRasterLayer = background?.id this._currentRasterLayer = background?.id
} }