forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			174 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			174 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | |
| import FeaturePipeline from "../FeatureSource/FeaturePipeline"
 | |
| import { Tiles } from "../../Models/TileRange"
 | |
| import ShowDataLayer from "../../UI/ShowDataLayer/ShowDataLayer"
 | |
| import { TileHierarchyAggregator } from "../../UI/ShowDataLayer/TileHierarchyAggregator"
 | |
| import ShowTileInfo from "../../UI/ShowDataLayer/ShowTileInfo"
 | |
| import { UIEventSource } from "../UIEventSource"
 | |
| import MapState from "./MapState"
 | |
| import SelectedFeatureHandler from "../Actors/SelectedFeatureHandler"
 | |
| import Hash from "../Web/Hash"
 | |
| import { BBox } from "../BBox"
 | |
| import FeatureInfoBox from "../../UI/Popup/FeatureInfoBox"
 | |
| import { FeatureSourceForLayer, Tiled } from "../FeatureSource/FeatureSource"
 | |
| import MetaTagRecalculator from "../FeatureSource/Actors/MetaTagRecalculator"
 | |
| import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
 | |
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
 | |
| 
 | |
| export default class FeaturePipelineState extends MapState {
 | |
|     /**
 | |
|      * The piece of code which fetches data from various sources and shows it on the background map
 | |
|      */
 | |
|     public readonly featurePipeline: FeaturePipeline
 | |
|     private readonly featureAggregator: TileHierarchyAggregator
 | |
|     private readonly metatagRecalculator: MetaTagRecalculator
 | |
|     private readonly popups: Map<string, ScrollableFullScreen> = new Map<
 | |
|         string,
 | |
|         ScrollableFullScreen
 | |
|     >()
 | |
| 
 | |
|     constructor(layoutToUse: LayoutConfig) {
 | |
|         super(layoutToUse)
 | |
| 
 | |
|         const clustering = layoutToUse?.clustering
 | |
|         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this)
 | |
|         const clusterCounter = this.featureAggregator
 | |
|         const self = this
 | |
| 
 | |
|         /**
 | |
|          * We are a bit in a bind:
 | |
|          * There is the featurePipeline, which creates some sources during construction
 | |
|          * THere is the metatagger, which needs to have these sources registered AND which takes a FeaturePipeline as argument
 | |
|          *
 | |
|          * This is a bit of a catch-22 (except that it isn't)
 | |
|          * The sources that are registered in the constructor are saved into 'registeredSources' temporary
 | |
|          *
 | |
|          */
 | |
|         const sourcesToRegister = []
 | |
| 
 | |
|         function registerRaw(source: FeatureSourceForLayer & Tiled) {
 | |
|             if (self.metatagRecalculator === undefined) {
 | |
|                 sourcesToRegister.push(source)
 | |
|             } else {
 | |
|                 self.metatagRecalculator.registerSource(source)
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         function registerSource(source: FeatureSourceForLayer & Tiled) {
 | |
|             clusterCounter.addTile(source)
 | |
|             const sourceBBox = source.features.map((allFeatures) =>
 | |
|                 BBox.bboxAroundAll(allFeatures.map((f) => BBox.get(f.feature)))
 | |
|             )
 | |
| 
 | |
|             // Do show features indicates if the respective 'showDataLayer' should be shown. It can be hidden by e.g. clustering
 | |
|             const doShowFeatures = source.features.map(
 | |
|                 (f) => {
 | |
|                     const z = self.locationControl.data.zoom
 | |
| 
 | |
|                     if (!source.layer.isDisplayed.data) {
 | |
|                         return false
 | |
|                     }
 | |
| 
 | |
|                     const bounds = self.currentBounds.data
 | |
|                     if (bounds === undefined) {
 | |
|                         // Map is not yet displayed
 | |
|                         return false
 | |
|                     }
 | |
| 
 | |
|                     if (!sourceBBox.data.overlapsWith(bounds)) {
 | |
|                         // Not within range -> features are hidden
 | |
|                         return false
 | |
|                     }
 | |
| 
 | |
|                     if (z < source.layer.layerDef.minzoom) {
 | |
|                         // Layer is always hidden for this zoom level
 | |
|                         return false
 | |
|                     }
 | |
| 
 | |
|                     if (z > clustering.maxZoom) {
 | |
|                         return true
 | |
|                     }
 | |
| 
 | |
|                     if (f.length > clustering.minNeededElements) {
 | |
|                         // This tile alone already has too much features
 | |
|                         return false
 | |
|                     }
 | |
| 
 | |
|                     let [tileZ, tileX, tileY] = Tiles.tile_from_index(source.tileIndex)
 | |
|                     if (tileZ >= z) {
 | |
|                         while (tileZ > z) {
 | |
|                             tileZ--
 | |
|                             tileX = Math.floor(tileX / 2)
 | |
|                             tileY = Math.floor(tileY / 2)
 | |
|                         }
 | |
| 
 | |
|                         if (
 | |
|                             clusterCounter.getTile(Tiles.tile_index(tileZ, tileX, tileY))
 | |
|                                 ?.totalValue > clustering.minNeededElements
 | |
|                         ) {
 | |
|                             // To much elements
 | |
|                             return false
 | |
|                         }
 | |
|                     }
 | |
| 
 | |
|                     return true
 | |
|                 },
 | |
|                 [self.currentBounds, source.layer.isDisplayed, sourceBBox]
 | |
|             )
 | |
| 
 | |
|             new ShowDataLayer({
 | |
|                 features: source,
 | |
|                 leafletMap: self.leafletMap,
 | |
|                 layerToShow: source.layer.layerDef,
 | |
|                 doShowLayer: doShowFeatures,
 | |
|                 selectedElement: self.selectedElement,
 | |
|                 state: self,
 | |
|                 popup: (tags, layer) => self.CreatePopup(tags, layer),
 | |
|             })
 | |
|         }
 | |
| 
 | |
|         this.featurePipeline = new FeaturePipeline(registerSource, this, {
 | |
|             handleRawFeatureSource: registerRaw,
 | |
|         })
 | |
|         this.metatagRecalculator = new MetaTagRecalculator(this, this.featurePipeline)
 | |
|         this.metatagRecalculator.registerSource(this.currentView, true)
 | |
| 
 | |
|         sourcesToRegister.forEach((source) => self.metatagRecalculator.registerSource(source))
 | |
| 
 | |
|         new SelectedFeatureHandler(Hash.hash, this)
 | |
| 
 | |
|         this.AddClusteringToMap(this.leafletMap)
 | |
|     }
 | |
| 
 | |
|     public CreatePopup(tags: UIEventSource<any>, layer: LayerConfig): ScrollableFullScreen {
 | |
|         if (this.popups.has(tags.data.id)) {
 | |
|             return this.popups.get(tags.data.id)
 | |
|         }
 | |
|         const popup = new FeatureInfoBox(tags, layer, this)
 | |
|         this.popups.set(tags.data.id, popup)
 | |
|         return popup
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Adds the cluster-tiles to the given map
 | |
|      * @param leafletMap: a UIEventSource possible having a leaflet map
 | |
|      * @constructor
 | |
|      */
 | |
|     public AddClusteringToMap(leafletMap: UIEventSource<any>) {
 | |
|         const clustering = this.layoutToUse.clustering
 | |
|         const self = this
 | |
|         new ShowDataLayer({
 | |
|             features: this.featureAggregator.getCountsForZoom(
 | |
|                 clustering,
 | |
|                 this.locationControl,
 | |
|                 clustering.minNeededElements
 | |
|             ),
 | |
|             leafletMap: leafletMap,
 | |
|             layerToShow: ShowTileInfo.styling,
 | |
|             popup: this.featureSwitchIsDebugging.data
 | |
|                 ? (tags, layer) => new FeatureInfoBox(tags, layer, self)
 | |
|                 : undefined,
 | |
|             state: this,
 | |
|         })
 | |
|     }
 | |
| }
 |