| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  | import FeatureSource, {FeatureSourceForLayer, Tiled} from "../../Logic/FeatureSource/FeatureSource"; | 
					
						
							|  |  |  | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | 
					
						
							|  |  |  | import {UIEventSource} from "../../Logic/UIEventSource"; | 
					
						
							|  |  |  | import {Tiles} from "../../Models/TileRange"; | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | import {BBox} from "../../Logic/BBox"; | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | import FilteredLayer from "../../Models/FilteredLayer"; | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * A feature source containing but a single feature, which keeps stats about a tile | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | export class TileHierarchyAggregator implements FeatureSource { | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     private static readonly empty = [] | 
					
						
							|  |  |  |     public totalValue: number = 0 | 
					
						
							|  |  |  |     public showCount: number = 0 | 
					
						
							|  |  |  |     public hiddenCount: number = 0 | 
					
						
							|  |  |  |     public readonly features = new UIEventSource<{ feature: any, freshness: Date }[]>(TileHierarchyAggregator.empty) | 
					
						
							|  |  |  |     public readonly name; | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |     private _parent: TileHierarchyAggregator; | 
					
						
							|  |  |  |     private _root: TileHierarchyAggregator; | 
					
						
							|  |  |  |     private _z: number; | 
					
						
							|  |  |  |     private _x: number; | 
					
						
							|  |  |  |     private _y: number; | 
					
						
							|  |  |  |     private _tileIndex: number | 
					
						
							|  |  |  |     private _counter: SingleTileCounter | 
					
						
							|  |  |  |     private _subtiles: [TileHierarchyAggregator, TileHierarchyAggregator, TileHierarchyAggregator, TileHierarchyAggregator] = [undefined, undefined, undefined, undefined] | 
					
						
							|  |  |  |     private readonly featuresStatic = [] | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |     private readonly featureProperties: { count: string, kilocount: string, tileId: string, id: string, showCount: string, totalCount: string }; | 
					
						
							|  |  |  |     private readonly _state: { filteredLayers: UIEventSource<FilteredLayer[]> }; | 
					
						
							|  |  |  |     private readonly updateSignal = new UIEventSource<any>(undefined) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |     private constructor(parent: TileHierarchyAggregator, | 
					
						
							|  |  |  |                         state: { | 
					
						
							|  |  |  |                             filteredLayers: UIEventSource<FilteredLayer[]> | 
					
						
							|  |  |  |                         }, | 
					
						
							|  |  |  |                         z: number, x: number, y: number) { | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         this._parent = parent; | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |         this._state = state; | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         this._root = parent?._root ?? this | 
					
						
							|  |  |  |         this._z = z; | 
					
						
							|  |  |  |         this._x = x; | 
					
						
							|  |  |  |         this._y = y; | 
					
						
							|  |  |  |         this._tileIndex = Tiles.tile_index(z, x, y) | 
					
						
							|  |  |  |         this.name = "Count(" + this._tileIndex + ")" | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         const totals = { | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |             id: "" + this._tileIndex, | 
					
						
							|  |  |  |             tileId: "" + this._tileIndex, | 
					
						
							| 
									
										
										
										
											2021-10-13 01:28:46 +02:00
										 |  |  |             count: `0`, | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |             kilocount: "0", | 
					
						
							|  |  |  |             showCount: "0", | 
					
						
							|  |  |  |             totalCount: "0" | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         this.featureProperties = totals | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const now = new Date() | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         const feature = { | 
					
						
							|  |  |  |             "type": "Feature", | 
					
						
							|  |  |  |             "properties": totals, | 
					
						
							|  |  |  |             "geometry": { | 
					
						
							|  |  |  |                 "type": "Point", | 
					
						
							|  |  |  |                 "coordinates": Tiles.centerPointOf(z, x, y) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         this.featuresStatic.push({feature: feature, freshness: now}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const bbox = BBox.fromTile(z, x, y) | 
					
						
							|  |  |  |         const box = { | 
					
						
							|  |  |  |             "type": "Feature", | 
					
						
							|  |  |  |             "properties": totals, | 
					
						
							|  |  |  |             "geometry": { | 
					
						
							|  |  |  |                 "type": "Polygon", | 
					
						
							|  |  |  |                 "coordinates": [ | 
					
						
							|  |  |  |                     [ | 
					
						
							|  |  |  |                         [bbox.minLon, bbox.minLat], | 
					
						
							|  |  |  |                         [bbox.minLon, bbox.maxLat], | 
					
						
							|  |  |  |                         [bbox.maxLon, bbox.maxLat], | 
					
						
							|  |  |  |                         [bbox.maxLon, bbox.minLat], | 
					
						
							|  |  |  |                         [bbox.minLon, bbox.minLat] | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |                     ] | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |                 ] | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         this.featuresStatic.push({feature: box, freshness: now}) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     public static createHierarchy(state: { filteredLayers: UIEventSource<FilteredLayer[]> }) { | 
					
						
							|  |  |  |         return new TileHierarchyAggregator(undefined, state, 0, 0, 0) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |     public getTile(tileIndex): TileHierarchyAggregator { | 
					
						
							|  |  |  |         if (tileIndex === this._tileIndex) { | 
					
						
							|  |  |  |             return this; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         let [tileZ, tileX, tileY] = Tiles.tile_from_index(tileIndex) | 
					
						
							|  |  |  |         while (tileZ - 1 > this._z) { | 
					
						
							|  |  |  |             tileX = Math.floor(tileX / 2) | 
					
						
							|  |  |  |             tileY = Math.floor(tileY / 2) | 
					
						
							|  |  |  |             tileZ-- | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const xDiff = tileX - (2 * this._x) | 
					
						
							|  |  |  |         const yDiff = tileY - (2 * this._y) | 
					
						
							|  |  |  |         const subtileIndex = yDiff * 2 + xDiff; | 
					
						
							|  |  |  |         return this._subtiles[subtileIndex]?.getTile(tileIndex) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     public addTile(source: FeatureSourceForLayer & Tiled) { | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         if (source.tileIndex === this._tileIndex) { | 
					
						
							|  |  |  |             if (this._counter === undefined) { | 
					
						
							|  |  |  |                 this._counter = new SingleTileCounter(this._tileIndex) | 
					
						
							|  |  |  |                 this._counter.countsPerLayer.addCallbackAndRun(_ => self.update()) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this._counter.addTileCount(source) | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // We have to give it to one of the subtiles
 | 
					
						
							|  |  |  |             let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex) | 
					
						
							|  |  |  |             while (tileZ - 1 > this._z) { | 
					
						
							|  |  |  |                 tileX = Math.floor(tileX / 2) | 
					
						
							|  |  |  |                 tileY = Math.floor(tileY / 2) | 
					
						
							|  |  |  |                 tileZ-- | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const xDiff = tileX - (2 * this._x) | 
					
						
							|  |  |  |             const yDiff = tileY - (2 * this._y) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const subtileIndex = yDiff * 2 + xDiff; | 
					
						
							|  |  |  |             if (this._subtiles[subtileIndex] === undefined) { | 
					
						
							|  |  |  |                 this._subtiles[subtileIndex] = new TileHierarchyAggregator(this, this._state, tileZ, tileX, tileY) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this._subtiles[subtileIndex].addTile(source) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.updateSignal.setData(source) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     getCountsForZoom(clusteringConfig: { maxZoom: number }, locationControl: UIEventSource<{ zoom: number }>, cutoff: number = 0): FeatureSource { | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         const empty = [] | 
					
						
							|  |  |  |         const features = locationControl.map(loc => loc.zoom).map(targetZoom => { | 
					
						
							|  |  |  |             if (targetZoom - 1 > clusteringConfig.maxZoom) { | 
					
						
							|  |  |  |                 return empty | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const features = [] | 
					
						
							|  |  |  |             self.visitSubTiles(aggr => { | 
					
						
							|  |  |  |                 if (aggr.showCount < cutoff) { | 
					
						
							|  |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (aggr._z === targetZoom) { | 
					
						
							|  |  |  |                     features.push(...aggr.features.data) | 
					
						
							|  |  |  |                     return false | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 return aggr._z <= targetZoom; | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return features | 
					
						
							|  |  |  |         }, [this.updateSignal.stabilized(500)]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return new StaticFeatureSource(features, true); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |     private update() { | 
					
						
							|  |  |  |         const newMap = new Map<string, number>() | 
					
						
							|  |  |  |         let total = 0 | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |         let hiddenCount = 0 | 
					
						
							|  |  |  |         let showCount = 0 | 
					
						
							|  |  |  |         let isShown: Map<string, FilteredLayer> = new Map<string, FilteredLayer>() | 
					
						
							|  |  |  |         for (const filteredLayer of this._state.filteredLayers.data) { | 
					
						
							|  |  |  |             isShown.set(filteredLayer.layerDef.id, filteredLayer) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         this?._counter?.countsPerLayer?.data?.forEach((count, layerId) => { | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |             newMap.set("layer:" + layerId, count) | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             total += count | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |             this.featureProperties["direct_layer:" + layerId] = count | 
					
						
							|  |  |  |             const flayer = isShown.get(layerId) | 
					
						
							|  |  |  |             if (flayer.isDisplayed.data && this._z >= flayer.layerDef.minzoom) { | 
					
						
							|  |  |  |                 showCount += count | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 hiddenCount += count; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         for (const tile of this._subtiles) { | 
					
						
							|  |  |  |             if (tile === undefined) { | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |                 continue; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             total += tile.totalValue | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             showCount += tile.showCount | 
					
						
							|  |  |  |             hiddenCount += tile.hiddenCount | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             for (const key in tile.featureProperties) { | 
					
						
							|  |  |  |                 if (key.startsWith("layer:")) { | 
					
						
							|  |  |  |                     newMap.set(key, (newMap.get(key) ?? 0) + Number(tile.featureProperties[key] ?? 0)) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         this.totalValue = total | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |         this.showCount = showCount | 
					
						
							|  |  |  |         this.hiddenCount = hiddenCount | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         this._parent?.update() | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         if (total === 0) { | 
					
						
							|  |  |  |             this.features.setData(TileHierarchyAggregator.empty) | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-09-29 01:12:29 +02:00
										 |  |  |             this.featureProperties.count = "" + total; | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |             this.featureProperties.kilocount = "" + Math.floor(total / 1000); | 
					
						
							|  |  |  |             this.featureProperties.showCount = "" + showCount | 
					
						
							|  |  |  |             this.featureProperties.totalCount = "" + total | 
					
						
							|  |  |  |             newMap.forEach((value, key) => { | 
					
						
							|  |  |  |                 this.featureProperties[key] = "" + value | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             this.features.data = this.featuresStatic | 
					
						
							|  |  |  |             this.features.ping() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |     private visitSubTiles(f: (aggr: TileHierarchyAggregator) => boolean) { | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         const visitFurther = f(this) | 
					
						
							| 
									
										
										
										
											2021-10-15 18:48:33 +02:00
										 |  |  |         if (visitFurther) { | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             this._subtiles.forEach(tile => tile?.visitSubTiles(f)) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Keeps track of a single tile | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | class SingleTileCounter implements Tiled { | 
					
						
							|  |  |  |     public readonly bbox: BBox; | 
					
						
							|  |  |  |     public readonly tileIndex: number; | 
					
						
							|  |  |  |     public readonly countsPerLayer: UIEventSource<Map<string, number>> = new UIEventSource<Map<string, number>>(new Map<string, number>()) | 
					
						
							|  |  |  |     public readonly z: number | 
					
						
							|  |  |  |     public readonly x: number | 
					
						
							|  |  |  |     public readonly y: number | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     private readonly registeredLayers: Map<string, LayerConfig> = new Map<string, LayerConfig>(); | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |     constructor(tileIndex: number) { | 
					
						
							|  |  |  |         this.tileIndex = tileIndex | 
					
						
							|  |  |  |         this.bbox = BBox.fromTileIndex(tileIndex) | 
					
						
							|  |  |  |         const [z, x, y] = Tiles.tile_from_index(tileIndex) | 
					
						
							|  |  |  |         this.z = z; | 
					
						
							|  |  |  |         this.x = x; | 
					
						
							|  |  |  |         this.y = y | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |     public addTileCount(source: FeatureSourceForLayer) { | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |         const layer = source.layer.layerDef | 
					
						
							|  |  |  |         this.registeredLayers.set(layer.id, layer) | 
					
						
							|  |  |  |         const self = this | 
					
						
							|  |  |  |         source.features.map(f => { | 
					
						
							| 
									
										
										
										
											2021-09-29 01:12:29 +02:00
										 |  |  |             const isDisplayed = source.layer.isDisplayed.data | 
					
						
							|  |  |  |             self.countsPerLayer.data.set(layer.id, isDisplayed ? f.length : 0) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |             self.countsPerLayer.ping() | 
					
						
							| 
									
										
										
										
											2021-09-29 01:12:29 +02:00
										 |  |  |         }, [source.layer.isDisplayed]) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |