| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import { Utils } from "../../../Utils" | 
					
						
							|  |  |  | import * as OsmToGeoJson from "osmtogeojson" | 
					
						
							|  |  |  | import StaticFeatureSource from "../Sources/StaticFeatureSource" | 
					
						
							|  |  |  | import PerLayerFeatureSourceSplitter from "../PerLayerFeatureSourceSplitter" | 
					
						
							|  |  |  | import { Store, UIEventSource } from "../../UIEventSource" | 
					
						
							|  |  |  | import FilteredLayer from "../../../Models/FilteredLayer" | 
					
						
							|  |  |  | import { FeatureSourceForLayer, Tiled } from "../FeatureSource" | 
					
						
							|  |  |  | import { Tiles } from "../../../Models/TileRange" | 
					
						
							|  |  |  | import { BBox } from "../../BBox" | 
					
						
							|  |  |  | import LayoutConfig from "../../../Models/ThemeConfig/LayoutConfig" | 
					
						
							|  |  |  | import { Or } from "../../Tags/Or" | 
					
						
							|  |  |  | import { TagsFilter } from "../../Tags/TagsFilter" | 
					
						
							|  |  |  | import { OsmObject } from "../../Osm/OsmObject" | 
					
						
							|  |  |  | import { FeatureCollection } from "@turf/turf" | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-15 02:44:11 +01:00
										 |  |  | /** | 
					
						
							|  |  |  |  * If a tile is needed (requested via the UIEventSource in the constructor), will download the appropriate tile and pass it via 'handleTile' | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | export default class OsmFeatureSource { | 
					
						
							|  |  |  |     public readonly isRunning: UIEventSource<boolean> = new UIEventSource<boolean>(false) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |     public readonly downloadedTiles = new Set<number>() | 
					
						
							|  |  |  |     public rawDataHandlers: ((osmJson: any, tileId: number) => void)[] = [] | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private readonly _backend: string | 
					
						
							|  |  |  |     private readonly filteredLayers: Store<FilteredLayer[]> | 
					
						
							|  |  |  |     private readonly handleTile: (fs: FeatureSourceForLayer & Tiled) => void | 
					
						
							|  |  |  |     private isActive: Store<boolean> | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |     private options: { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void | 
					
						
							|  |  |  |         isActive: Store<boolean> | 
					
						
							|  |  |  |         neededTiles: Store<number[]> | 
					
						
							| 
									
										
										
										
											2021-09-30 04:13:23 +02:00
										 |  |  |         markTileVisited?: (tileId: number) => void | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     } | 
					
						
							|  |  |  |     private readonly allowedTags: TagsFilter | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |      * | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |      * @param options: allowedFeatures is normally calculated from the layoutToUse | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |     constructor(options: { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         handleTile: (tile: FeatureSourceForLayer & Tiled) => void | 
					
						
							|  |  |  |         isActive: Store<boolean> | 
					
						
							|  |  |  |         neededTiles: Store<number[]> | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |         state: { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             readonly filteredLayers: UIEventSource<FilteredLayer[]> | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |             readonly osmConnection: { | 
					
						
							|  |  |  |                 Backend(): string | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |             readonly layoutToUse?: LayoutConfig | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         readonly allowedFeatures?: TagsFilter | 
					
						
							| 
									
										
										
										
											2021-09-30 04:13:23 +02:00
										 |  |  |         markTileVisited?: (tileId: number) => void | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |     }) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this.options = options | 
					
						
							|  |  |  |         this._backend = options.state.osmConnection.Backend() | 
					
						
							|  |  |  |         this.filteredLayers = options.state.filteredLayers.map((layers) => | 
					
						
							|  |  |  |             layers.filter((layer) => layer.layerDef.source.geojsonSource === undefined) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |         this.handleTile = options.handleTile | 
					
						
							|  |  |  |         this.isActive = options.isActive | 
					
						
							|  |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         options.neededTiles.addCallbackAndRunD((neededTiles) => { | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |             self.Update(neededTiles) | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-10-08 15:11:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-22 14:13:41 +01:00
										 |  |  |         const neededLayers = (options.state.layoutToUse?.layers ?? []) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             .filter((layer) => !layer.doNotDownload) | 
					
						
							|  |  |  |             .filter( | 
					
						
							|  |  |  |                 (layer) => layer.source.geojsonSource === undefined || layer.source.isOsmCacheLayer | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         this.allowedTags = | 
					
						
							|  |  |  |             options.allowedFeatures ?? new Or(neededLayers.map((l) => l.source.osmTags)) | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |     private async Update(neededTiles: number[]) { | 
					
						
							|  |  |  |         if (this.options.isActive?.data === false) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         neededTiles = neededTiles.filter((tile) => !this.downloadedTiles.has(tile)) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (neededTiles.length == 0) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.isRunning.setData(true) | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             for (const neededTile of neededTiles) { | 
					
						
							|  |  |  |                 this.downloadedTiles.add(neededTile) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |                 await this.LoadTile(...Tiles.tile_from_index(neededTile)) | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.error(e) | 
					
						
							|  |  |  |         } finally { | 
					
						
							|  |  |  |             this.isRunning.setData(false) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * The requested tile might only contain part of the relation. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * This method will download the full relation and return it as geojson if it was incomplete. | 
					
						
							|  |  |  |      * If the feature is already complete (or is not a relation), the feature will be returned | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private async patchIncompleteRelations( | 
					
						
							|  |  |  |         feature: { properties: { id: string } }, | 
					
						
							|  |  |  |         originalJson: { elements: { type: "node" | "way" | "relation"; id: number }[] } | 
					
						
							|  |  |  |     ): Promise<any> { | 
					
						
							|  |  |  |         if (!feature.properties.id.startsWith("relation")) { | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             return feature | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const relationSpec = originalJson.elements.find( | 
					
						
							|  |  |  |             (f) => "relation/" + f.id === feature.properties.id | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         const members: { type: string; ref: number }[] = relationSpec["members"] | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |         for (const member of members) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const isFound = originalJson.elements.some( | 
					
						
							|  |  |  |                 (f) => f.id === member.ref && f.type === member.type | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             if (isFound) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             // This member is missing. We redownload the entire relation instead
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             console.debug("Fetching incomplete relation " + feature.properties.id) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             return (await OsmObject.DownloadObjectAsync(feature.properties.id)).asGeoJson() | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return feature | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private async LoadTile(z, x, y): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-06-28 01:37:49 +02:00
										 |  |  |         if (z >= 22) { | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |             throw "This is an absurd high zoom level" | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         if (z < 14) { | 
					
						
							| 
									
										
										
										
											2022-01-17 21:33:03 +01:00
										 |  |  |             throw `Zoom ${z} is too much for OSM to handle! Use a higher zoom level!` | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const bbox = BBox.fromTile(z, x, y) | 
					
						
							|  |  |  |         const url = `${this._backend}/api/0.6/map?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}` | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         let error = undefined | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |         try { | 
					
						
							|  |  |  |             const osmJson = await Utils.downloadJson(url) | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |             try { | 
					
						
							| 
									
										
										
										
											2022-06-21 22:57:13 +02:00
										 |  |  |                 console.log("Got tile", z, x, y, "from the osm api") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 this.rawDataHandlers.forEach((handler) => | 
					
						
							|  |  |  |                     handler(osmJson, Tiles.tile_index(z, x, y)) | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 const geojson = <FeatureCollection<any, { id: string }>>OsmToGeoJson.default( | 
					
						
							|  |  |  |                     osmJson, | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |                     // @ts-ignore
 | 
					
						
							|  |  |  |                     { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         flatProperties: true, | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-07 18:58:45 +02:00
										 |  |  |                 // The geojson contains _all_ features at the given location
 | 
					
						
							|  |  |  |                 // We only keep what is needed
 | 
					
						
							| 
									
										
										
										
											2021-10-08 15:11:20 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 geojson.features = geojson.features.filter((feature) => | 
					
						
							|  |  |  |                     this.allowedTags.matchesProperties(feature.properties) | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 for (let i = 0; i < geojson.features.length; i++) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     geojson.features[i] = await this.patchIncompleteRelations( | 
					
						
							|  |  |  |                         geojson.features[i], | 
					
						
							|  |  |  |                         osmJson | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 geojson.features.forEach((f) => { | 
					
						
							| 
									
										
										
										
											2021-12-17 19:28:05 +01:00
										 |  |  |                     f.properties["_backend"] = this._backend | 
					
						
							|  |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 const index = Tiles.tile_index(z, x, y) | 
					
						
							|  |  |  |                 new PerLayerFeatureSourceSplitter( | 
					
						
							|  |  |  |                     this.filteredLayers, | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |                     this.handleTile, | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                     StaticFeatureSource.fromGeojson(geojson.features), | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |                     { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         tileIndex: index, | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2021-10-08 15:11:20 +02:00
										 |  |  |                 if (this.options.markTileVisited) { | 
					
						
							| 
									
										
										
										
											2021-09-30 04:13:23 +02:00
										 |  |  |                     this.options.markTileVisited(index) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 console.error( | 
					
						
							|  |  |  |                     "PANIC: got the tile from the OSM-api, but something crashed handling this tile" | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 error = e | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |         } catch (e) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             console.error( | 
					
						
							|  |  |  |                 "Could not download tile", | 
					
						
							|  |  |  |                 z, | 
					
						
							|  |  |  |                 x, | 
					
						
							|  |  |  |                 y, | 
					
						
							|  |  |  |                 "due to", | 
					
						
							|  |  |  |                 e, | 
					
						
							|  |  |  |                 "; retrying with smaller bounds" | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             if (e === "rate limited") { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             await this.LoadTile(z + 1, x * 2, y * 2) | 
					
						
							|  |  |  |             await this.LoadTile(z + 1, 1 + x * 2, y * 2) | 
					
						
							|  |  |  |             await this.LoadTile(z + 1, x * 2, 1 + y * 2) | 
					
						
							|  |  |  |             await this.LoadTile(z + 1, 1 + x * 2, 1 + y * 2) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         if (error !== undefined) { | 
					
						
							|  |  |  |             throw error | 
					
						
							| 
									
										
										
										
											2022-06-24 18:12:39 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-28 17:30:48 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | } |