forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			168 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
	
		
			6.2 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import GeoJsonSource from "./GeoJsonSource"
 | |
| import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
 | |
| import { FeatureSource } from "../FeatureSource"
 | |
| import { Or } from "../../Tags/Or"
 | |
| import FeatureSwitchState from "../../State/FeatureSwitchState"
 | |
| import OverpassFeatureSource from "./OverpassFeatureSource"
 | |
| import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource"
 | |
| import OsmFeatureSource from "./OsmFeatureSource"
 | |
| import FeatureSourceMerger from "./FeatureSourceMerger"
 | |
| import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource"
 | |
| import { BBox } from "../../BBox"
 | |
| import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource"
 | |
| import StaticFeatureSource from "./StaticFeatureSource"
 | |
| import { OsmPreferences } from "../../Osm/OsmPreferences"
 | |
| 
 | |
| /**
 | |
|  * This source will fetch the needed data from various sources for the given layout.
 | |
|  *
 | |
|  * Note that special layers (with `source=null` will be ignored)
 | |
|  */
 | |
| export default class LayoutSource extends FeatureSourceMerger {
 | |
|     private readonly _isLoading: UIEventSource<boolean> = new UIEventSource<boolean>(false)
 | |
|     /**
 | |
|      * Indicates if a data source is loading something
 | |
|      */
 | |
|     public readonly isLoading: Store<boolean> = this._isLoading
 | |
|     constructor(
 | |
|         layers: LayerConfig[],
 | |
|         featureSwitches: FeatureSwitchState,
 | |
|         mapProperties: { bounds: Store<BBox>; zoom: Store<number> },
 | |
|         backend: string,
 | |
|         isDisplayed: (id: string) => Store<boolean>
 | |
|     ) {
 | |
|         const { bounds, zoom } = mapProperties
 | |
|         // remove all 'special' layers
 | |
|         layers = layers.filter((layer) => layer.source !== null && layer.source !== undefined)
 | |
| 
 | |
|         const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined)
 | |
|         const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined)
 | |
|         const fromCache = osmLayers.map(
 | |
|             (l) =>
 | |
|                 new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, {
 | |
|                     isActive: isDisplayed(l.id),
 | |
|                 })
 | |
|         )
 | |
| 
 | |
|         const overpassSource = LayoutSource.setupOverpass(
 | |
|             backend,
 | |
|             osmLayers,
 | |
|             bounds,
 | |
|             zoom,
 | |
|             featureSwitches
 | |
|         )
 | |
| 
 | |
|         const osmApiSource = LayoutSource.setupOsmApiSource(
 | |
|             osmLayers,
 | |
|             bounds,
 | |
|             zoom,
 | |
|             backend,
 | |
|             featureSwitches
 | |
|         )
 | |
|         const geojsonSources: FeatureSource[] = geojsonlayers.map((l) =>
 | |
|             LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id))
 | |
|         )
 | |
| 
 | |
|         const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? []))
 | |
| 
 | |
|         super(overpassSource, osmApiSource, ...geojsonSources, ...fromCache)
 | |
| 
 | |
|         const self = this
 | |
|         function setIsLoading() {
 | |
|             const loading = overpassSource?.runningQuery?.data && osmApiSource?.isRunning?.data
 | |
|             self._isLoading.setData(loading)
 | |
|         }
 | |
| 
 | |
|         overpassSource?.runningQuery?.addCallbackAndRun((_) => setIsLoading())
 | |
|         osmApiSource?.isRunning?.addCallbackAndRun((_) => setIsLoading())
 | |
|     }
 | |
| 
 | |
|     private static setupGeojsonSource(
 | |
|         layer: LayerConfig,
 | |
|         mapProperties: { zoom: Store<number>; bounds: Store<BBox> },
 | |
|         isActive?: Store<boolean>
 | |
|     ): FeatureSource {
 | |
|         const source = layer.source
 | |
|         isActive = mapProperties.zoom.map(
 | |
|             (z) => (isActive?.data ?? true) && z >= layer.maxzoom,
 | |
|             [isActive]
 | |
|         )
 | |
|         if (source.geojsonZoomLevel === undefined) {
 | |
|             // This is a 'load everything at once' geojson layer
 | |
|             return new GeoJsonSource(layer, { isActive })
 | |
|         } else {
 | |
|             return new DynamicGeoJsonTileSource(layer, mapProperties, { isActive })
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     private static setupOsmApiSource(
 | |
|         osmLayers: LayerConfig[],
 | |
|         bounds: Store<BBox>,
 | |
|         zoom: Store<number>,
 | |
|         backend: string,
 | |
|         featureSwitches: FeatureSwitchState
 | |
|     ): OsmFeatureSource | undefined {
 | |
|         if (osmLayers.length == 0) {
 | |
|             return undefined
 | |
|         }
 | |
|         const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
 | |
|         const isActive = zoom.mapD((z) => {
 | |
|             if (z < minzoom) {
 | |
|                 // We are zoomed out over the zoomlevel of any layer
 | |
|                 console.debug("Disabling overpass source: zoom < minzoom")
 | |
|                 return false
 | |
|             }
 | |
| 
 | |
|             // Overpass should handle this if zoomed out a bit
 | |
|             return z > featureSwitches.overpassMaxZoom.data
 | |
|         })
 | |
|         const allowedFeatures = new Or(osmLayers.map((l) => l.source.osmTags)).optimize()
 | |
|         if (typeof allowedFeatures === "boolean") {
 | |
|             throw "Invalid filter to init OsmFeatureSource: it optimizes away to " + allowedFeatures
 | |
|         }
 | |
|         return new OsmFeatureSource({
 | |
|             allowedFeatures,
 | |
|             bounds,
 | |
|             backend,
 | |
|             isActive,
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     private static setupOverpass(
 | |
|         backend: string,
 | |
|         osmLayers: LayerConfig[],
 | |
|         bounds: Store<BBox>,
 | |
|         zoom: Store<number>,
 | |
|         featureSwitches: FeatureSwitchState
 | |
|     ): OverpassFeatureSource | undefined {
 | |
|         if (osmLayers.length == 0) {
 | |
|             return undefined
 | |
|         }
 | |
|         const minzoom = Math.min(...osmLayers.map((layer) => layer.minzoom))
 | |
|         const isActive = zoom.mapD((z) => {
 | |
|             if (z < minzoom) {
 | |
|                 // We are zoomed out over the zoomlevel of any layer
 | |
|                 console.debug("Disabling overpass source: zoom < minzoom")
 | |
|                 return false
 | |
|             }
 | |
| 
 | |
|             return z <= featureSwitches.overpassMaxZoom.data
 | |
|         })
 | |
| 
 | |
|         return new OverpassFeatureSource(
 | |
|             {
 | |
|                 zoom,
 | |
|                 bounds,
 | |
|                 layers: osmLayers,
 | |
|                 widenFactor: featureSwitches.layoutToUse.widenFactor,
 | |
|                 overpassUrl: featureSwitches.overpassUrl,
 | |
|                 overpassTimeout: featureSwitches.overpassTimeout,
 | |
|                 overpassMaxZoom: featureSwitches.overpassMaxZoom,
 | |
|             },
 | |
|             {
 | |
|                 padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)),
 | |
|                 isActive,
 | |
|             }
 | |
|         )
 | |
|     }
 | |
| }
 |