From c11ff652b8100a9344e2bb0b181830411b8fb61c Mon Sep 17 00:00:00 2001 From: pietervdvn Date: Tue, 21 Sep 2021 01:47:58 +0200 Subject: [PATCH] More refactoring of the featuresources, cleanup, small changes --- .../Sources/LocalStorageSource.ts | 35 --------- .../Sources/StaticFeatureSource.ts | 20 +++++ .../TiledFeatureSource/TileHierarchyMerger.ts | 45 +++++++++++ UI/Base/MinimapImplementation.ts | 0 UI/{ => ShowDataLayer}/ShowDataLayer.ts | 78 ++++++++----------- UI/ShowDataLayer/ShowDataLayerOptions.ts | 0 UI/ShowDataLayer/ShowDataMultiLayer.ts | 22 ++++++ 7 files changed, 121 insertions(+), 79 deletions(-) delete mode 100644 Logic/FeatureSource/Sources/LocalStorageSource.ts create mode 100644 Logic/FeatureSource/Sources/StaticFeatureSource.ts create mode 100644 Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts create mode 100644 UI/Base/MinimapImplementation.ts rename UI/{ => ShowDataLayer}/ShowDataLayer.ts (74%) create mode 100644 UI/ShowDataLayer/ShowDataLayerOptions.ts create mode 100644 UI/ShowDataLayer/ShowDataMultiLayer.ts diff --git a/Logic/FeatureSource/Sources/LocalStorageSource.ts b/Logic/FeatureSource/Sources/LocalStorageSource.ts deleted file mode 100644 index c8c28b5c01..0000000000 --- a/Logic/FeatureSource/Sources/LocalStorageSource.ts +++ /dev/null @@ -1,35 +0,0 @@ -import FeatureSource from "./FeatureSource"; -import {UIEventSource} from "../UIEventSource"; -import LocalStorageSaverActor from "./LocalStorageSaverActor"; -import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; - -export default class LocalStorageSource implements FeatureSource { - public features: UIEventSource<{ feature: any; freshness: Date }[]>; - public readonly name = "LocalStorageSource"; - - constructor(layout: UIEventSource) { - this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) - const key = LocalStorageSaverActor.storageKey + layout.data.id - layout.addCallbackAndRun(_ => { - try { - const fromStorage = localStorage.getItem(key); - if (fromStorage == null) { - return; - } - const loaded: { feature: any; freshness: Date | string }[] = - JSON.parse(fromStorage); - - const parsed: { feature: any; freshness: Date }[] = loaded.map(ff => ({ - feature: ff.feature, - freshness: typeof ff.freshness == "string" ? new Date(ff.freshness) : ff.freshness - })) - - this.features.setData(parsed); - console.log("Loaded ", loaded.length, " features from localstorage as cache") - } catch (e) { - console.log("Could not load features from localStorage:", e) - localStorage.removeItem(key) - } - }) - } -} \ No newline at end of file diff --git a/Logic/FeatureSource/Sources/StaticFeatureSource.ts b/Logic/FeatureSource/Sources/StaticFeatureSource.ts new file mode 100644 index 0000000000..7ffe1b02a2 --- /dev/null +++ b/Logic/FeatureSource/Sources/StaticFeatureSource.ts @@ -0,0 +1,20 @@ +import FeatureSource from "../FeatureSource"; +import {UIEventSource} from "../../UIEventSource"; + +/** + * A simple dummy implementation for whenever it is needed + */ +export default class StaticFeatureSource implements FeatureSource { + public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; + public readonly name: string = "StaticFeatureSource" + + constructor(features: any[]) { + const now = new Date(); + this.features = new UIEventSource(features.map(f => ({ + feature: f, + freshness: now + }))) + } + + +} \ No newline at end of file diff --git a/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts b/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts new file mode 100644 index 0000000000..768a935fc4 --- /dev/null +++ b/Logic/FeatureSource/TiledFeatureSource/TileHierarchyMerger.ts @@ -0,0 +1,45 @@ +import TileHierarchy from "./TiledFeatureSource/TileHierarchy"; +import FeatureSource, {FeatureSourceForLayer, Tiled} from "./FeatureSource"; +import {UIEventSource} from "../UIEventSource"; +import FilteredLayer from "../../Models/FilteredLayer"; +import FeatureSourceMerger from "./Sources/FeatureSourceMerger"; +import {BBox} from "../GeoOperations"; +import {Utils} from "../../Utils"; + +export class TileHierarchyMerger implements TileHierarchy { + public readonly loadedTiles: Map = new Map(); + private readonly sources: Map> = new Map>(); + + public readonly layer: FilteredLayer; + private _handleTile: (src: FeatureSourceForLayer, index: number) => void; + + constructor(layer: FilteredLayer, handleTile: (src: FeatureSourceForLayer, index: number) => void) { + this.layer = layer; + this._handleTile = handleTile; + } + + /** + * Add another feature source for the given tile. + * Entries for this tile will be merged + * @param src + * @param index + */ + public registerTile(src: FeatureSource, index: number) { + + if (this.sources.has(index)) { + const sources = this.sources.get(index) + sources.data.push(src) + sources.ping() + return; + } + + // We have to setup + const sources = new UIEventSource([src]) + this.sources.set(index, sources) + const merger = new FeatureSourceMerger(this.layer, index, BBox.fromTile(...Utils.tile_from_index(index)), sources) + this.loadedTiles.set(index, merger) + this._handleTile(merger, index) + } + + +} \ No newline at end of file diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer/ShowDataLayer.ts similarity index 74% rename from UI/ShowDataLayer.ts rename to UI/ShowDataLayer/ShowDataLayer.ts index ced30b29f8..661e51d07c 100644 --- a/UI/ShowDataLayer.ts +++ b/UI/ShowDataLayer/ShowDataLayer.ts @@ -3,48 +3,46 @@ */ import {UIEventSource} from "../Logic/UIEventSource"; import * as L from "leaflet" -import "leaflet.markercluster" import State from "../State"; import FeatureInfoBox from "./Popup/FeatureInfoBox"; -import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import LayerConfig from "../Models/ThemeConfig/LayerConfig"; +import FeatureSource from "../Logic/FeatureSource/FeatureSource"; +export interface ShowDataLayerOptions { + features: FeatureSource, + leafletMap: UIEventSource, + enablePopups?: true | boolean, + zoomToFeatures? : false | boolean, +} export default class ShowDataLayer { - private _layerDict; private readonly _leafletMap: UIEventSource; - private _cleanCount = 0; private readonly _enablePopups: boolean; private readonly _features: UIEventSource<{ feature: any }[]> + private readonly _layerToShow: LayerConfig; - constructor(features: UIEventSource<{ feature: any }[]>, - leafletMap: UIEventSource, - layoutToUse: UIEventSource, - enablePopups = true, - zoomToFeatures = false) { - this._leafletMap = leafletMap; - this._enablePopups = enablePopups; + // Used to generate a fresh ID when needed + private _cleanCount = 0; + + constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig}) { + this._leafletMap = options.leafletMap; + this._enablePopups = options.enablePopups ?? true; + if(options.features === undefined){ + throw "Invalid ShowDataLayer invocation" + } + const features = options.features.features.map(featFreshes => featFreshes.map(ff => ff.feature)); this._features = features; + this._layerToShow = options.layerToShow; const self = this; - self._layerDict = {}; - - layoutToUse.addCallbackAndRun(layoutToUse => { - for (const layer of layoutToUse.layers) { - if (self._layerDict[layer.id] === undefined) { - self._layerDict[layer.id] = layer; - } - } - }); let geoLayer = undefined; - let cluster = undefined; function update() { if (features.data === undefined) { return; } - const mp = leafletMap.data; + const mp =options. leafletMap.data; if (mp === undefined) { return; @@ -55,11 +53,8 @@ export default class ShowDataLayer { if (geoLayer !== undefined) { mp.removeLayer(geoLayer); } - if (cluster !== undefined) { - mp.removeLayer(cluster); - } - const allFeats = features.data.map(ff => ff.feature); + const allFeats = features.data; geoLayer = self.CreateGeojsonLayer(); for (const feat of allFeats) { if (feat === undefined) { @@ -68,17 +63,10 @@ export default class ShowDataLayer { // @ts-ignore geoLayer.addData(feat); } - if (layoutToUse.data.clustering.minNeededElements <= allFeats.length) { - // Activate clustering if it wasn't already activated - const cl = window["L"]; // This is a dirty workaround, the clustering plugin binds to the L of the window, not of the namespace or something - cluster = cl.markerClusterGroup({disableClusteringAtZoom: layoutToUse.data.clustering.maxZoom}); - cluster.addLayer(geoLayer); - mp.addLayer(cluster); - } else { + mp.addLayer(geoLayer) - } - if (zoomToFeatures) { + if (options.zoomToFeatures ?? false) { try { mp.fitBounds(geoLayer.getBounds(), {animate: false}) } catch (e) { @@ -91,7 +79,7 @@ export default class ShowDataLayer { } features.addCallback(() => update()); - leafletMap.addCallback(() => update()); + options.leafletMap.addCallback(() => update()); update(); } @@ -99,8 +87,8 @@ export default class ShowDataLayer { private createStyleFor(feature) { const tagsSource = State.state.allElements.addOrGetElement(feature); // Every object is tied to exactly one layer - const layer = this._layerDict[feature._matching_layer_id]; - return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); + const layer = this._layerToShow + return layer?.GenerateLeafletStyle(tagsSource, true); } private pointToLayer(feature, latLng): L.Layer { @@ -108,7 +96,7 @@ export default class ShowDataLayer { // We have to convert them to the appropriate icon // Click handling is done in the next step - const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; + const layer: LayerConfig = this._layerToShow if (layer === undefined) { return; } @@ -131,12 +119,14 @@ export default class ShowDataLayer { }); } + /** + * POst processing - basically adding the popup + * @param feature + * @param leafletLayer + * @private + */ private postProcessFeature(feature, leafletLayer: L.Layer) { - const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; - if (layer === undefined) { - console.warn("No layer found for object (probably a now disabled layer)", feature, this._layerDict) - return; - } + const layer: LayerConfig = this._layerToShow if (layer.title === undefined || !this._enablePopups) { // No popup action defined -> Don't do anything // or probably a map in the popup - no popups needed! diff --git a/UI/ShowDataLayer/ShowDataLayerOptions.ts b/UI/ShowDataLayer/ShowDataLayerOptions.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UI/ShowDataLayer/ShowDataMultiLayer.ts b/UI/ShowDataLayer/ShowDataMultiLayer.ts new file mode 100644 index 0000000000..5bff575cbc --- /dev/null +++ b/UI/ShowDataLayer/ShowDataMultiLayer.ts @@ -0,0 +1,22 @@ +import {UIEventSource} from "../Logic/UIEventSource"; +import FilteredLayer from "../Models/FilteredLayer"; +import ShowDataLayer, {ShowDataLayerOptions} from "./ShowDataLayer/ShowDataLayer"; +import PerLayerFeatureSourceSplitter from "../Logic/FeatureSource/PerLayerFeatureSourceSplitter"; + +/** + * SHows geojson on the given leaflet map, but attempts to figure out the correct layer first + */ +export default class ShowDataMultiLayer { + constructor(options: ShowDataLayerOptions & { layers: UIEventSource }) { + + new PerLayerFeatureSourceSplitter(options.layers, (perLayer => { + const newOptions = { + layerToShow: perLayer.layer.layerDef, + ...options + } + new ShowDataLayer(newOptions) + }), + options.features) + + } +} \ No newline at end of file