forked from MapComplete/MapComplete
		
	More refactoring of the featuresources, cleanup, small changes
This commit is contained in:
		
							parent
							
								
									d144f70ffb
								
							
						
					
					
						commit
						c11ff652b8
					
				
					 7 changed files with 121 additions and 79 deletions
				
			
		|  | @ -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<LayoutConfig>) { |  | ||||||
|         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) |  | ||||||
|             } |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										20
									
								
								Logic/FeatureSource/Sources/StaticFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								Logic/FeatureSource/Sources/StaticFeatureSource.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  |         }))) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -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<FeatureSourceForLayer & Tiled> { | ||||||
|  |     public readonly loadedTiles: Map<number, FeatureSourceForLayer & Tiled> = new Map<number, FeatureSourceForLayer & Tiled>(); | ||||||
|  |     private readonly sources: Map<number, UIEventSource<FeatureSource[]>> = new Map<number, UIEventSource<FeatureSource[]>>(); | ||||||
|  | 
 | ||||||
|  |     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<FeatureSource[]>([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) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
							
								
								
									
										0
									
								
								UI/Base/MinimapImplementation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								UI/Base/MinimapImplementation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -3,48 +3,46 @@ | ||||||
|  */ |  */ | ||||||
| import {UIEventSource} from "../Logic/UIEventSource"; | import {UIEventSource} from "../Logic/UIEventSource"; | ||||||
| import * as L from "leaflet" | import * as L from "leaflet" | ||||||
| import "leaflet.markercluster" |  | ||||||
| import State from "../State"; | import State from "../State"; | ||||||
| import FeatureInfoBox from "./Popup/FeatureInfoBox"; | import FeatureInfoBox from "./Popup/FeatureInfoBox"; | ||||||
| import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; |  | ||||||
| import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../Models/ThemeConfig/LayerConfig"; | ||||||
|  | import FeatureSource from "../Logic/FeatureSource/FeatureSource"; | ||||||
| 
 | 
 | ||||||
|  | export interface ShowDataLayerOptions { | ||||||
|  |     features: FeatureSource, | ||||||
|  |     leafletMap: UIEventSource<L.Map>, | ||||||
|  |     enablePopups?: true | boolean, | ||||||
|  |     zoomToFeatures? : false | boolean, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| export default class ShowDataLayer { | export default class ShowDataLayer { | ||||||
| 
 | 
 | ||||||
|     private _layerDict; |  | ||||||
|     private readonly _leafletMap: UIEventSource<L.Map>; |     private readonly _leafletMap: UIEventSource<L.Map>; | ||||||
|     private _cleanCount = 0; |  | ||||||
|     private readonly _enablePopups: boolean; |     private readonly _enablePopups: boolean; | ||||||
|     private readonly _features: UIEventSource<{ feature: any }[]> |     private readonly _features: UIEventSource<{ feature: any }[]> | ||||||
|  |     private readonly _layerToShow: LayerConfig; | ||||||
| 
 | 
 | ||||||
|     constructor(features: UIEventSource<{ feature: any }[]>, |     // Used to generate a fresh ID when needed
 | ||||||
|                 leafletMap: UIEventSource<L.Map>, |     private _cleanCount = 0; | ||||||
|                 layoutToUse: UIEventSource<LayoutConfig>, |      | ||||||
|                 enablePopups = true, |     constructor(options: ShowDataLayerOptions & { layerToShow: LayerConfig}) { | ||||||
|                 zoomToFeatures = false) { |         this._leafletMap = options.leafletMap; | ||||||
|         this._leafletMap = leafletMap; |         this._enablePopups = options.enablePopups ?? true; | ||||||
|         this._enablePopups = enablePopups; |         if(options.features === undefined){ | ||||||
|  |             throw "Invalid ShowDataLayer invocation" | ||||||
|  |         } | ||||||
|  |         const features = options.features.features.map(featFreshes => featFreshes.map(ff => ff.feature)); | ||||||
|         this._features = features; |         this._features = features; | ||||||
|  |         this._layerToShow = options.layerToShow; | ||||||
|         const self = this; |         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 geoLayer = undefined; | ||||||
|         let cluster = undefined; |  | ||||||
| 
 | 
 | ||||||
|         function update() { |         function update() { | ||||||
|             if (features.data === undefined) { |             if (features.data === undefined) { | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             const mp = leafletMap.data; |             const mp =options. leafletMap.data; | ||||||
| 
 | 
 | ||||||
|             if (mp === undefined) { |             if (mp === undefined) { | ||||||
|                 return; |                 return; | ||||||
|  | @ -55,11 +53,8 @@ export default class ShowDataLayer { | ||||||
|             if (geoLayer !== undefined) { |             if (geoLayer !== undefined) { | ||||||
|                 mp.removeLayer(geoLayer); |                 mp.removeLayer(geoLayer); | ||||||
|             } |             } | ||||||
|             if (cluster !== undefined) { |  | ||||||
|                 mp.removeLayer(cluster); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             const allFeats = features.data.map(ff => ff.feature); |             const allFeats = features.data; | ||||||
|             geoLayer = self.CreateGeojsonLayer(); |             geoLayer = self.CreateGeojsonLayer(); | ||||||
|             for (const feat of allFeats) { |             for (const feat of allFeats) { | ||||||
|                 if (feat === undefined) { |                 if (feat === undefined) { | ||||||
|  | @ -68,17 +63,10 @@ export default class ShowDataLayer { | ||||||
|                 // @ts-ignore
 |                 // @ts-ignore
 | ||||||
|                 geoLayer.addData(feat); |                 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) { |                 mp.addLayer(geoLayer) | ||||||
|  | 
 | ||||||
|  |             if (options.zoomToFeatures ?? false) { | ||||||
|                 try { |                 try { | ||||||
|                     mp.fitBounds(geoLayer.getBounds(), {animate: false}) |                     mp.fitBounds(geoLayer.getBounds(), {animate: false}) | ||||||
|                 } catch (e) { |                 } catch (e) { | ||||||
|  | @ -91,7 +79,7 @@ export default class ShowDataLayer { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         features.addCallback(() => update()); |         features.addCallback(() => update()); | ||||||
|         leafletMap.addCallback(() => update()); |         options.leafletMap.addCallback(() => update()); | ||||||
|         update(); |         update(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -99,8 +87,8 @@ export default class ShowDataLayer { | ||||||
|     private createStyleFor(feature) { |     private createStyleFor(feature) { | ||||||
|         const tagsSource = State.state.allElements.addOrGetElement(feature); |         const tagsSource = State.state.allElements.addOrGetElement(feature); | ||||||
|         // Every object is tied to exactly one layer
 |         // Every object is tied to exactly one layer
 | ||||||
|         const layer = this._layerDict[feature._matching_layer_id]; |         const layer = this._layerToShow  | ||||||
|         return layer?.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); |         return layer?.GenerateLeafletStyle(tagsSource, true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private pointToLayer(feature, latLng): L.Layer { |     private pointToLayer(feature, latLng): L.Layer { | ||||||
|  | @ -108,7 +96,7 @@ export default class ShowDataLayer { | ||||||
|         // We have to convert them to the appropriate icon
 |         // We have to convert them to the appropriate icon
 | ||||||
|         // Click handling is done in the next step
 |         // 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) { |         if (layer === undefined) { | ||||||
|             return; |             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) { |     private postProcessFeature(feature, leafletLayer: L.Layer) { | ||||||
|         const layer: LayerConfig = this._layerDict[feature._matching_layer_id]; |         const layer: LayerConfig = this._layerToShow | ||||||
|         if (layer === undefined) { |  | ||||||
|             console.warn("No layer found for object (probably a now disabled layer)", feature, this._layerDict) |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         if (layer.title === undefined || !this._enablePopups) { |         if (layer.title === undefined || !this._enablePopups) { | ||||||
|             // No popup action defined -> Don't do anything
 |             // No popup action defined -> Don't do anything
 | ||||||
|             // or probably a map in the popup - no popups needed!
 |             // or probably a map in the popup - no popups needed!
 | ||||||
							
								
								
									
										0
									
								
								UI/ShowDataLayer/ShowDataLayerOptions.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								UI/ShowDataLayer/ShowDataLayerOptions.ts
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										22
									
								
								UI/ShowDataLayer/ShowDataMultiLayer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								UI/ShowDataLayer/ShowDataMultiLayer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<FilteredLayer[]> }) { | ||||||
|  | 
 | ||||||
|  |         new PerLayerFeatureSourceSplitter(options.layers, (perLayer => { | ||||||
|  |                 const newOptions = { | ||||||
|  |                     layerToShow: perLayer.layer.layerDef, | ||||||
|  |                     ...options | ||||||
|  |                 } | ||||||
|  |                 new ShowDataLayer(newOptions) | ||||||
|  |             }), | ||||||
|  |             options.features) | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue