| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | import GeoJsonSource from "./GeoJsonSource" | 
					
						
							|  |  |  | import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  | import { FeatureSource } from "../FeatureSource" | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | import { Or } from "../../Tags/Or" | 
					
						
							|  |  |  | import FeatureSwitchState from "../../State/FeatureSwitchState" | 
					
						
							|  |  |  | import OverpassFeatureSource from "./OverpassFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  | import { ImmutableStore, Store, UIEventSource } from "../../UIEventSource" | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  | import OsmFeatureSource from "./OsmFeatureSource" | 
					
						
							|  |  |  | import FeatureSourceMerger from "./FeatureSourceMerger" | 
					
						
							|  |  |  | import DynamicGeoJsonTileSource from "../TiledFeatureSource/DynamicGeoJsonTileSource" | 
					
						
							|  |  |  | import { BBox } from "../../BBox" | 
					
						
							|  |  |  | import LocalStorageFeatureSource from "../TiledFeatureSource/LocalStorageFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  | import StaticFeatureSource from "./StaticFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  | import { OsmPreferences } from "../../Osm/OsmPreferences" | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * 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 { | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |     private readonly _isLoading: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							| 
									
										
										
										
											2023-04-06 02:20:25 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Indicates if a data source is loading something | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |     public readonly isLoading: Store<boolean> = this._isLoading | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     constructor( | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         layers: LayerConfig[], | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         featureSwitches: FeatureSwitchState, | 
					
						
							|  |  |  |         mapProperties: { bounds: Store<BBox>; zoom: Store<number> }, | 
					
						
							|  |  |  |         backend: string, | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         isDisplayed: (id: string) => Store<boolean> | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     ) { | 
					
						
							|  |  |  |         const { bounds, zoom } = mapProperties | 
					
						
							|  |  |  |         // remove all 'special' layers
 | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  |         layers = layers.filter((layer) => layer.source !== null && layer.source !== undefined) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         const geojsonlayers = layers.filter((layer) => layer.source.geojsonSource !== undefined) | 
					
						
							|  |  |  |         const osmLayers = layers.filter((layer) => layer.source.geojsonSource === undefined) | 
					
						
							|  |  |  |         const fromCache = osmLayers.map( | 
					
						
							|  |  |  |             (l) => | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |                 new LocalStorageFeatureSource(backend, l.id, 15, mapProperties, { | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |                     isActive: isDisplayed(l.id), | 
					
						
							|  |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-04-20 17:42:07 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const overpassSource = LayoutSource.setupOverpass( | 
					
						
							|  |  |  |             backend, | 
					
						
							|  |  |  |             osmLayers, | 
					
						
							|  |  |  |             bounds, | 
					
						
							|  |  |  |             zoom, | 
					
						
							|  |  |  |             featureSwitches | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         const osmApiSource = LayoutSource.setupOsmApiSource( | 
					
						
							|  |  |  |             osmLayers, | 
					
						
							|  |  |  |             bounds, | 
					
						
							|  |  |  |             zoom, | 
					
						
							|  |  |  |             backend, | 
					
						
							|  |  |  |             featureSwitches | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         const geojsonSources: FeatureSource[] = geojsonlayers.map((l) => | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |             LayoutSource.setupGeojsonSource(l, mapProperties, isDisplayed(l.id)) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         const expiryInSeconds = Math.min(...(layers?.map((l) => l.maxAgeOfCache) ?? [])) | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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()) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private static setupGeojsonSource( | 
					
						
							|  |  |  |         layer: LayerConfig, | 
					
						
							|  |  |  |         mapProperties: { zoom: Store<number>; bounds: Store<BBox> }, | 
					
						
							|  |  |  |         isActive?: Store<boolean> | 
					
						
							|  |  |  |     ): FeatureSource { | 
					
						
							|  |  |  |         const source = layer.source | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         isActive = mapProperties.zoom.map( | 
					
						
							|  |  |  |             (z) => (isActive?.data ?? true) && z >= layer.maxzoom, | 
					
						
							|  |  |  |             [isActive] | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |     ): OsmFeatureSource | undefined { | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  |         if (osmLayers.length == 0) { | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |             return undefined | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         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( | 
					
						
							| 
									
										
										
										
											2023-04-20 17:42:07 +02:00
										 |  |  |         backend: string, | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         osmLayers: LayerConfig[], | 
					
						
							|  |  |  |         bounds: Store<BBox>, | 
					
						
							|  |  |  |         zoom: Store<number>, | 
					
						
							|  |  |  |         featureSwitches: FeatureSwitchState | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |     ): OverpassFeatureSource | undefined { | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  |         if (osmLayers.length == 0) { | 
					
						
							| 
									
										
										
										
											2023-04-20 01:52:23 +02:00
										 |  |  |             return undefined | 
					
						
							| 
									
										
										
										
											2023-04-06 01:33:08 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         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, | 
					
						
							| 
									
										
										
										
											2023-03-30 04:51:56 +02:00
										 |  |  |                 layers: osmLayers, | 
					
						
							|  |  |  |                 widenFactor: featureSwitches.layoutToUse.widenFactor, | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |                 overpassUrl: featureSwitches.overpassUrl, | 
					
						
							|  |  |  |                 overpassTimeout: featureSwitches.overpassTimeout, | 
					
						
							|  |  |  |                 overpassMaxZoom: featureSwitches.overpassMaxZoom, | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 padToTiles: zoom.map((zoom) => Math.min(15, zoom + 1)), | 
					
						
							|  |  |  |                 isActive, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |