| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  | import FeatureSource from "./FeatureSource"; | 
					
						
							|  |  |  | import {UIEventSource} from "../UIEventSource"; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | import Loc from "../../Models/Loc"; | 
					
						
							|  |  |  | import State from "../../State"; | 
					
						
							|  |  |  | import {Utils} from "../../Utils"; | 
					
						
							|  |  |  | import LayerConfig from "../../Customizations/JSON/LayerConfig"; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Fetches a geojson file somewhere and passes it along | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export default class GeoJsonSource implements FeatureSource { | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  |     public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; | 
					
						
							|  |  |  |     public readonly name; | 
					
						
							| 
									
										
										
										
											2021-04-23 20:09:27 +02:00
										 |  |  |     private onFail: ((errorMsg: any, url: string) => void) = undefined; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |     private readonly layerId: string; | 
					
						
							|  |  |  |     private readonly seenids: Set<string> = new Set<string>() | 
					
						
							| 
									
										
										
										
											2021-05-16 15:34:44 +02:00
										 |  |  |     public readonly isOsmCache: boolean | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-06 03:03:54 +02:00
										 |  |  |     private constructor(locationControl: UIEventSource<Loc>, | 
					
						
							|  |  |  |                         flayer: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }, | 
					
						
							|  |  |  |                         onFail?: ((errorMsg: any) => void)) { | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |         this.layerId = flayer.layerDef.id; | 
					
						
							| 
									
										
										
										
											2021-04-23 20:09:27 +02:00
										 |  |  |         let url = flayer.layerDef.source.geojsonSource.replace("{layer}", flayer.layerDef.id); | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  |         this.name = "GeoJsonSource of " + url; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |         const zoomLevel = flayer.layerDef.source.geojsonZoomLevel; | 
					
						
							| 
									
										
										
										
											2021-05-16 15:34:44 +02:00
										 |  |  |          | 
					
						
							|  |  |  |         this.isOsmCache = flayer.layerDef.source.isOsmCacheLayer; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         this.features = new UIEventSource<{ feature: any; freshness: Date }[]>([]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (zoomLevel === undefined) { | 
					
						
							|  |  |  |             // This is a classic, static geojson layer
 | 
					
						
							|  |  |  |             if (onFail === undefined) { | 
					
						
							| 
									
										
										
										
											2021-05-06 03:03:54 +02:00
										 |  |  |                 onFail = _ => { | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             this.onFail = onFail; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             this.LoadJSONFrom(url) | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2021-04-23 20:09:27 +02:00
										 |  |  |             this.ConfigureDynamicLayer(url, zoomLevel, locationControl, flayer) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * Merges together the layers which have the same source | 
					
						
							|  |  |  |      * @param flayers | 
					
						
							|  |  |  |      * @param locationControl | 
					
						
							|  |  |  |      * @constructor | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public static ConstructMultiSource(flayers: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[], locationControl: UIEventSource<Loc>): GeoJsonSource[] { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const flayersPerSource = new Map<string, { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }[]>(); | 
					
						
							|  |  |  |         for (const flayer of flayers) { | 
					
						
							| 
									
										
										
										
											2021-04-23 20:09:27 +02:00
										 |  |  |             const url = flayer.layerDef.source.geojsonSource?.replace(/{layer}/g, flayer.layerDef.id) | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |             if (url === undefined) { | 
					
						
							|  |  |  |                 continue; | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             if (!flayersPerSource.has(url)) { | 
					
						
							|  |  |  |                 flayersPerSource.set(url, []) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             flayersPerSource.get(url).push(flayer) | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const sources: GeoJsonSource[] = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         flayersPerSource.forEach((flayers, key) => { | 
					
						
							|  |  |  |             if (flayers.length == 1) { | 
					
						
							|  |  |  |                 sources.push(new GeoJsonSource(locationControl, flayers[0])); | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const zoomlevels = Utils.Dedup(flayers.map(flayer => "" + (flayer.layerDef.source.geojsonZoomLevel ?? ""))) | 
					
						
							|  |  |  |             if (zoomlevels.length > 1) { | 
					
						
							|  |  |  |                 throw "Multiple zoomlevels defined for same geojson source " + key | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             let isShown = new UIEventSource<boolean>(true, "IsShown for multiple layers: or of multiple values"); | 
					
						
							|  |  |  |             for (const flayer of flayers) { | 
					
						
							|  |  |  |                 flayer.isDisplayed.addCallbackAndRun(() => { | 
					
						
							|  |  |  |                     let value = false; | 
					
						
							|  |  |  |                     for (const flayer of flayers) { | 
					
						
							|  |  |  |                         value = flayer.isDisplayed.data || value; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     isShown.setData(value); | 
					
						
							|  |  |  |                 }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const source = new GeoJsonSource(locationControl, { | 
					
						
							|  |  |  |                 isDisplayed: isShown, | 
					
						
							|  |  |  |                 layerDef: flayers[0].layerDef // We only care about the source info here
 | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |             sources.push(source) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         return sources; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-06 03:03:54 +02:00
										 |  |  |     private ConfigureDynamicLayer(url: string, zoomLevel: number, locationControl: UIEventSource<Loc>, flayer: { isDisplayed: UIEventSource<boolean>, layerDef: LayerConfig }) { | 
					
						
							|  |  |  |         // This is a dynamic template with a fixed zoom level
 | 
					
						
							|  |  |  |         url = url.replace("{z}", "" + zoomLevel) | 
					
						
							|  |  |  |         const loadedTiles = new Set<string>(); | 
					
						
							|  |  |  |         const self = this; | 
					
						
							|  |  |  |         this.onFail = (msg, url) => { | 
					
						
							|  |  |  |             console.warn(`Could not load geojson layer from`, url, "due to", msg) | 
					
						
							|  |  |  |             loadedTiles.add(url); // We add the url to the 'loadedTiles' in order to not reload it in the future
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const neededTiles = locationControl.map( | 
					
						
							|  |  |  |             _ => { | 
					
						
							|  |  |  |                 // Yup, this is cheating to just get the bounds here
 | 
					
						
							|  |  |  |                 const bounds = State.state.leafletMap.data.getBounds() | 
					
						
							|  |  |  |                 const tileRange = Utils.TileRangeBetween(zoomLevel, bounds.getNorth(), bounds.getEast(), bounds.getSouth(), bounds.getWest()) | 
					
						
							|  |  |  |                 const needed = Utils.MapRange(tileRange, (x, y) => { | 
					
						
							|  |  |  |                     return url.replace("{x}", "" + x).replace("{y}", "" + y); | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |                 return new Set<string>(needed); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             , [flayer.isDisplayed]); | 
					
						
							|  |  |  |         neededTiles.stabilized(250).addCallback((needed: Set<string>) => { | 
					
						
							|  |  |  |             if (needed === undefined) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (!flayer.isDisplayed.data) { | 
					
						
							|  |  |  |                 // No need to download! - the layer is disabled
 | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (locationControl.data.zoom < flayer.layerDef.minzoom) { | 
					
						
							|  |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             needed.forEach(neededTile => { | 
					
						
							|  |  |  |                 if (loadedTiles.has(neededTile)) { | 
					
						
							|  |  |  |                     return; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 loadedTiles.add(neededTile) | 
					
						
							|  |  |  |                 self.LoadJSONFrom(neededTile) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |     private LoadJSONFrom(url: string) { | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |         const eventSource = this.features; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |         const self = this; | 
					
						
							| 
									
										
										
										
											2021-07-03 22:24:12 +02:00
										 |  |  |         Utils.downloadJson(url) | 
					
						
							|  |  |  |             .then(json => { | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |             if (json.elements === [] && json.remarks.indexOf("runtime error") > 0) { | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |                 self.onFail("Runtime error (timeout)", url) | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const time = new Date(); | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |             const newFeatures: { feature: any, freshness: Date } [] = [] | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |             let i = 0; | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |             let skipped = 0; | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |             for (const feature of json.features) { | 
					
						
							|  |  |  |                 if (feature.properties.id === undefined) { | 
					
						
							|  |  |  |                     feature.properties.id = url + "/" + i; | 
					
						
							|  |  |  |                     feature.id = url + "/" + i; | 
					
						
							|  |  |  |                     i++; | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |                 if (self.seenids.has(feature.properties.id)) { | 
					
						
							|  |  |  |                     skipped++; | 
					
						
							|  |  |  |                     continue; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 self.seenids.add(feature.properties.id) | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  |                 let freshness: Date = time; | 
					
						
							| 
									
										
										
										
											2021-04-25 13:25:03 +02:00
										 |  |  |                 if (feature.properties["_last_edit:timestamp"] !== undefined) { | 
					
						
							|  |  |  |                     freshness = new Date(feature["_last_edit:timestamp"]) | 
					
						
							| 
									
										
										
										
											2021-04-22 20:08:03 +02:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 20:08:03 +02:00
										 |  |  |                 newFeatures.push({feature: feature, freshness: freshness}) | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  |             console.debug("Downloaded " + newFeatures.length + " new features and " + skipped + " already seen features from " + url); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (newFeatures.length == 0) { | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |                 return; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-04-23 12:55:38 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-22 03:30:46 +02:00
										 |  |  |             eventSource.setData(eventSource.data.concat(newFeatures)) | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-03 22:24:12 +02:00
										 |  |  |         }).catch(msg => self.onFail(msg, url)) | 
					
						
							| 
									
										
										
										
											2021-03-21 01:32:21 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 20:27:01 +02:00
										 |  |  | } |