| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Fetches a geojson file somewhere and passes it along | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { Store, UIEventSource } from "../../UIEventSource" | 
					
						
							|  |  |  | import { Utils } from "../../../Utils" | 
					
						
							|  |  |  | import { FeatureSource } from "../FeatureSource" | 
					
						
							|  |  |  | import { BBox } from "../../BBox" | 
					
						
							|  |  |  | import { GeoOperations } from "../../GeoOperations" | 
					
						
							|  |  |  | import { Feature } from "geojson" | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  | import LayerConfig from "../../../Models/ThemeConfig/LayerConfig" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { Tiles } from "../../../Models/TileRange" | 
					
						
							| 
									
										
										
										
											2021-09-21 02:10:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  | export default class GeoJsonSource implements FeatureSource { | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |     private readonly _features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>(undefined) | 
					
						
							|  |  |  |     public readonly features: Store<Feature[]> = this._features | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private readonly seenids: Set<string> | 
					
						
							|  |  |  |     private readonly idKey?: string | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |     private readonly url: string | 
					
						
							|  |  |  |     private readonly layer: LayerConfig | 
					
						
							|  |  |  |     private _isDownloaded = false | 
					
						
							|  |  |  |     private currentlyRunning: Promise<any> | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     public constructor( | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         layer: LayerConfig, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         options?: { | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             zxy?: number | [number, number, number] | BBox | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             featureIdBlacklist?: Set<string> | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             isActive?: Store<boolean> | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     ) { | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         if (layer.source.geojsonZoomLevel !== undefined && options?.zxy === undefined) { | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |             throw "Dynamic layers are not supported. Use 'DynamicGeoJsonTileSource instead" | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         this.idKey = layer.source.idKey | 
					
						
							| 
									
										
										
										
											2022-02-11 03:57:39 +01:00
										 |  |  |         this.seenids = options?.featureIdBlacklist ?? new Set<string>() | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         let url = layer.source.geojsonSource.replace("{layer}", layer.id) | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |         this.layer = layer | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         let zxy = options?.zxy | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |         if (zxy !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             let tile_bbox: BBox | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             if (typeof zxy === "number") { | 
					
						
							|  |  |  |                 zxy = Tiles.tile_from_index(zxy) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  |             if (zxy instanceof BBox) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 tile_bbox = zxy | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 const [z, x, y] = zxy | 
					
						
							|  |  |  |                 tile_bbox = BBox.fromTile(z, x, y) | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  |                 url = url | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     .replace("{z}", "" + z) | 
					
						
							|  |  |  |                     .replace("{x}", "" + x) | 
					
						
							|  |  |  |                     .replace("{y}", "" + y) | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             let bounds: Record<"minLat" | "maxLat" | "minLon" | "maxLon", number> = tile_bbox | 
					
						
							|  |  |  |             if (layer.source.mercatorCrs) { | 
					
						
							| 
									
										
										
										
											2021-10-27 03:52:19 +02:00
										 |  |  |                 bounds = tile_bbox.toMercator() | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |             url = url | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 .replace("{y_min}", "" + bounds.minLat) | 
					
						
							|  |  |  |                 .replace("{y_max}", "" + bounds.maxLat) | 
					
						
							|  |  |  |                 .replace("{x_min}", "" + bounds.minLon) | 
					
						
							|  |  |  |                 .replace("{x_max}", "" + bounds.maxLon) | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |         this.url = url | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         if (options?.isActive !== undefined) { | 
					
						
							|  |  |  |             options.isActive.addCallbackAndRunD(async (active) => { | 
					
						
							|  |  |  |                 if (!active) { | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |                 this.updateAsync() | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |                 return true // data is loaded, we can safely unregister
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |             this.updateAsync() | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     public async updateAsync(): Promise<void> { | 
					
						
							|  |  |  |         if (!this.currentlyRunning) { | 
					
						
							|  |  |  |             this.currentlyRunning = this.LoadJSONFrom() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         await this.currentlyRunning | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * Init the download, write into the specified event source for the given layer. | 
					
						
							|  |  |  |      * Note this method caches the requested geojson for five minutes | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |     private async LoadJSONFrom(options?: { maxCacheAgeSec?: number | 300 }): Promise<Feature[]> { | 
					
						
							|  |  |  |         if (this._isDownloaded) { | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |         const url = this.url | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |             let json = await Utils.downloadJsonCached(url, (options?.maxCacheAgeSec ?? 300) * 1000) | 
					
						
							| 
									
										
										
										
											2022-07-09 21:41:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |             if (json.features === undefined || json.features === null) { | 
					
						
							|  |  |  |                 json.features = [] | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |             if (this.layer.source.mercatorCrs) { | 
					
						
							|  |  |  |                 json = GeoOperations.GeoJsonToWGS84(json) | 
					
						
							| 
									
										
										
										
											2023-06-09 16:13:35 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |             const newFeatures: Feature[] = [] | 
					
						
							|  |  |  |             let i = 0 | 
					
						
							|  |  |  |             for (const feature of json.features) { | 
					
						
							|  |  |  |                 if (feature.geometry.type === "Point") { | 
					
						
							|  |  |  |                     // See https://github.com/maproulette/maproulette-backend/issues/242
 | 
					
						
							|  |  |  |                     feature.geometry.coordinates = feature.geometry.coordinates.map(Number) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |                 const props = feature.properties | 
					
						
							|  |  |  |                 for (const key in props) { | 
					
						
							|  |  |  |                     if (props[key] === null) { | 
					
						
							|  |  |  |                         delete props[key] | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |                     if (typeof props[key] !== "string") { | 
					
						
							|  |  |  |                         // Make sure all the values are string, it crashes stuff otherwise
 | 
					
						
							|  |  |  |                         props[key] = JSON.stringify(props[key]) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |                 if (this.idKey !== undefined) { | 
					
						
							|  |  |  |                     props.id = props[this.idKey] | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |                 if (props.id === undefined) { | 
					
						
							|  |  |  |                     props.id = url + "/" + i | 
					
						
							|  |  |  |                     feature.id = url + "/" + i | 
					
						
							|  |  |  |                     i++ | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (this.seenids.has(props.id)) { | 
					
						
							|  |  |  |                     continue | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 this.seenids.add(props.id) | 
					
						
							|  |  |  |                 newFeatures.push(feature) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 16:35:49 +01:00
										 |  |  |             this._features.setData(newFeatures) | 
					
						
							|  |  |  |             this._isDownloaded = true | 
					
						
							|  |  |  |             return newFeatures | 
					
						
							|  |  |  |         } catch (e) { | 
					
						
							|  |  |  |             console.warn("Could not load ", url, "due to", e) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } |