| 
									
										
										
										
											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 { | 
					
						
							|  |  |  |     public readonly features: Store<Feature[]> | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     private readonly seenids: Set<string> | 
					
						
							|  |  |  |     private readonly idKey?: string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     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) | 
					
						
							|  |  |  |         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
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 05:13:48 +02:00
										 |  |  |         const eventsource = new UIEventSource<Feature[]>([]) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         if (options?.isActive !== undefined) { | 
					
						
							|  |  |  |             options.isActive.addCallbackAndRunD(async (active) => { | 
					
						
							|  |  |  |                 if (!active) { | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 this.LoadJSONFrom(url, eventsource, layer) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                     .then((fs) => console.debug("Loaded", fs.length, "features from", url)) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |                     .catch((err) => console.warn("Could not load ", url, "due to", err)) | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  |             this.LoadJSONFrom(url, eventsource, layer) | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 .then((fs) => console.debug("Loaded", fs.length, "features from", url)) | 
					
						
							| 
									
										
										
										
											2023-06-04 22:52:13 +02:00
										 |  |  |                 .catch((err) => console.warn("Could not load ", url, "due to", err)) | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |         this.features = eventsource | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |     private async LoadJSONFrom( | 
					
						
							|  |  |  |         url: string, | 
					
						
							|  |  |  |         eventSource: UIEventSource<Feature[]>, | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |         layer: LayerConfig, | 
					
						
							|  |  |  |         options?: { | 
					
						
							|  |  |  |             maxCacheAgeSec?: number | 300 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     ): Promise<Feature[]> { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const self = this | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |         let json = await Utils.downloadJsonCached(url, (options?.maxCacheAgeSec ?? 300) * 1000) | 
					
						
							| 
									
										
										
										
											2022-07-09 21:41:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         if (json.features === undefined || json.features === null) { | 
					
						
							|  |  |  |             json.features = [] | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-21 01:57:16 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         if (layer.source.mercatorCrs) { | 
					
						
							|  |  |  |             json = GeoOperations.GeoJsonToWGS84(json) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         const time = new Date() | 
					
						
							|  |  |  |         const newFeatures: Feature[] = [] | 
					
						
							|  |  |  |         let i = 0 | 
					
						
							|  |  |  |         for (const feature of json.features) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             if (feature.geometry.type === "Point") { | 
					
						
							| 
									
										
										
										
											2023-06-09 16:13:35 +02:00
										 |  |  |                 // 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
										 |  |  |             const props = feature.properties | 
					
						
							|  |  |  |             for (const key in props) { | 
					
						
							|  |  |  |                 if (props[key] === null) { | 
					
						
							|  |  |  |                     delete props[key] | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |                 if (typeof props[key] !== "string") { | 
					
						
							|  |  |  |                     // Make sure all the values are string, it crashes stuff otherwise
 | 
					
						
							|  |  |  |                     props[key] = JSON.stringify(props[key]) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             if (self.idKey !== undefined) { | 
					
						
							|  |  |  |                 props.id = props[self.idKey] | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |             if (props.id === undefined) { | 
					
						
							|  |  |  |                 props.id = url + "/" + i | 
					
						
							|  |  |  |                 feature.id = url + "/" + i | 
					
						
							|  |  |  |                 i++ | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (self.seenids.has(props.id)) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             self.seenids.add(props.id) | 
					
						
							|  |  |  |             newFeatures.push(feature) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-26 05:58:28 +02:00
										 |  |  |         eventSource.setData(newFeatures) | 
					
						
							| 
									
										
										
										
											2023-05-23 01:16:45 +02:00
										 |  |  |         return newFeatures | 
					
						
							| 
									
										
										
										
											2021-09-20 17:14:55 +02:00
										 |  |  |     } | 
					
						
							|  |  |  | } |