| 
									
										
										
										
											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-09-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | export class TileHierarchyAggregator implements FeatureSource { | 
					
						
							|  |  |  |     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] | 
					
						
							|  |  |  |     public totalValue: number = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static readonly empty = [] | 
					
						
							|  |  |  |     public readonly features = new UIEventSource<{ feature: any, freshness: Date }[]>(TileHierarchyAggregator.empty) | 
					
						
							|  |  |  |     public readonly name; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private readonly featuresStatic = [] | 
					
						
							| 
									
										
										
										
											2021-10-13 01:28:46 +02:00
										 |  |  |     private readonly featureProperties: { count: string, kilocount: string, tileId: string, id: string }; | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     private constructor(parent: TileHierarchyAggregator, z: number, x: number, y: number) { | 
					
						
							|  |  |  |         this._parent = parent; | 
					
						
							|  |  |  |         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 + ")" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const totals = { | 
					
						
							| 
									
										
										
										
											2021-09-29 01:12:29 +02:00
										 |  |  |             id: ""+this._tileIndex, | 
					
						
							|  |  |  |             tileId: ""+this._tileIndex, | 
					
						
							| 
									
										
										
										
											2021-10-13 01:28:46 +02:00
										 |  |  |             count: `0`, | 
					
						
							|  |  |  |             kilocount: "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}) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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-09-27 14:45:48 +02:00
										 |  |  |     private update() { | 
					
						
							|  |  |  |         const newMap = new Map<string, number>() | 
					
						
							|  |  |  |         let total = 0 | 
					
						
							|  |  |  |         this?._counter?.countsPerLayer?.data?.forEach((count, layerId) => { | 
					
						
							|  |  |  |             newMap.set(layerId, count) | 
					
						
							|  |  |  |             total += count | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         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 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         this.totalValue = total | 
					
						
							|  |  |  |         this._parent?.update() | 
					
						
							|  |  |  |          | 
					
						
							|  |  |  |         if (total === 0) { | 
					
						
							|  |  |  |             this.features.setData(TileHierarchyAggregator.empty) | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-09-29 01:12:29 +02:00
										 |  |  |             this.featureProperties.count = "" + total; | 
					
						
							| 
									
										
										
										
											2021-10-13 01:28:46 +02:00
										 |  |  |             this.featureProperties.kilocount = "" +Math.floor(total/1000); | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             this.features.data = this.featuresStatic | 
					
						
							|  |  |  |             this.features.ping() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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 { | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             // 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) { | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |                 tileX = Math.floor(tileX / 2) | 
					
						
							|  |  |  |                 tileY = Math.floor(tileY / 2) | 
					
						
							|  |  |  |                 tileZ-- | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             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, tileZ, tileX, tileY) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |             this._subtiles[subtileIndex].addTile(source) | 
					
						
							| 
									
										
										
										
											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
										 |  |  |     public static createHierarchy() { | 
					
						
							|  |  |  |         return new TileHierarchyAggregator(undefined, 0, 0, 0) | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |     private visitSubTiles(f : (aggr: TileHierarchyAggregator) => boolean){ | 
					
						
							|  |  |  |         const visitFurther = f(this) | 
					
						
							|  |  |  |         if(visitFurther){ | 
					
						
							|  |  |  |             this._subtiles.forEach(tile => tile?.visitSubTiles(f)) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |      | 
					
						
							| 
									
										
										
										
											2021-10-13 00:43:19 +02:00
										 |  |  |     getCountsForZoom(clusteringConfig: {maxZoom: number}, locationControl: UIEventSource<{ zoom : number }>, cutoff: number = 0) : FeatureSource{ | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2021-10-13 00:43:19 +02:00
										 |  |  |         const empty = [] | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |         return new StaticFeatureSource( | 
					
						
							|  |  |  |             locationControl.map(loc => { | 
					
						
							|  |  |  |                 const targetZoom = loc.zoom | 
					
						
							| 
									
										
										
										
											2021-10-13 00:43:19 +02:00
										 |  |  |                  | 
					
						
							|  |  |  |                 if(targetZoom > clusteringConfig.maxZoom){ | 
					
						
							|  |  |  |                     return empty | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 const features = [] | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  |                 self.visitSubTiles(aggr => { | 
					
						
							|  |  |  |                     if(aggr.totalValue < cutoff) { | 
					
						
							|  |  |  |                         return false | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     if(aggr._z === targetZoom){ | 
					
						
							|  |  |  |                         features.push(...aggr.features.data) | 
					
						
							|  |  |  |                         return false | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return aggr._z <= targetZoom; | 
					
						
							|  |  |  |                      | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                  | 
					
						
							|  |  |  |                 return features | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             , true); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											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>()) | 
					
						
							|  |  |  |     private readonly registeredLayers: Map<string, LayerConfig> = new Map<string, LayerConfig>(); | 
					
						
							|  |  |  |     public readonly z: number | 
					
						
							|  |  |  |     public readonly x: number | 
					
						
							|  |  |  |     public readonly y: number | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							| 
									
										
										
										
											2021-09-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |         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-27 14:45:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-26 17:36:39 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |