forked from MapComplete/MapComplete
		
	More refactoring to fix the tests
This commit is contained in:
		
							parent
							
								
									71285d34cd
								
							
						
					
					
						commit
						b8abbc9505
					
				
					 16 changed files with 507 additions and 418 deletions
				
			
		|  | @ -1,13 +1,15 @@ | |||
| import * as editorlayerindex from "../../assets/editor-layer-index.json" | ||||
| import BaseLayer from "../../Models/BaseLayer"; | ||||
| import * as L from "leaflet"; | ||||
| import {TileLayer} from "leaflet"; | ||||
| import * as X from "leaflet-providers"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import {GeoOperations} from "../GeoOperations"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| 
 | ||||
| export interface AvailableBaseLayersObj { | ||||
|     readonly osmCarto: BaseLayer; | ||||
|     layerOverview: BaseLayer[]; | ||||
|     AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]>  | ||||
|     SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> ; | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Calculates which layers are available at the current location | ||||
|  * Changes the basemap | ||||
|  | @ -15,284 +17,24 @@ import Loc from "../../Models/Loc"; | |||
| export default class AvailableBaseLayers { | ||||
|      | ||||
|      | ||||
|     public static osmCarto: BaseLayer = | ||||
|         { | ||||
|             id: "osm", | ||||
|             name: "OpenStreetMap", | ||||
|             layer: () => AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap", | ||||
|                 "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright", | ||||
|                 19, | ||||
|                 false, false), | ||||
|             feature: null, | ||||
|             max_zoom: 19, | ||||
|             min_zoom: 0, | ||||
|             isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context)
 | ||||
|             category: "osmbasedmap" | ||||
|     public static layerOverview: BaseLayer[]; | ||||
|     public static osmCarto: BaseLayer; | ||||
| 
 | ||||
|     private static implementation: AvailableBaseLayersObj | ||||
|      | ||||
|     static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { | ||||
|         return AvailableBaseLayers.implementation.AvailableLayersAt(location); | ||||
|     } | ||||
| 
 | ||||
|     public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex()); | ||||
| 
 | ||||
|     public static AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { | ||||
|         const source = location.map( | ||||
|             (currentLocation) => { | ||||
| 
 | ||||
|                 if (currentLocation === undefined) { | ||||
|                     return AvailableBaseLayers.layerOverview; | ||||
|                 } | ||||
| 
 | ||||
|                 const currentLayers = source?.data; // A bit unorthodox - I know
 | ||||
|                 const newLayers = AvailableBaseLayers.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat); | ||||
| 
 | ||||
|                 if (currentLayers === undefined) { | ||||
|                     return newLayers; | ||||
|                 } | ||||
|                 if (newLayers.length !== currentLayers.length) { | ||||
|                     return newLayers; | ||||
|                 } | ||||
|                 for (let i = 0; i < newLayers.length; i++) { | ||||
|                     if (newLayers[i].name !== currentLayers[i].name) { | ||||
|                         return newLayers; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return currentLayers; | ||||
|             }); | ||||
|         return source; | ||||
|     } | ||||
| 
 | ||||
|     public static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { | ||||
|         return AvailableBaseLayers.AvailableLayersAt(location).map(available => { | ||||
|             // First float all 'best layers' to the top
 | ||||
|             available.sort((a, b) => { | ||||
|                     if (a.isBest && b.isBest) { | ||||
|                         return 0; | ||||
|                     } | ||||
|                     if (!a.isBest) { | ||||
|                         return 1 | ||||
|                     } | ||||
| 
 | ||||
|                     return -1; | ||||
|                 } | ||||
|             ) | ||||
| 
 | ||||
|             if (preferedCategory.data === undefined) { | ||||
|                 return available[0] | ||||
|             } | ||||
| 
 | ||||
|             let prefered: string [] | ||||
|             if (typeof preferedCategory.data === "string") { | ||||
|                 prefered = [preferedCategory.data] | ||||
|             } else { | ||||
|                 prefered = preferedCategory.data; | ||||
|             } | ||||
| 
 | ||||
|             prefered.reverse(); | ||||
|             for (const category of prefered) { | ||||
|                 //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 | ||||
|                 available.sort((a, b) => { | ||||
|                         if (a.category === category && b.category === category) { | ||||
|                             return 0; | ||||
|                         } | ||||
|                         if (a.category !== category) { | ||||
|                             return 1 | ||||
|                         } | ||||
| 
 | ||||
|                         return -1; | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|             return available[0] | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private static CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { | ||||
|         const availableLayers = [AvailableBaseLayers.osmCarto] | ||||
|         const globalLayers = []; | ||||
|         for (const layerOverviewItem of AvailableBaseLayers.layerOverview) { | ||||
|             const layer = layerOverviewItem; | ||||
| 
 | ||||
|             if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) { | ||||
|                 globalLayers.push(layer); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (lon === undefined || lat === undefined) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (GeoOperations.inside([lon, lat], layer.feature)) { | ||||
|                 availableLayers.push(layer); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return availableLayers.concat(globalLayers); | ||||
|     } | ||||
| 
 | ||||
|     private static LoadRasterIndex(): BaseLayer[] { | ||||
|         const layers: BaseLayer[] = [] | ||||
|         // @ts-ignore
 | ||||
|         const features = editorlayerindex.features; | ||||
|         for (const i in features) { | ||||
|             const layer = features[i]; | ||||
|             const props = layer.properties; | ||||
| 
 | ||||
|             if (props.id === "Bing") { | ||||
|                 // Doesnt work
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.id === "MAPNIK") { | ||||
|                 // Already added by default
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.overlay) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.url.toLowerCase().indexOf("apikey") > 0) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.max_zoom < 19) { | ||||
|                 // We want users to zoom to level 19 when adding a point
 | ||||
|                 // If they are on a layer which hasn't enough precision, they can not zoom far enough. This is confusing, so we don't use this layer
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.name === undefined) { | ||||
|                 console.warn("Editor layer index: name not defined on ", props) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             const leafletLayer: () => TileLayer = () => AvailableBaseLayers.CreateBackgroundLayer( | ||||
|                 props.id, | ||||
|                 props.name, | ||||
|                 props.url, | ||||
|                 props.name, | ||||
|                 props.license_url, | ||||
|                 props.max_zoom, | ||||
|                 props.type.toLowerCase() === "wms", | ||||
|                 props.type.toLowerCase() === "wmts" | ||||
|             ) | ||||
| 
 | ||||
|             // Note: if layer.geometry is null, there is global coverage for this layer
 | ||||
|             layers.push({ | ||||
|                 id: props.id, | ||||
|                 max_zoom: props.max_zoom ?? 25, | ||||
|                 min_zoom: props.min_zoom ?? 1, | ||||
|                 name: props.name, | ||||
|                 layer: leafletLayer, | ||||
|                 feature: layer, | ||||
|                 isBest: props.best ?? false, | ||||
|                 category: props.category | ||||
|             }); | ||||
|         } | ||||
|         return layers; | ||||
|     } | ||||
| 
 | ||||
|     private static LoadProviderIndex(): BaseLayer[] { | ||||
|         // @ts-ignore
 | ||||
|         X; // Import X to make sure the namespace is not optimized away
 | ||||
|         function l(id: string, name: string): BaseLayer { | ||||
|             try { | ||||
|                 const layer: any = () => L.tileLayer.provider(id, undefined); | ||||
|                 return { | ||||
|                     feature: null, | ||||
|                     id: id, | ||||
|                     name: name, | ||||
|                     layer: layer, | ||||
|                     min_zoom: layer.minzoom, | ||||
|                     max_zoom: layer.maxzoom, | ||||
|                     category: "osmbasedmap", | ||||
|                     isBest: false | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.error("Could not find provided layer", name, e); | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const layers = [ | ||||
|             l("CyclOSM", "CyclOSM - A bicycle oriented map"), | ||||
|             l("Stamen.TonerLite", "Toner Lite (by Stamen)"), | ||||
|             l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"), | ||||
|             l("Stamen.Watercolor", "Watercolor (by Stamen)"), | ||||
|             l("Stadia.OSMBright", "Osm Bright (by Stadia)"), | ||||
|             l("CartoDB.Positron", "Positron (by CartoDB)"), | ||||
|             l("CartoDB.PositronNoLabels", "Positron  - no labels (by CartoDB)"), | ||||
|             l("CartoDB.Voyager", "Voyager (by CartoDB)"), | ||||
|             l("CartoDB.VoyagerNoLabels", "Voyager  - no labels (by CartoDB)"), | ||||
|         ]; | ||||
|         return Utils.NoNull(layers); | ||||
|     static SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { | ||||
|         return AvailableBaseLayers.implementation.SelectBestLayerAccordingTo(location, preferedCategory); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Converts a layer from the editor-layer-index into a tilelayer usable by leaflet | ||||
|      */ | ||||
|     private static CreateBackgroundLayer(id: string, name: string, url: string, attribution: string, attributionUrl: string, | ||||
|                                          maxZoom: number, isWms: boolean, isWMTS?: boolean): TileLayer { | ||||
| 
 | ||||
|         url = url.replace("{zoom}", "{z}") | ||||
|             .replace("&BBOX={bbox}", "") | ||||
|             .replace("&bbox={bbox}", ""); | ||||
| 
 | ||||
|         const subdomainsMatch = url.match(/{switch:[^}]*}/) | ||||
|         let domains: string[] = []; | ||||
|         if (subdomainsMatch !== null) { | ||||
|             let domainsStr = subdomainsMatch[0].substr("{switch:".length); | ||||
|             domainsStr = domainsStr.substr(0, domainsStr.length - 1); | ||||
|             domains = domainsStr.split(","); | ||||
|             url = url.replace(/{switch:[^}]*}/, "{s}") | ||||
|     public static implement(backend: AvailableBaseLayersObj){ | ||||
|         AvailableBaseLayers.layerOverview = backend.layerOverview | ||||
|         AvailableBaseLayers.osmCarto = backend.osmCarto | ||||
|         AvailableBaseLayers.implementation = backend | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|         if (isWms) { | ||||
|             url = url.replace("&SRS={proj}", ""); | ||||
|             url = url.replace("&srs={proj}", ""); | ||||
|             const paramaters = ["format", "layers", "version", "service", "request", "styles", "transparent", "version"]; | ||||
|             const urlObj = new URL(url); | ||||
| 
 | ||||
|             const isUpper = urlObj.searchParams["LAYERS"] !== null; | ||||
|             const options = { | ||||
|                 maxZoom: maxZoom ?? 19, | ||||
|                 attribution: attribution + " | ", | ||||
|                 subdomains: domains, | ||||
|                 uppercase: isUpper, | ||||
|                 transparent: false | ||||
|             }; | ||||
| 
 | ||||
|             for (const paramater of paramaters) { | ||||
|                 let p = paramater; | ||||
|                 if (isUpper) { | ||||
|                     p = paramater.toUpperCase(); | ||||
|                 } | ||||
|                 options[paramater] = urlObj.searchParams.get(p); | ||||
|             } | ||||
| 
 | ||||
|             if (options.transparent === null) { | ||||
|                 options.transparent = false; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             return L.tileLayer.wms(urlObj.protocol + "//" + urlObj.host + urlObj.pathname, options); | ||||
|         } | ||||
| 
 | ||||
|         if (attributionUrl) { | ||||
|             attribution = `<a href='${attributionUrl}' target='_blank'>${attribution}</a>`; | ||||
|         } | ||||
| 
 | ||||
|         return L.tileLayer(url, | ||||
|             { | ||||
|                 attribution: attribution, | ||||
|                 maxZoom: maxZoom, | ||||
|                 minZoom: 1, | ||||
|                 // @ts-ignore
 | ||||
|                 wmts: isWMTS ?? false, | ||||
|                 subdomains: domains | ||||
|             }); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										292
									
								
								Logic/Actors/AvailableBaseLayersImplementation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										292
									
								
								Logic/Actors/AvailableBaseLayersImplementation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,292 @@ | |||
| import BaseLayer from "../../Models/BaseLayer"; | ||||
| import {UIEventSource} from "../UIEventSource"; | ||||
| import Loc from "../../Models/Loc"; | ||||
| import {GeoOperations} from "../GeoOperations"; | ||||
| import * as editorlayerindex from "../../assets/editor-layer-index.json"; | ||||
| import {TileLayer} from "leaflet"; | ||||
| import * as X from "leaflet-providers"; | ||||
| import * as L from "leaflet"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import {AvailableBaseLayersObj} from "./AvailableBaseLayers"; | ||||
| 
 | ||||
| export default class AvailableBaseLayersImplementation implements AvailableBaseLayersObj{ | ||||
| 
 | ||||
|     public readonly osmCarto: BaseLayer = | ||||
|         { | ||||
|             id: "osm", | ||||
|             name: "OpenStreetMap", | ||||
|             layer: () => AvailableBaseLayersImplementation.CreateBackgroundLayer("osm", "OpenStreetMap", | ||||
|                 "https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright", | ||||
|                 19, | ||||
|                 false, false), | ||||
|             feature: null, | ||||
|             max_zoom: 19, | ||||
|             min_zoom: 0, | ||||
|             isBest: false, // This is a lie! Of course OSM is the best map! (But not in this context)
 | ||||
|             category: "osmbasedmap" | ||||
|         } | ||||
| 
 | ||||
|     public layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat(AvailableBaseLayersImplementation.LoadProviderIndex()); | ||||
| 
 | ||||
|     public AvailableLayersAt(location: UIEventSource<Loc>): UIEventSource<BaseLayer[]> { | ||||
|         const source = location.map( | ||||
|             (currentLocation) => { | ||||
| 
 | ||||
|                 if (currentLocation === undefined) { | ||||
|                     return this.layerOverview; | ||||
|                 } | ||||
| 
 | ||||
|                 const currentLayers = source?.data; // A bit unorthodox - I know
 | ||||
|                 const newLayers = this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat); | ||||
| 
 | ||||
|                 if (currentLayers === undefined) { | ||||
|                     return newLayers; | ||||
|                 } | ||||
|                 if (newLayers.length !== currentLayers.length) { | ||||
|                     return newLayers; | ||||
|                 } | ||||
|                 for (let i = 0; i < newLayers.length; i++) { | ||||
|                     if (newLayers[i].name !== currentLayers[i].name) { | ||||
|                         return newLayers; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 return currentLayers; | ||||
|             }); | ||||
|         return source; | ||||
|     } | ||||
| 
 | ||||
|     public SelectBestLayerAccordingTo(location: UIEventSource<Loc>, preferedCategory: UIEventSource<string | string[]>): UIEventSource<BaseLayer> { | ||||
|         return this.AvailableLayersAt(location).map(available => { | ||||
|             // First float all 'best layers' to the top
 | ||||
|             available.sort((a, b) => { | ||||
|                     if (a.isBest && b.isBest) { | ||||
|                         return 0; | ||||
|                     } | ||||
|                     if (!a.isBest) { | ||||
|                         return 1 | ||||
|                     } | ||||
| 
 | ||||
|                     return -1; | ||||
|                 } | ||||
|             ) | ||||
| 
 | ||||
|             if (preferedCategory.data === undefined) { | ||||
|                 return available[0] | ||||
|             } | ||||
| 
 | ||||
|             let prefered: string [] | ||||
|             if (typeof preferedCategory.data === "string") { | ||||
|                 prefered = [preferedCategory.data] | ||||
|             } else { | ||||
|                 prefered = preferedCategory.data; | ||||
|             } | ||||
| 
 | ||||
|             prefered.reverse(); | ||||
|             for (const category of prefered) { | ||||
|                 //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 | ||||
|                 available.sort((a, b) => { | ||||
|                         if (a.category === category && b.category === category) { | ||||
|                             return 0; | ||||
|                         } | ||||
|                         if (a.category !== category) { | ||||
|                             return 1 | ||||
|                         } | ||||
| 
 | ||||
|                         return -1; | ||||
|                     } | ||||
|                 ) | ||||
|             } | ||||
|             return available[0] | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { | ||||
|         const availableLayers = [this.osmCarto] | ||||
|         const globalLayers = []; | ||||
|         for (const layerOverviewItem of this.layerOverview) { | ||||
|             const layer = layerOverviewItem; | ||||
| 
 | ||||
|             if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) { | ||||
|                 globalLayers.push(layer); | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (lon === undefined || lat === undefined) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (GeoOperations.inside([lon, lat], layer.feature)) { | ||||
|                 availableLayers.push(layer); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return availableLayers.concat(globalLayers); | ||||
|     } | ||||
| 
 | ||||
|     private static LoadRasterIndex(): BaseLayer[] { | ||||
|         const layers: BaseLayer[] = [] | ||||
|         // @ts-ignore
 | ||||
|         const features = editorlayerindex.features; | ||||
|         for (const i in features) { | ||||
|             const layer = features[i]; | ||||
|             const props = layer.properties; | ||||
| 
 | ||||
|             if (props.id === "Bing") { | ||||
|                 // Doesnt work
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.id === "MAPNIK") { | ||||
|                 // Already added by default
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.overlay) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.url.toLowerCase().indexOf("apikey") > 0) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.max_zoom < 19) { | ||||
|                 // We want users to zoom to level 19 when adding a point
 | ||||
|                 // If they are on a layer which hasn't enough precision, they can not zoom far enough. This is confusing, so we don't use this layer
 | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             if (props.name === undefined) { | ||||
|                 console.warn("Editor layer index: name not defined on ", props) | ||||
|                 continue | ||||
|             } | ||||
| 
 | ||||
|             const leafletLayer: () => TileLayer = () => AvailableBaseLayersImplementation.CreateBackgroundLayer( | ||||
|                 props.id, | ||||
|                 props.name, | ||||
|                 props.url, | ||||
|                 props.name, | ||||
|                 props.license_url, | ||||
|                 props.max_zoom, | ||||
|                 props.type.toLowerCase() === "wms", | ||||
|                 props.type.toLowerCase() === "wmts" | ||||
|             ) | ||||
| 
 | ||||
|             // Note: if layer.geometry is null, there is global coverage for this layer
 | ||||
|             layers.push({ | ||||
|                 id: props.id, | ||||
|                 max_zoom: props.max_zoom ?? 25, | ||||
|                 min_zoom: props.min_zoom ?? 1, | ||||
|                 name: props.name, | ||||
|                 layer: leafletLayer, | ||||
|                 feature: layer, | ||||
|                 isBest: props.best ?? false, | ||||
|                 category: props.category | ||||
|             }); | ||||
|         } | ||||
|         return layers; | ||||
|     } | ||||
| 
 | ||||
|     private static LoadProviderIndex(): BaseLayer[] { | ||||
|         // @ts-ignore
 | ||||
|         X; // Import X to make sure the namespace is not optimized away
 | ||||
|         function l(id: string, name: string): BaseLayer { | ||||
|             try { | ||||
|                 const layer: any = () => L.tileLayer.provider(id, undefined); | ||||
|                 return { | ||||
|                     feature: null, | ||||
|                     id: id, | ||||
|                     name: name, | ||||
|                     layer: layer, | ||||
|                     min_zoom: layer.minzoom, | ||||
|                     max_zoom: layer.maxzoom, | ||||
|                     category: "osmbasedmap", | ||||
|                     isBest: false | ||||
|                 } | ||||
|             } catch (e) { | ||||
|                 console.error("Could not find provided layer", name, e); | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         const layers = [ | ||||
|             l("CyclOSM", "CyclOSM - A bicycle oriented map"), | ||||
|             l("Stamen.TonerLite", "Toner Lite (by Stamen)"), | ||||
|             l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"), | ||||
|             l("Stamen.Watercolor", "Watercolor (by Stamen)"), | ||||
|             l("Stadia.OSMBright", "Osm Bright (by Stadia)"), | ||||
|             l("CartoDB.Positron", "Positron (by CartoDB)"), | ||||
|             l("CartoDB.PositronNoLabels", "Positron  - no labels (by CartoDB)"), | ||||
|             l("CartoDB.Voyager", "Voyager (by CartoDB)"), | ||||
|             l("CartoDB.VoyagerNoLabels", "Voyager  - no labels (by CartoDB)"), | ||||
|         ]; | ||||
|         return Utils.NoNull(layers); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Converts a layer from the editor-layer-index into a tilelayer usable by leaflet | ||||
|      */ | ||||
|     private static CreateBackgroundLayer(id: string, name: string, url: string, attribution: string, attributionUrl: string, | ||||
|                                          maxZoom: number, isWms: boolean, isWMTS?: boolean): TileLayer { | ||||
| 
 | ||||
|         url = url.replace("{zoom}", "{z}") | ||||
|             .replace("&BBOX={bbox}", "") | ||||
|             .replace("&bbox={bbox}", ""); | ||||
| 
 | ||||
|         const subdomainsMatch = url.match(/{switch:[^}]*}/) | ||||
|         let domains: string[] = []; | ||||
|         if (subdomainsMatch !== null) { | ||||
|             let domainsStr = subdomainsMatch[0].substr("{switch:".length); | ||||
|             domainsStr = domainsStr.substr(0, domainsStr.length - 1); | ||||
|             domains = domainsStr.split(","); | ||||
|             url = url.replace(/{switch:[^}]*}/, "{s}") | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         if (isWms) { | ||||
|             url = url.replace("&SRS={proj}", ""); | ||||
|             url = url.replace("&srs={proj}", ""); | ||||
|             const paramaters = ["format", "layers", "version", "service", "request", "styles", "transparent", "version"]; | ||||
|             const urlObj = new URL(url); | ||||
| 
 | ||||
|             const isUpper = urlObj.searchParams["LAYERS"] !== null; | ||||
|             const options = { | ||||
|                 maxZoom: maxZoom ?? 19, | ||||
|                 attribution: attribution + " | ", | ||||
|                 subdomains: domains, | ||||
|                 uppercase: isUpper, | ||||
|                 transparent: false | ||||
|             }; | ||||
| 
 | ||||
|             for (const paramater of paramaters) { | ||||
|                 let p = paramater; | ||||
|                 if (isUpper) { | ||||
|                     p = paramater.toUpperCase(); | ||||
|                 } | ||||
|                 options[paramater] = urlObj.searchParams.get(p); | ||||
|             } | ||||
| 
 | ||||
|             if (options.transparent === null) { | ||||
|                 options.transparent = false; | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             return L.tileLayer.wms(urlObj.protocol + "//" + urlObj.host + urlObj.pathname, options); | ||||
|         } | ||||
| 
 | ||||
|         if (attributionUrl) { | ||||
|             attribution = `<a href='${attributionUrl}' target='_blank'>${attribution}</a>`; | ||||
|         } | ||||
| 
 | ||||
|         return L.tileLayer(url, | ||||
|             { | ||||
|                 attribution: attribution, | ||||
|                 maxZoom: maxZoom, | ||||
|                 minZoom: 1, | ||||
|                 // @ts-ignore
 | ||||
|                 wmts: isWMTS ?? false, | ||||
|                 subdomains: domains | ||||
|             }); | ||||
|     } | ||||
| } | ||||
|  | @ -179,7 +179,7 @@ export default class OverpassFeatureSource implements FeatureSource { | |||
| 
 | ||||
|         self.retries.setData(0); | ||||
|         try { | ||||
|             data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date)); | ||||
|             data.features.forEach(feature => SimpleMetaTagger.objectMetaInfo.applyMetaTagsOnFeature(feature, date, undefined)); | ||||
|             self.features.setData(data.features.map(f => ({feature: f, freshness: date}))); | ||||
|             return [bounds, date, layersToDownload]; | ||||
|         } catch (e) { | ||||
|  |  | |||
|  | @ -81,4 +81,5 @@ export default class StrayClickHandler { | |||
|     } | ||||
| 
 | ||||
|      | ||||
| 
 | ||||
| } | ||||
|  | @ -121,50 +121,7 @@ export default class FeaturePipelineState extends MapState { | |||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public setupClickDialogOnMap(filterViewIsOpened: UIEventSource<boolean>, leafletMap: UIEventSource<any>) { | ||||
|    | ||||
|         const self = this | ||||
|         function setup(){ | ||||
|             let presetCount = 0; | ||||
|             for (const layer of self.layoutToUse.layers) { | ||||
|                 for (const preset of layer.presets) { | ||||
|                     presetCount++; | ||||
|                 } | ||||
|             } | ||||
|             if (presetCount == 0) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const newPointDialogIsShown = new UIEventSource<boolean>(false); | ||||
|             const addNewPoint = new ScrollableFullScreen( | ||||
|                 () => Translations.t.general.add.title.Clone(), | ||||
|                 () => new SimpleAddUI(newPointDialogIsShown, filterViewIsOpened, self), | ||||
|                 "new", | ||||
|                 newPointDialogIsShown | ||||
|             ); | ||||
|             addNewPoint.isShown.addCallback((isShown) => { | ||||
|                 if (!isShown) { | ||||
|                     self.LastClickLocation.setData(undefined); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             new StrayClickHandler( | ||||
|                 self.LastClickLocation, | ||||
|                 self.selectedElement, | ||||
|                 self.filteredLayers, | ||||
|                 leafletMap, | ||||
|                 addNewPoint | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         this.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => { | ||||
|             if (addNewAllowed) { | ||||
|                 setup() | ||||
|                 return true; | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -7,9 +7,6 @@ import BackgroundLayerResetter from "../Actors/BackgroundLayerResetter"; | |||
| import Attribution from "../../UI/BigComponents/Attribution"; | ||||
| import Minimap, {MinimapObj} from "../../UI/Base/Minimap"; | ||||
| import {Tiles} from "../../Models/TileRange"; | ||||
| import * as L from "leaflet"; | ||||
| import Img from "../../UI/Base/Img"; | ||||
| import Svg from "../../Svg"; | ||||
| import BaseUIElement from "../../UI/BaseUIElement"; | ||||
| import FilteredLayer from "../../Models/FilteredLayer"; | ||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"; | ||||
|  | @ -26,7 +23,7 @@ export default class MapState extends UserRelatedState { | |||
|     /** | ||||
|      The leaflet instance of the big basemap | ||||
|      */ | ||||
|     public leafletMap = new UIEventSource<L.Map>(undefined, "leafletmap"); | ||||
|     public leafletMap = new UIEventSource<any /*L.Map*/>(undefined, "leafletmap"); | ||||
|     /** | ||||
|      * A list of currently available background layers | ||||
|      */ | ||||
|  | @ -68,7 +65,6 @@ export default class MapState extends UserRelatedState { | |||
|     public overlayToggles: { config: TilesourceConfig, isDisplayed: UIEventSource<boolean> }[] | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     constructor(layoutToUse: LayoutConfig) { | ||||
|         super(layoutToUse); | ||||
| 
 | ||||
|  | @ -130,47 +126,9 @@ export default class MapState extends UserRelatedState { | |||
| 
 | ||||
|         this.lockBounds() | ||||
|         this.AddAllOverlaysToMap(this.leafletMap) | ||||
|         this.addHomeMarker() | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private addHomeMarker() { | ||||
|         const leafletMap = this.leafletMap | ||||
|         const osmConnection = this.osmConnection | ||||
| 
 | ||||
|         function addHomeMarker() { | ||||
|             const userDetails = osmConnection.userDetails.data; | ||||
|             if (userDetails === undefined) { | ||||
|                 return false; | ||||
|             } | ||||
|             const home = userDetails.home; | ||||
|             if (home === undefined) { | ||||
|                 return userDetails.loggedIn; // If logged in, the home is not set and we unregister. If not logged in, we stay registered if a login still comes
 | ||||
|             } | ||||
|             const leaflet = leafletMap.data; | ||||
|             if (leaflet === undefined) { | ||||
|                 return false; | ||||
|             } | ||||
|             const color = getComputedStyle(document.body).getPropertyValue( | ||||
|                 "--subtle-detail-color" | ||||
|             ); | ||||
|             const icon = L.icon({ | ||||
|                 iconUrl: Img.AsData( | ||||
|                     Svg.home_white_bg.replace(/#ffffff/g, color) | ||||
|                 ), | ||||
|                 iconSize: [30, 30], | ||||
|                 iconAnchor: [15, 15], | ||||
|             }); | ||||
|             const marker = L.marker([home.lat, home.lon], {icon: icon}); | ||||
|             marker.addTo(leaflet); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         osmConnection.userDetails.addCallbackAndRunD(_ => addHomeMarker()); | ||||
|         leafletMap.addCallbackAndRunD(_ => addHomeMarker()) | ||||
|     } | ||||
| 
 | ||||
|     private lockBounds() { | ||||
|         const layout = this.layoutToUse; | ||||
|  | @ -198,6 +156,7 @@ export default class MapState extends UserRelatedState { | |||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private InitializeFilteredLayers() { | ||||
|         // Initialize the filtered layers state
 | ||||
| 
 | ||||
|  | @ -252,8 +211,8 @@ export default class MapState extends UserRelatedState { | |||
|         return new UIEventSource<FilteredLayer[]>(flayers); | ||||
|     } | ||||
| 
 | ||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>){ | ||||
|         const initialized =new Set() | ||||
|     public AddAllOverlaysToMap(leafletMap: UIEventSource<any>) { | ||||
|         const initialized = new Set() | ||||
|         for (const overlayToggle of this.overlayToggles) { | ||||
|             new ShowOverlayLayer(overlayToggle.config, leafletMap, overlayToggle.isDisplayed) | ||||
|             initialized.add(overlayToggle.config) | ||||
|  |  | |||
|  | @ -9,6 +9,8 @@ import {Utils} from "../../Utils"; | |||
| import Locale from "../../UI/i18n/Locale"; | ||||
| import ElementsState from "./ElementsState"; | ||||
| import SelectedElementTagsUpdater from "../Actors/SelectedElementTagsUpdater"; | ||||
| import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource"; | ||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | ||||
| 
 | ||||
| /** | ||||
|  * The part of the state which keeps track of user-related stuff, e.g. the OSM-connection, | ||||
|  | @ -34,6 +36,10 @@ export default class UserRelatedState extends ElementsState { | |||
|      * WHich other themes the user previously visited | ||||
|      */ | ||||
|     public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; | ||||
|     /** | ||||
|      * A feature source containing the current home location of the user | ||||
|      */ | ||||
|     public homeLocation: FeatureSource | ||||
| 
 | ||||
|     constructor(layoutToUse: LayoutConfig) { | ||||
|         super(layoutToUse); | ||||
|  | @ -104,4 +110,37 @@ export default class UserRelatedState extends ElementsState { | |||
|             .ping(); | ||||
|     } | ||||
| 
 | ||||
|     private initHomeLocation() { | ||||
|         const empty = [] | ||||
|         const feature = UIEventSource.ListStabilized(this.osmConnection.userDetails.map(userDetails => { | ||||
| 
 | ||||
|             if (userDetails === undefined) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             const home = userDetails.home; | ||||
|             if (home === undefined) { | ||||
|                 return undefined; | ||||
|             } | ||||
|             return [home.lon, home.lat] | ||||
|         })).map(homeLonLat => { | ||||
|             if(homeLonLat === undefined){ | ||||
|                 return empty | ||||
|             } | ||||
|             return [{ | ||||
|                 "type": "Feature", | ||||
|                 "properties": { | ||||
|                     "user:home": "yes", | ||||
|                     "_lon": homeLonLat[0], | ||||
|                     "_lat": homeLonLat[1] | ||||
|                 }, | ||||
|                 "geometry": { | ||||
|                     "type": "Point", | ||||
|                     "coordinates": homeLonLat | ||||
|                 } | ||||
|             }] | ||||
|         }) | ||||
| 
 | ||||
|         this.homeLocation = new StaticFeatureSource(feature, false) | ||||
|     } | ||||
|      | ||||
| } | ||||
|  | @ -136,7 +136,7 @@ export class UIEventSource<T> { | |||
|             if (oldList === list) { | ||||
|                 return; | ||||
|             } | ||||
|             if (oldList.length !== list.length) { | ||||
|             if (oldList === undefined || oldList.length !== list.length) { | ||||
|                 stable.setData(list); | ||||
|                 return; | ||||
|             } | ||||
|  |  | |||
|  | @ -117,7 +117,7 @@ export interface LayoutConfigJson { | |||
|      * These tiles are using a ceratin zoom level, that can be controlled here | ||||
|      * Default: overpassMaxZoom + 1 | ||||
|      */ | ||||
|     osmApiTileSize: number | ||||
|     osmApiTileSize?: number | ||||
|      | ||||
|     /** | ||||
|      * A tagrendering depicts how to show some tags or how to show a question for it. | ||||
|  |  | |||
|  | @ -17,6 +17,12 @@ import {VariableUiElement} from "./Base/VariableUIElement"; | |||
| import LeftControls from "./BigComponents/LeftControls"; | ||||
| import RightControls from "./BigComponents/RightControls"; | ||||
| import CenterMessageBox from "./CenterMessageBox"; | ||||
| import ShowDataLayer from "./ShowDataLayer/ShowDataLayer"; | ||||
| import AllKnownLayers from "../Customizations/AllKnownLayers"; | ||||
| import ScrollableFullScreen from "./Base/ScrollableFullScreen"; | ||||
| import Translations from "./i18n/Translations"; | ||||
| import SimpleAddUI from "./BigComponents/SimpleAddUI"; | ||||
| import StrayClickHandler from "../Logic/Actors/StrayClickHandler"; | ||||
| 
 | ||||
| export class DefaultGuiState { | ||||
|     public readonly welcomeMessageIsOpened; | ||||
|  | @ -85,9 +91,9 @@ export default class DefaultGUI { | |||
|         state.mainMapObject.SetClass("w-full h-full") | ||||
|             .AttachTo("leafletDiv") | ||||
| 
 | ||||
|         state.setupClickDialogOnMap( | ||||
|         this.setupClickDialogOnMap( | ||||
|             guiState.filterViewIsOpened, | ||||
|             state.leafletMap | ||||
|             state | ||||
|         ) | ||||
| 
 | ||||
|         this.InitWelcomeMessage(); | ||||
|  | @ -126,6 +132,14 @@ export default class DefaultGUI { | |||
|             .getElementById("centermessage") | ||||
|             .classList.add("pointer-events-none"); | ||||
|          | ||||
|          | ||||
|         new ShowDataLayer({ | ||||
|             leafletMap: state.leafletMap, | ||||
|             layerToShow: AllKnownLayers.sharedLayers.get("home_location"), | ||||
|             features:            state.homeLocation, | ||||
|             enablePopups: false, | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private InitWelcomeMessage() { | ||||
|  | @ -158,4 +172,48 @@ export default class DefaultGUI { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public setupClickDialogOnMap(filterViewIsOpened: UIEventSource<boolean>, state: FeaturePipelineState) { | ||||
| 
 | ||||
|         function setup(){ | ||||
|             let presetCount = 0; | ||||
|             for (const layer of state.layoutToUse.layers) { | ||||
|                 for (const preset of layer.presets) { | ||||
|                     presetCount++; | ||||
|                 } | ||||
|             } | ||||
|             if (presetCount == 0) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const newPointDialogIsShown = new UIEventSource<boolean>(false); | ||||
|             const addNewPoint = new ScrollableFullScreen( | ||||
|                 () => Translations.t.general.add.title.Clone(), | ||||
|                 () => new SimpleAddUI(newPointDialogIsShown, filterViewIsOpened, state), | ||||
|                 "new", | ||||
|                 newPointDialogIsShown | ||||
|             ); | ||||
|             addNewPoint.isShown.addCallback((isShown) => { | ||||
|                 if (!isShown) { | ||||
|                     state.LastClickLocation.setData(undefined); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             new StrayClickHandler( | ||||
|                 state.LastClickLocation, | ||||
|                 state.selectedElement, | ||||
|                 state.filteredLayers, | ||||
|                 state.leafletMap, | ||||
|                 addNewPoint | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|         state.featureSwitchAddNew.addCallbackAndRunD(addNewAllowed => { | ||||
|             if (addNewAllowed) { | ||||
|                 setup() | ||||
|                 return true; | ||||
|             } | ||||
|         }) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -18,6 +18,7 @@ import {Unit} from "../../Models/Unit"; | |||
| import {FixedInputElement} from "./FixedInputElement"; | ||||
| import WikidataSearchBox from "../Wikipedia/WikidataSearchBox"; | ||||
| import Wikidata from "../../Logic/Web/Wikidata"; | ||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||
| 
 | ||||
| interface TextFieldDef { | ||||
|     name: string, | ||||
|  | @ -35,8 +36,6 @@ interface TextFieldDef { | |||
| 
 | ||||
| export default class ValidatedTextField { | ||||
| 
 | ||||
|     public static bestLayerAt: (location: UIEventSource<Loc>, preferences: UIEventSource<string[]>) => any | ||||
| 
 | ||||
|     public static tpList: TextFieldDef[] = [ | ||||
|         ValidatedTextField.tp( | ||||
|             "string", | ||||
|  | @ -93,7 +92,7 @@ export default class ValidatedTextField { | |||
|                 }) | ||||
|                 if (args[1]) { | ||||
|                     // We have a prefered map!
 | ||||
|                     options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( | ||||
|                     options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo( | ||||
|                         location, new UIEventSource<string[]>(args[1].split(",")) | ||||
|                     ) | ||||
|                 } | ||||
|  | @ -137,7 +136,7 @@ export default class ValidatedTextField { | |||
|                 }) | ||||
|                 if (args[1]) { | ||||
|                     // We have a prefered map!
 | ||||
|                     options.mapBackgroundLayer = ValidatedTextField.bestLayerAt( | ||||
|                     options.mapBackgroundLayer = AvailableBaseLayers.SelectBestLayerAccordingTo( | ||||
|                         location, new UIEventSource<string[]>(args[1].split(",")) | ||||
|                     ) | ||||
|                 } | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | |||
| import MoveConfig from "../../Models/ThemeConfig/MoveConfig"; | ||||
| import {ElementStorage} from "../../Logic/ElementStorage"; | ||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | ||||
| import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers"; | ||||
| 
 | ||||
| interface MoveReason { | ||||
|     text: Translation | string, | ||||
|  | @ -138,7 +139,7 @@ export default class MoveWizard extends Toggle { | |||
|             const locationInput = new LocationInput({ | ||||
|                 minZoom: reason.minZoom, | ||||
|                 centerLocation: loc, | ||||
|                 mapBackground: ValidatedTextField.bestLayerAt(loc, new UIEventSource(background)) | ||||
|                 mapBackground:AvailableBaseLayers.SelectBestLayerAccordingTo(loc, new UIEventSource(background)) | ||||
|             }) | ||||
| 
 | ||||
|             if (reason.lockBounds) { | ||||
|  |  | |||
|  | @ -4,42 +4,16 @@ import * as L from "leaflet"; | |||
| 
 | ||||
| export default class ShowOverlayLayer { | ||||
| 
 | ||||
|     public static implementation: (config: TilesourceConfig, | ||||
|                                    leafletMap: UIEventSource<any>, | ||||
|                                    isShown?: UIEventSource<boolean>) => void; | ||||
|      | ||||
|     constructor(config: TilesourceConfig, | ||||
|                 leafletMap: UIEventSource<any>, | ||||
|                 isShown: UIEventSource<boolean> = undefined) { | ||||
|          | ||||
|         leafletMap.map(leaflet => { | ||||
|             if(leaflet === undefined){ | ||||
|                 return; | ||||
|         if(ShowOverlayLayer.implementation === undefined){ | ||||
|             throw "Call ShowOverlayLayerImplemenation.initialize() first before using this" | ||||
|         } | ||||
| 
 | ||||
|             const tileLayer =  L.tileLayer(config.source, | ||||
|                 { | ||||
|                     attribution: "", | ||||
|                     maxZoom: config.maxzoom, | ||||
|                     minZoom: config.minzoom, | ||||
|                     // @ts-ignore
 | ||||
|                     wmts: false, | ||||
|                 }); | ||||
|              | ||||
|             if(isShown === undefined){ | ||||
|                 tileLayer.addTo(leaflet) | ||||
|             ShowOverlayLayer.implementation(config, leafletMap, isShown) | ||||
|     } | ||||
|              | ||||
|             isShown?.addCallbackAndRunD(isShown => { | ||||
|                 if(isShown){ | ||||
|                     tileLayer.addTo(leaflet) | ||||
|                 }else{ | ||||
|                     leaflet.removeLayer(tileLayer) | ||||
|                 } | ||||
|                  | ||||
|             }) | ||||
|              | ||||
|         } ) | ||||
|          | ||||
|          | ||||
|          | ||||
|     } | ||||
|      | ||||
|      | ||||
| } | ||||
							
								
								
									
										45
									
								
								UI/ShowDataLayer/ShowOverlayLayerImplementation.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								UI/ShowDataLayer/ShowOverlayLayerImplementation.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | |||
| import * as L from "leaflet"; | ||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import ShowOverlayLayer from "./ShowOverlayLayer"; | ||||
| 
 | ||||
| export default class ShowOverlayLayerImplementation { | ||||
|      | ||||
|     public static Implement(){ | ||||
|         ShowOverlayLayer.implementation = ShowOverlayLayerImplementation.AddToMap | ||||
|     } | ||||
|      | ||||
|     public static AddToMap(config: TilesourceConfig, | ||||
|                            leafletMap: UIEventSource<any>, | ||||
|                            isShown: UIEventSource<boolean> = undefined){ | ||||
|         leafletMap.map(leaflet => { | ||||
|             if (leaflet === undefined) { | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             const tileLayer = L.tileLayer(config.source, | ||||
|                 { | ||||
|                     attribution: "", | ||||
|                     maxZoom: config.maxzoom, | ||||
|                     minZoom: config.minzoom, | ||||
|                     // @ts-ignore
 | ||||
|                     wmts: false, | ||||
|                 }); | ||||
| 
 | ||||
|             if (isShown === undefined) { | ||||
|                 tileLayer.addTo(leaflet) | ||||
|             } | ||||
| 
 | ||||
|             isShown?.addCallbackAndRunD(isShown => { | ||||
|                 if (isShown) { | ||||
|                     tileLayer.addTo(leaflet) | ||||
|                 } else { | ||||
|                     leaflet.removeLayer(tileLayer) | ||||
|                 } | ||||
| 
 | ||||
|             }) | ||||
| 
 | ||||
|         }) | ||||
|     } | ||||
|      | ||||
| } | ||||
							
								
								
									
										17
									
								
								assets/layers/home_location/home_location.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								assets/layers/home_location/home_location.json
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| { | ||||
|   "id": "home_location", | ||||
|   "description": "Meta layer showing the home location of the user", | ||||
|   "minzoom": 0, | ||||
|   "source": { | ||||
|     "osmTags": "user:home=yes" | ||||
|   }, | ||||
|   "icon": { | ||||
|     "render": "circle:white;./assets/svg/home.svg" | ||||
|   }, | ||||
|   "iconSize": { | ||||
|     "render": "20,20,center" | ||||
|   }, | ||||
|   "color": { | ||||
|     "render": "#00f" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										9
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										9
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -12,11 +12,16 @@ import DetermineLayout from "./Logic/DetermineLayout"; | |||
| import LayoutConfig from "./Models/ThemeConfig/LayoutConfig"; | ||||
| import DefaultGUI, {DefaultGuiState} from "./UI/DefaultGUI"; | ||||
| import State from "./State"; | ||||
| import AvailableBaseLayersImplementation from "./Logic/Actors/AvailableBaseLayersImplementation"; | ||||
| import ShowOverlayLayerImplementation from "./UI/ShowDataLayer/ShowOverlayLayerImplementation"; | ||||
| 
 | ||||
| // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts running from console
 | ||||
| MinimapImplementation.initialize() | ||||
| // Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts
 | ||||
| ValidatedTextField.bestLayerAt = (location, layerPref) => AvailableBaseLayers.SelectBestLayerAccordingTo(location, layerPref) | ||||
| AvailableBaseLayers.implement(new AvailableBaseLayersImplementation()) | ||||
| SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/"); | ||||
| ShowOverlayLayerImplementation.Implement(); | ||||
| // Miscelleanous
 | ||||
| 
 | ||||
| Utils.DisableLongPresses() | ||||
| 
 | ||||
| // --------------------- Special actions based on the parameters -----------------
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue