Fix: redraw line layers on background change

This commit is contained in:
Pieter Vander Vennet 2023-10-06 01:42:13 +02:00
parent a83499614d
commit 6e5fcdef20
7 changed files with 281 additions and 306 deletions

View file

@ -1,42 +1,14 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
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_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
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_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}

View file

@ -13,7 +13,6 @@ export interface MapProperties {
readonly allowMoving: UIEventSource<true | boolean>
readonly allowRotating: UIEventSource<true | boolean>
readonly lastClickLocation: Store<{ lon: number; lat: number }>
readonly allowZooming: UIEventSource<true | boolean>
}

View file

@ -61,7 +61,7 @@ export default class PointRenderingConfig extends WithContextLoader {
})
if (json.icon === undefined && json.label === undefined) {
throw `A point rendering should define at least an icon or a label`
throw `At ${context}: A point rendering should define at least an icon or a label`
}
if (this.location.size == 0) {

View file

@ -1,62 +1,58 @@
import LayoutConfig from "./ThemeConfig/LayoutConfig"
import { SpecialVisualizationState } from "../UI/SpecialVisualization"
import { Changes } from "../Logic/Osm/Changes"
import { Store, UIEventSource } from "../Logic/UIEventSource"
import LayoutConfig from "./ThemeConfig/LayoutConfig";
import { SpecialVisualizationState } from "../UI/SpecialVisualization";
import { Changes } from "../Logic/Osm/Changes";
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 {
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"
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";
/**
*
@ -67,71 +63,71 @@ import { Imgur } from "../Logic/ImageProviders/Imgur"
* It ties up all the needed elements and starts some actors.
*/
export default class ThemeViewState implements SpecialVisualizationState {
readonly layout: LayoutConfig
readonly map: UIEventSource<MlMap>
readonly changes: Changes
readonly featureSwitches: FeatureSwitchState
readonly featureSwitchIsTesting: Store<boolean>
readonly featureSwitchUserbadge: Store<boolean>
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 featureProperties: FeaturePropertiesStore;
readonly osmConnection: OsmConnection
readonly selectedElement: UIEventSource<Feature>
readonly selectedElementAndLayer: Store<{ feature: Feature; layer: LayerConfig }>
readonly mapProperties: MapProperties & ExportableMap
readonly osmObjectDownloader: OsmObjectDownloader
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>
readonly dataIsLoading: Store<boolean>;
/**
* 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 fullNodeDatabase?: FullNodeDatabaseSource
readonly guistate: MenuState;
readonly fullNodeDatabase?: FullNodeDatabaseSource;
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>
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>;
readonly availableLayers: Store<RasterLayerPolygon[]>
readonly selectedLayer: UIEventSource<LayerConfig>
readonly userRelatedState: UserRelatedState
readonly geolocation: GeoLocationHandler
readonly availableLayers: Store<RasterLayerPolygon[]>;
readonly selectedLayer: UIEventSource<LayerConfig>;
readonly userRelatedState: UserRelatedState;
readonly geolocation: GeoLocationHandler;
readonly imageUploadManager: ImageUploadManager
readonly imageUploadManager: ImageUploadManager;
readonly lastClickObject: WritableFeatureSource
readonly lastClickObject: WritableFeatureSource;
readonly overlayLayerStates: ReadonlyMap<
string,
{ readonly isDisplayed: UIEventSource<boolean> }
>
>;
/**
* All 'level'-tags that are available with the current features
*/
readonly floors: Store<string[]>
readonly floors: Store<string[]>;
constructor(layout: LayoutConfig) {
Utils.initDomPurify()
this.layout = layout
this.featureSwitches = new FeatureSwitchState(layout)
Utils.initDomPurify();
this.layout = layout;
this.featureSwitches = new FeatureSwitchState(layout);
this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id
)
this.map = new UIEventSource<MlMap>(undefined)
const initial = new InitialMapPositioning(layout)
this.mapProperties = new MapLibreAdaptor(this.map, initial)
const geolocationState = new GeoLocationState()
);
this.map = new UIEventSource<MlMap>(undefined);
const initial = new InitialMapPositioning(layout);
this.mapProperties = new MapLibreAdaptor(this.map, initial);
const geolocationState = new GeoLocationState();
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting;
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin;
this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting,
@ -141,56 +137,56 @@ export default class ThemeViewState implements SpecialVisualizationState {
undefined,
"Used to complete the login"
)
})
});
this.userRelatedState = new UserRelatedState(
this.osmConnection,
layout?.language,
layout,
this.featureSwitches,
this.mapProperties
)
);
this.userRelatedState.fixateNorth.addCallbackAndRunD((fixated) => {
this.mapProperties.allowRotating.setData(fixated !== "yes")
})
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element")
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer")
this.mapProperties.allowRotating.setData(fixated !== "yes");
});
this.selectedElement = new UIEventSource<Feature | undefined>(undefined, "Selected element");
this.selectedLayer = new UIEventSource<LayerConfig>(undefined, "Selected layer");
this.selectedElementAndLayer = this.selectedElement.mapD(
(feature) => {
const layer = this.selectedLayer.data
const layer = this.selectedLayer.data;
if (!layer) {
return undefined
return undefined;
}
return { layer, feature }
return { layer, feature };
},
[this.selectedLayer]
)
);
this.geolocation = new GeoLocationHandler(
geolocationState,
this.selectedElement,
this.mapProperties,
this.userRelatedState.gpsLocationHistoryRetentionTime
)
);
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location)
this.availableLayers = AvailableRasterLayers.layersAvailableAt(this.mapProperties.location);
const self = this
this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id)
const self = this;
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) {
const isDisplayed = QueryParameters.GetBooleanQueryParameter(
"overlay-" + rasterInfo.id,
rasterInfo.defaultState ?? true,
"Wether or not overlayer layer " + rasterInfo.id + " is shown"
)
const state = { isDisplayed }
overlayLayerStates.set(rasterInfo.id, state)
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state)
);
const state = { isDisplayed };
overlayLayerStates.set(rasterInfo.id, state);
new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state);
}
this.overlayLayerStates = overlayLayerStates
this.overlayLayerStates = overlayLayerStates;
}
{
@ -199,7 +195,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
if (this.layout.layers.some((l) => l._needsFullNodeDatabase)) {
this.fullNodeDatabase = new FullNodeDatabaseSource()
this.fullNodeDatabase = new FullNodeDatabaseSource();
}
const layoutSource = new LayoutSource(
@ -209,49 +205,49 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.osmConnection.Backend(),
(id) => self.layerState.filteredLayers.get(id).isDisplayed,
this.fullNodeDatabase
)
);
this.indexedFeatures = layoutSource
this.indexedFeatures = layoutSource;
const empty = []
let currentViewIndex = 0
const empty = [];
let currentViewIndex = 0;
this.currentView = new StaticFeatureSource(
this.mapProperties.bounds.map((bbox) => {
if (!bbox) {
return empty
return empty;
}
currentViewIndex++
currentViewIndex++;
return <Feature[]>[
bbox.asGeoJson({
zoom: this.mapProperties.zoom.data,
...this.mapProperties.location.data,
id: "current_view",
}),
]
id: "current_view"
})
];
})
)
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds)
this.dataIsLoading = layoutSource.isLoading
);
this.featuresInView = new BBoxFeatureSource(layoutSource, this.mapProperties.bounds);
this.dataIsLoading = layoutSource.isLoading;
const indexedElements = this.indexedFeatures
this.featureProperties = new FeaturePropertiesStore(indexedElements)
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,
historicalUserLocations: this.geolocation.historicalUserLocations,
historicalUserLocations: this.geolocation.historicalUserLocations
},
layout?.isLeftRightSensitive() ?? false
)
this.historicalUserLocations = this.geolocation.historicalUserLocations
);
this.historicalUserLocations = this.geolocation.historicalUserLocations;
this.newFeatures = new NewGeometryFromChangesFeatureSource(
this.changes,
indexedElements,
this.featureProperties
)
layoutSource.addSource(this.newFeatures)
);
layoutSource.addSource(this.newFeatures);
const perLayer = new PerLayerFeatureSourceSplitter(
Array.from(this.layerState.filteredLayers.values()).filter(
@ -267,11 +263,11 @@ export default class ThemeViewState implements SpecialVisualizationState {
features.length,
"leftover features, such as",
features[0].properties
)
},
);
}
}
)
this.perLayer = perLayer.perLayer
);
this.perLayer = perLayer.perLayer;
}
this.perLayer.forEach((fs) => {
new SaveFeatureSourceToLocalStorage(
@ -281,80 +277,80 @@ export default class ThemeViewState implements SpecialVisualizationState {
fs,
this.featureProperties,
fs.layer.layerDef.maxAgeOfCache
)
})
);
});
this.floors = this.featuresInView.features.stabilized(500).map((features) => {
if (!features) {
return []
return [];
}
const floors = new Set<string>()
const floors = new Set<string>();
for (const feature of features) {
let level = feature.properties["_level"]
let level = feature.properties["_level"];
if (level) {
const levels = level.split(";")
const levels = level.split(";");
for (const l of levels) {
floors.add(l)
floors.add(l);
}
} 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"
sorted.sort()
sorted.sort();
sorted.sort((a, b) => {
// We use the laxer 'parseInt' to deal with floor '1A'
const na = parseInt(a)
const nb = parseInt(b)
const na = parseInt(a);
const nb = parseInt(b);
if (isNaN(na) || isNaN(nb)) {
return 0
return 0;
}
return na - nb
})
sorted.reverse(/* new list, no side-effects */)
return sorted
})
return na - nb;
});
sorted.reverse(/* new list, no side-effects */);
return sorted;
});
const lastClick = (this.lastClickObject = new LastClickFeatureSource(
this.mapProperties.lastClickLocation,
this.layout
))
));
this.osmObjectDownloader = new OsmObjectDownloader(
this.osmConnection.Backend(),
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.imageUploadManager = new ImageUploadManager(
layout,
Imgur.singleton,
this.featureProperties,
this.osmConnection,
this.changes
)
);
this.initActors()
this.addLastClick(lastClick)
this.drawSpecialLayers()
this.initHotkeys()
this.miscSetup()
this.initActors();
this.addLastClick(lastClick);
this.drawSpecialLayers();
this.initHotkeys();
this.miscSetup();
if (!Utils.runningFromConsole) {
console.log("State setup completed", this)
console.log("State setup completed", this);
}
}
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) => {
const doShowLayer = this.mapProperties.zoom.map(
(z) =>
(fs.layer.isDisplayed?.data ?? true) && z >= (fs.layer.layerDef?.minzoom ?? 0),
[fs.layer.isDisplayed]
)
);
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)
@ -364,15 +360,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.
* 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(
fs.layer,
fs,
(id) => this.featureProperties.getStore(id),
this.layerState.globalFilters
)
filteringFeatureSource.set(layerName, filtered)
);
filteringFeatureSource.set(layerName, filtered);
new ShowDataLayer(map, {
layer: fs.layer.layerDef,
@ -380,30 +376,30 @@ export default class ThemeViewState implements SpecialVisualizationState {
doShowLayer,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
fetchStore: (id) => this.featureProperties.getStore(id),
})
})
return filteringFeatureSource
fetchStore: (id) => this.featureProperties.getStore(id)
});
});
return filteringFeatureSource;
}
/**
* Various small methods that need to be called
*/
private miscSetup() {
this.userRelatedState.markLayoutAsVisited(this.layout)
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") {
return
return;
}
this.lastClickObject.features.setData([])
})
this.lastClickObject.features.setData([]);
});
if (this.layout.customCss !== undefined && window.location.pathname.indexOf("theme") >= 0) {
Utils.LoadCustomCss(this.layout.customCss)
Utils.LoadCustomCss(this.layout.customCss);
}
}
@ -412,74 +408,74 @@ export default class ThemeViewState implements SpecialVisualizationState {
{ nomod: "Escape", onUp: true },
Translations.t.hotkeyDocumentation.closeSidebar,
() => {
this.selectedElement.setData(undefined)
this.guistate.closeAll()
this.selectedElement.setData(undefined);
this.guistate.closeAll();
}
)
);
Hotkeys.RegisterHotkey(
{
nomod: "b",
nomod: "b"
},
Translations.t.hotkeyDocumentation.openLayersPanel,
() => {
if (this.featureSwitches.featureSwitchFilter.data) {
this.guistate.openFilterView()
this.guistate.openFilterView();
}
}
)
);
Hotkeys.RegisterHotkey(
{ shift: "O" },
Translations.t.hotkeyDocumentation.selectMapnik,
() => {
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto)
this.mapProperties.rasterLayer.setData(AvailableRasterLayers.osmCarto);
}
)
);
const setLayerCategory = (category: EliCategory) => {
const available = this.availableLayers.data
const current = this.mapProperties.rasterLayer
const available = this.availableLayers.data;
const current = this.mapProperties.rasterLayer;
const best = RasterLayerUtils.SelectBestLayerAccordingTo(
available,
category,
current.data
)
console.log("Best layer for category", category, "is", best.properties.id)
current.setData(best)
}
);
console.log("Best layer for category", category, "is", best.properties.id);
current.setData(best);
};
Hotkeys.RegisterHotkey(
{ nomod: "O" },
Translations.t.hotkeyDocumentation.selectOsmbasedmap,
() => setLayerCategory("osmbasedmap")
)
);
Hotkeys.RegisterHotkey({ nomod: "M" }, Translations.t.hotkeyDocumentation.selectMap, () =>
setLayerCategory("map")
)
);
Hotkeys.RegisterHotkey(
{ nomod: "P" },
Translations.t.hotkeyDocumentation.selectAerial,
() => setLayerCategory("photo")
)
);
}
private addLastClick(last_click: LastClickFeatureSource) {
// The last_click gets a _very_ special treatment as it interacts with various parts
const last_click_layer = this.layerState.filteredLayers.get("last_click")
this.featureProperties.trackFeatureSource(last_click)
this.indexedFeatures.addSource(last_click)
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
this.selectedElement.setData(undefined)
this.selectedElement.setData(features[0])
this.selectedElement.setData(undefined);
this.selectedElement.setData(features[0]);
}
})
});
new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(last_click_layer, last_click),
@ -491,18 +487,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
if (this.mapProperties.zoom.data < Constants.minZoomLevelToAddNewPoint) {
this.map.data.flyTo({
zoom: Constants.minZoomLevelToAddNewPoint,
center: this.mapProperties.lastClickLocation.data,
})
return
center: this.mapProperties.lastClickLocation.data
});
return;
}
// We first clear the selection to make sure no weird state is around
this.selectedLayer.setData(undefined)
this.selectedElement.setData(undefined)
this.selectedLayer.setData(undefined);
this.selectedElement.setData(undefined);
this.selectedElement.setData(feature)
this.selectedLayer.setData(last_click_layer.layerDef)
},
})
this.selectedElement.setData(feature);
this.selectedLayer.setData(last_click_layer.layerDef);
}
});
}
/**
@ -510,7 +506,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
*/
private drawSpecialLayers() {
type AddedByDefaultTypes = (typeof Constants.added_by_default)[number]
const empty = []
const empty = [];
/**
* A listing which maps the layerId onto the featureSource
*/
@ -530,21 +526,21 @@ export default class ThemeViewState implements SpecialVisualizationState {
bbox === undefined ? empty : <Feature[]>[bbox.asGeoJson({ id: "range" })]
)
),
current_view: this.currentView,
}
current_view: this.currentView
};
if (this.layout?.lockLocation) {
const bbox = new BBox(this.layout.lockLocation)
this.mapProperties.maxbounds.setData(bbox)
const bbox = new BBox(this.layout.lockLocation);
this.mapProperties.maxbounds.setData(bbox);
ShowDataLayer.showRange(
this.map,
new StaticFeatureSource([bbox.asGeoJson({})]),
new StaticFeatureSource([bbox.asGeoJson({id: "range"})]),
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) {
const params = MetaTagging.createExtraFuncParams(this)
this.featureProperties.trackFeatureSource(specialLayers.current_view)
const params = MetaTagging.createExtraFuncParams(this);
this.featureProperties.trackFeatureSource(specialLayers.current_view);
specialLayers.current_view.features.addCallbackAndRunD((features) => {
MetaTagging.addMetatags(
features,
@ -553,37 +549,36 @@ export default class ThemeViewState implements SpecialVisualizationState {
this.layout,
this.osmObjectDownloader,
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 (
!QueryParameters.wasInitialized(FilteredLayer.queryParameterKey(rangeFLayer.layerDef))
) {
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true)
rangeIsDisplayed?.syncWith(this.featureSwitches.featureSwitchIsTesting, true);
}
this.layerState.filteredLayers.forEach((flayer) => {
const id = flayer.layerDef.id
const features: FeatureSource = specialLayers[id]
const id = flayer.layerDef.id;
const features: FeatureSource = specialLayers[id];
if (features === undefined) {
return
return;
}
this.featureProperties.trackFeatureSource(features)
// this.indexedFeatures.addSource(features)
this.featureProperties.trackFeatureSource(features);
new ShowDataLayer(this.map, {
features,
doShowLayer: flayer.isDisplayed,
layer: flayer.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,
})
})
selectedLayer: this.selectedLayer
});
});
}
/**
@ -592,35 +587,35 @@ export default class ThemeViewState implements SpecialVisualizationState {
private initActors() {
// Unselect the selected element if it is panned out of view
this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => {
const selected = this.selectedElement.data
const selected = this.selectedElement.data;
if (selected === undefined) {
return
return;
}
const bbox = BBox.get(selected)
const bbox = BBox.get(selected);
if (!bbox.overlapsWith(bounds)) {
this.selectedElement.setData(undefined)
this.selectedElement.setData(undefined);
}
})
});
this.selectedElement.addCallback((selected) => {
if (selected === undefined) {
// We did _unselect_ an item - we always remove the lastclick-object
this.lastClickObject.features.setData([])
this.selectedLayer.setData(undefined)
this.lastClickObject.features.setData([]);
this.selectedLayer.setData(undefined);
}
})
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 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
)
);
}
}

View file

@ -421,6 +421,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
}
if (!map.getLayer(background.id)) {
console.log("Adding background layer", background.id, "beforeId", addLayerBeforeId,"; all layers are", map.getStyle().layers.map(l => l.id))
map.addLayer(
{
id: background.id,

View file

@ -228,6 +228,10 @@ class LineRenderingLayer {
this._onClick = onClick
const self = this
features.features.addCallbackAndRunD(() => self.update(features.features))
map.on("styledata", () => self.update(features.features))
// map.on("style.load", () => self.update(features.features))
}
public destruct(): void {
@ -280,11 +284,12 @@ class LineRenderingLayer {
// As such, we only now read the features from the featureSource and compare with the previously set data
const features = featureSource.data
const src = <GeoJSONSource>map.getSource(this._layername)
if (this.currentSourceData === features) {
if (src !== undefined && this.currentSourceData === features && src._data === <any> features) {
// Already up to date
return
}
{// Add source to the map or update the features
{
// Add source to the map or update the feature source
if (src === undefined) {
this.currentSourceData = features;
map.addSource(this._layername, {
@ -312,6 +317,10 @@ class LineRenderingLayer {
});
for (const feature of features) {
if(!feature.properties.id){
console.warn("Feature without id:", feature)
continue
}
map.setFeatureState(
{ source: this._layername, id: feature.properties.id },
this.calculatePropsFor(feature.properties)
@ -411,6 +420,7 @@ class LineRenderingLayer {
}
}
}
}
export default class ShowDataLayer {
@ -418,7 +428,6 @@ export default class ShowDataLayer {
<LayerConfigJson>range_layer,
"ShowDataLayer.ts:range.json"
)
private readonly _map: Store<MlMap>
private readonly _options: ShowDataLayerOptions & {
layer: LayerConfig
drawMarkers?: true | boolean
@ -432,10 +441,9 @@ export default class ShowDataLayer {
options: ShowDataLayerOptions & {
layer: LayerConfig
drawMarkers?: true | boolean
drawLines?: true | boolean
drawLines?: true | boolean,
}
) {
this._map = map
this._options = options
const self = this
this.onDestroy.push(map.addCallbackAndRunD((map) => self.initDrawFeatures(map)))
@ -456,7 +464,7 @@ export default class ShowDataLayer {
}
)
perLayer.forEach((fs) => {
new ShowDataLayer(mlmap, {
new ShowDataLayer(mlmap,{
layer: fs.layer.layerDef,
features: fs,
...(options ?? {}),
@ -469,7 +477,7 @@ export default class ShowDataLayer {
features: FeatureSource,
doShowLayer?: Store<boolean>
): ShowDataLayer {
return new ShowDataLayer(map, {
return new ShowDataLayer(map,{
layer: ShowDataLayer.rangeLayer,
features,
doShowLayer,

View file

@ -116,7 +116,7 @@ class ApplyButton extends UIElement {
this.state.indexedFeatures.featuresById.data.get(id)
)
new ShowDataLayer(mlmap, {
new ShowDataLayer(mlmap,{
features: StaticFeatureSource.fromGeojson(features),
zoomToFeatures: true,
layer: this.layer.layerDef,