forked from MapComplete/MapComplete
		
	Fix overlay layers
This commit is contained in:
		
							parent
							
								
									3aeedf22c8
								
							
						
					
					
						commit
						24f7610d0a
					
				
					 15 changed files with 216 additions and 184 deletions
				
			
		|  | @ -29,7 +29,6 @@ export default class FeaturePropertiesStore { | ||||||
| 
 | 
 | ||||||
|                 const source = self._elements.get(id) |                 const source = self._elements.get(id) | ||||||
|                 if (source === undefined) { |                 if (source === undefined) { | ||||||
|                     console.log("Adding feature store for", id) |  | ||||||
|                     self._elements.set(id, new UIEventSource<any>(feature.properties)) |                     self._elements.set(id, new UIEventSource<any>(feature.properties)) | ||||||
|                     continue |                     continue | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ export interface MapProperties { | ||||||
|     readonly location: UIEventSource<{ lon: number; lat: number }> |     readonly location: UIEventSource<{ lon: number; lat: number }> | ||||||
|     readonly zoom: UIEventSource<number> |     readonly zoom: UIEventSource<number> | ||||||
|     readonly minzoom: UIEventSource<number> |     readonly minzoom: UIEventSource<number> | ||||||
|  |     readonly maxzoom: UIEventSource<number> | ||||||
|     readonly bounds: UIEventSource<BBox> |     readonly bounds: UIEventSource<BBox> | ||||||
|     readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined> |     readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined> | ||||||
|     readonly maxbounds: UIEventSource<undefined | BBox> |     readonly maxbounds: UIEventSource<undefined | BBox> | ||||||
|  |  | ||||||
|  | @ -123,7 +123,9 @@ export interface RasterLayerProperties { | ||||||
|     /** |     /** | ||||||
|      * The name of the imagery source |      * The name of the imagery source | ||||||
|      */ |      */ | ||||||
|     readonly name: string |     readonly name: string | Record<string, string> | ||||||
|  | 
 | ||||||
|  |     readonly isOverlay?: boolean | ||||||
| 
 | 
 | ||||||
|     readonly id: string |     readonly id: string | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { LayerConfigJson } from "./LayerConfigJson" | import { LayerConfigJson } from "./LayerConfigJson" | ||||||
| import TilesourceConfigJson from "./TilesourceConfigJson" |  | ||||||
| import ExtraLinkConfigJson from "./ExtraLinkConfigJson" | import ExtraLinkConfigJson from "./ExtraLinkConfigJson" | ||||||
|  | import { RasterLayerProperties } from "../../RasterLayers" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Defines the entire theme. |  * Defines the entire theme. | ||||||
|  | @ -148,7 +148,7 @@ export interface LayoutConfigJson { | ||||||
|     /** |     /** | ||||||
|      * Define some (overlay) slippy map tilesources |      * Define some (overlay) slippy map tilesources | ||||||
|      */ |      */ | ||||||
|     tileLayerSources?: TilesourceConfigJson[] |     tileLayerSources?: (RasterLayerProperties & { defaultState?: true | boolean })[] | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The layers to display. |      * The layers to display. | ||||||
|  |  | ||||||
|  | @ -3,11 +3,12 @@ import { LayoutConfigJson } from "./Json/LayoutConfigJson" | ||||||
| import LayerConfig from "./LayerConfig" | import LayerConfig from "./LayerConfig" | ||||||
| import { LayerConfigJson } from "./Json/LayerConfigJson" | import { LayerConfigJson } from "./Json/LayerConfigJson" | ||||||
| import Constants from "../Constants" | import Constants from "../Constants" | ||||||
| import TilesourceConfig from "./TilesourceConfig" |  | ||||||
| import { ExtractImages } from "./Conversion/FixImages" | import { ExtractImages } from "./Conversion/FixImages" | ||||||
| import ExtraLinkConfig from "./ExtraLinkConfig" | import ExtraLinkConfig from "./ExtraLinkConfig" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import LanguageUtils from "../../Utils/LanguageUtils" | import LanguageUtils from "../../Utils/LanguageUtils" | ||||||
|  | import { RasterLayerProperties } from "../RasterLayers" | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Minimal information about a theme |  * Minimal information about a theme | ||||||
|  **/ |  **/ | ||||||
|  | @ -39,7 +40,7 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|     public widenFactor: number |     public widenFactor: number | ||||||
|     public defaultBackgroundId?: string |     public defaultBackgroundId?: string | ||||||
|     public layers: LayerConfig[] |     public layers: LayerConfig[] | ||||||
|     public tileLayerSources: TilesourceConfig[] |     public tileLayerSources: (RasterLayerProperties & { defaultState?: true | boolean })[] | ||||||
|     public readonly hideFromOverview: boolean |     public readonly hideFromOverview: boolean | ||||||
|     public lockLocation: boolean | [[number, number], [number, number]] |     public lockLocation: boolean | [[number, number], [number, number]] | ||||||
|     public readonly enableUserBadge: boolean |     public readonly enableUserBadge: boolean | ||||||
|  | @ -161,9 +162,7 @@ export default class LayoutConfig implements LayoutInformation { | ||||||
|         this.widenFactor = json.widenFactor ?? 1.5 |         this.widenFactor = json.widenFactor ?? 1.5 | ||||||
| 
 | 
 | ||||||
|         this.defaultBackgroundId = json.defaultBackgroundId |         this.defaultBackgroundId = json.defaultBackgroundId | ||||||
|         this.tileLayerSources = (json.tileLayerSources ?? []).map( |         this.tileLayerSources = json.tileLayerSources ?? [] | ||||||
|             (config, i) => new TilesourceConfig(config, `${this.id}.tileLayerSources[${i}]`) |  | ||||||
|         ) |  | ||||||
|         // At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
 |         // At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
 | ||||||
|         this.layers = json.layers.map( |         this.layers = json.layers.map( | ||||||
|             (lyrJson) => |             (lyrJson) => | ||||||
|  |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| import TilesourceConfigJson from "./Json/TilesourceConfigJson" |  | ||||||
| import Translations from "../../UI/i18n/Translations" |  | ||||||
| import { Translation } from "../../UI/i18n/Translation" |  | ||||||
| 
 |  | ||||||
| export default class TilesourceConfig { |  | ||||||
|     public readonly source: string |  | ||||||
|     public readonly id: string |  | ||||||
|     public readonly isOverlay: boolean |  | ||||||
|     public readonly name: Translation |  | ||||||
|     public readonly minzoom: number |  | ||||||
|     public readonly maxzoom: number |  | ||||||
|     public readonly defaultState: boolean |  | ||||||
| 
 |  | ||||||
|     constructor(config: TilesourceConfigJson, ctx: string = "") { |  | ||||||
|         this.id = config.id |  | ||||||
|         this.source = config.source |  | ||||||
|         this.isOverlay = config.isOverlay ?? false |  | ||||||
|         this.name = Translations.T(config.name) |  | ||||||
|         this.minzoom = config.minZoom ?? 0 |  | ||||||
|         this.maxzoom = config.maxZoom ?? 999 |  | ||||||
|         this.defaultState = config.defaultState ?? true |  | ||||||
|         if (this.id === undefined) { |  | ||||||
|             throw "An id is obligated" |  | ||||||
|         } |  | ||||||
|         if (this.minzoom > this.maxzoom) { |  | ||||||
|             throw ( |  | ||||||
|                 "Invalid tilesourceConfig: minzoom should be smaller then maxzoom (at " + ctx + ")" |  | ||||||
|             ) |  | ||||||
|         } |  | ||||||
|         if (this.minzoom < 0) { |  | ||||||
|             throw "minzoom should be > 0 (at " + ctx + ")" |  | ||||||
|         } |  | ||||||
|         if (this.maxzoom < 0) { |  | ||||||
|             throw "maxzoom should be > 0 (at " + ctx + ")" |  | ||||||
|         } |  | ||||||
|         if (this.source.indexOf("{zoom}") >= 0) { |  | ||||||
|             throw "Invalid source url: use {z} instead of {zoom}  (at " + ctx + ".source)" |  | ||||||
|         } |  | ||||||
|         if (!this.defaultState && config.name === undefined) { |  | ||||||
|             throw "Disabling an overlay without a name is not possible" |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -43,6 +43,7 @@ import MetaTagging from "../Logic/MetaTagging" | ||||||
| import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" | import ChangeGeometryApplicator from "../Logic/FeatureSource/Sources/ChangeGeometryApplicator" | ||||||
| import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" | import { NewGeometryFromChangesFeatureSource } from "../Logic/FeatureSource/Sources/NewGeometryFromChangesFeatureSource" | ||||||
| import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | import OsmObjectDownloader from "../Logic/Osm/OsmObjectDownloader" | ||||||
|  | import ShowOverlayRasterLayer from "../UI/Map/ShowOverlayRasterLayer" | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * |  * | ||||||
|  | @ -82,6 +83,10 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|     readonly geolocation: GeoLocationHandler |     readonly geolocation: GeoLocationHandler | ||||||
| 
 | 
 | ||||||
|     readonly lastClickObject: WritableFeatureSource |     readonly lastClickObject: WritableFeatureSource | ||||||
|  |     readonly overlayLayerStates: ReadonlyMap< | ||||||
|  |         string, | ||||||
|  |         { readonly isDisplayed: UIEventSource<boolean> } | ||||||
|  |     > | ||||||
| 
 | 
 | ||||||
|     constructor(layout: LayoutConfig) { |     constructor(layout: LayoutConfig) { | ||||||
|         this.layout = layout |         this.layout = layout | ||||||
|  | @ -125,6 +130,21 @@ export default class ThemeViewState implements SpecialVisualizationState { | ||||||
|         const self = this |         const self = this | ||||||
|         this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) |         this.layerState = new LayerState(this.osmConnection, layout.layers, layout.id) | ||||||
| 
 | 
 | ||||||
|  |         { | ||||||
|  |             const overlayLayerStates = new Map<string, { isDisplayed: UIEventSource<boolean> }>() | ||||||
|  |             for (const rasterInfo of this.layout.tileLayerSources) { | ||||||
|  |                 const isDisplayed = QueryParameters.GetBooleanQueryParameter( | ||||||
|  |                     "overlay-" + rasterInfo.id, | ||||||
|  |                     rasterInfo.defaultState ?? true, | ||||||
|  |                     "Wether or not overlayer layer " + rasterInfo.id + " is shown" | ||||||
|  |                 ) | ||||||
|  |                 const state = { isDisplayed } | ||||||
|  |                 overlayLayerStates.set(rasterInfo.id, state) | ||||||
|  |                 new ShowOverlayRasterLayer(rasterInfo, this.map, this.mapProperties, state) | ||||||
|  |             } | ||||||
|  |             this.overlayLayerStates = overlayLayerStates | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         { |         { | ||||||
|             /* Setup the layout source |             /* Setup the layout source | ||||||
|              * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too |              * A bit tricky, as this is heavily intertwined with the 'changes'-element, which generate a stream of new and changed features too | ||||||
|  |  | ||||||
|  | @ -1,68 +0,0 @@ | ||||||
| import { VariableUiElement } from "../Base/VariableUIElement" |  | ||||||
| import Toggle from "../Input/Toggle" |  | ||||||
| import Combine from "../Base/Combine" |  | ||||||
| import Translations from "../i18n/Translations" |  | ||||||
| import { Translation } from "../i18n/Translation" |  | ||||||
| import Svg from "../../Svg" |  | ||||||
| import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
| import FilteredLayer from "../../Models/FilteredLayer" |  | ||||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" |  | ||||||
| import Loc from "../../Models/Loc" |  | ||||||
| 
 |  | ||||||
| export default class FilterView extends VariableUiElement { |  | ||||||
|     constructor( |  | ||||||
|         filteredLayer: Store<FilteredLayer[]>, |  | ||||||
|         tileLayers: { config: TilesourceConfig; isDisplayed: UIEventSource<boolean> }[], |  | ||||||
|         state: { |  | ||||||
|             readonly availableBackgroundLayers?: Store<BaseLayer[]> |  | ||||||
|             readonly featureSwitchBackgroundSelection?: UIEventSource<boolean> |  | ||||||
|             readonly featureSwitchIsDebugging?: UIEventSource<boolean> |  | ||||||
|             readonly locationControl?: UIEventSource<Loc> |  | ||||||
|             readonly featureSwitchMoreQuests: Store<boolean> |  | ||||||
|         } |  | ||||||
|     ) { |  | ||||||
|         super( |  | ||||||
|             filteredLayer.map((filteredLayers) => { |  | ||||||
|                 // Create the views which toggle layers (and filters them) ...
 |  | ||||||
|                 let elements = filteredLayers |  | ||||||
|                     ?.map((l) => |  | ||||||
|                         FilterView.createOneFilteredLayerElement(l, state)?.SetClass("filter-panel") |  | ||||||
|                     ) |  | ||||||
|                     ?.filter((l) => l !== undefined) |  | ||||||
|                 elements[0].SetClass("first-filter-panel") |  | ||||||
| 
 |  | ||||||
|                 // ... create views for non-interactive layers ...
 |  | ||||||
|                 elements = elements.concat( |  | ||||||
|                     tileLayers.map((tl) => FilterView.createOverlayToggle(state, tl)) |  | ||||||
|                 ) |  | ||||||
| 
 |  | ||||||
|                 return elements |  | ||||||
|             }) |  | ||||||
|         ) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private static createOverlayToggle( |  | ||||||
|         state: { locationControl?: UIEventSource<Loc> }, |  | ||||||
|         config: { config: TilesourceConfig; isDisplayed: UIEventSource<boolean> } |  | ||||||
|     ) { |  | ||||||
|         const iconStyle = "width:1.5rem;height:1.5rem;margin-left:1.25rem;flex-shrink: 0;" |  | ||||||
| 
 |  | ||||||
|         const icon = new Combine([Svg.checkbox_filled]).SetStyle(iconStyle) |  | ||||||
|         const iconUnselected = new Combine([Svg.checkbox_empty]).SetStyle(iconStyle) |  | ||||||
|         const name: Translation = config.config.name |  | ||||||
| 
 |  | ||||||
|         const styledNameChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-2") |  | ||||||
|         const styledNameUnChecked = name.Clone().SetStyle("font-size:large").SetClass("ml-2") |  | ||||||
| 
 |  | ||||||
|         const style = "display:flex;align-items:center;padding:0.5rem 0;" |  | ||||||
|         const layerChecked = new Combine([icon, styledNameChecked]) |  | ||||||
|             .SetStyle(style) |  | ||||||
|             .onClick(() => config.isDisplayed.setData(false)) |  | ||||||
| 
 |  | ||||||
|         const layerNotChecked = new Combine([iconUnselected, styledNameUnChecked]) |  | ||||||
|             .SetStyle(style) |  | ||||||
|             .onClick(() => config.isDisplayed.setData(true)) |  | ||||||
| 
 |  | ||||||
|         return new Toggle(layerChecked, layerNotChecked, config.isDisplayed) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										47
									
								
								UI/BigComponents/OverlayToggle.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								UI/BigComponents/OverlayToggle.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | ||||||
|  | <script lang="ts">/** | ||||||
|  |  * The OverlayToggle shows a single toggle to enable or disable an overlay | ||||||
|  |  */ | ||||||
|  | import Checkbox from "../Base/Checkbox.svelte"; | ||||||
|  | import { onDestroy } from "svelte"; | ||||||
|  | import { UIEventSource } from "../../Logic/UIEventSource"; | ||||||
|  | import Tr from "../Base/Tr.svelte"; | ||||||
|  | import Translations from "../i18n/Translations"; | ||||||
|  | import type { RasterLayerProperties } from "../../Models/RasterLayers"; | ||||||
|  | import { Translation } from "../i18n/Translation"; | ||||||
|  | 
 | ||||||
|  | export let layerproperties : RasterLayerProperties | ||||||
|  | export let state: {isDisplayed: UIEventSource<boolean>}; | ||||||
|  | export let zoomlevel: UIEventSource<number>; | ||||||
|  | export let highlightedLayer: UIEventSource<string> | undefined; | ||||||
|  | 
 | ||||||
|  | let isDisplayed: boolean = state.isDisplayed.data; | ||||||
|  | onDestroy(state.isDisplayed.addCallbackAndRunD(d => { | ||||||
|  |   isDisplayed = d; | ||||||
|  |   return false; | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | let mainElem: HTMLElement; | ||||||
|  | $:  onDestroy( | ||||||
|  |   highlightedLayer.addCallbackAndRun(highlightedLayer => { | ||||||
|  |     if (highlightedLayer === layerproperties.id) { | ||||||
|  |       mainElem?.classList?.add("glowing-shadow"); | ||||||
|  |     } else { | ||||||
|  |       mainElem?.classList?.remove("glowing-shadow"); | ||||||
|  |     } | ||||||
|  |   }) | ||||||
|  | ); | ||||||
|  | </script> | ||||||
|  | {#if layerproperties.name} | ||||||
|  |   <div bind:this={mainElem}> | ||||||
|  |     <label class="flex gap-1"> | ||||||
|  |       <Checkbox selected={state.isDisplayed} /> | ||||||
|  |       <Tr t={new Translation(layerproperties.name)}/> | ||||||
|  |       {#if $zoomlevel < layerproperties.min_zoom} | ||||||
|  |         <span class="alert"> | ||||||
|  |           <Tr t={Translations.t.general.layerSelection.zoomInToSeeThisLayer} /> | ||||||
|  |         </span> | ||||||
|  |       {/if} | ||||||
|  |     </label> | ||||||
|  |   </div> | ||||||
|  | {/if} | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import { Store, UIEventSource } from "../../Logic/UIEventSource" | import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
| import type { Map as MLMap } from "maplibre-gl" | import type { Map as MLMap } from "maplibre-gl" | ||||||
| import { Map as MlMap } from "maplibre-gl" | import { Map as MlMap, SourceSpecification } from "maplibre-gl" | ||||||
| import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" | import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers" | ||||||
| import { Utils } from "../../Utils" | import { Utils } from "../../Utils" | ||||||
| import { BBox } from "../../Logic/BBox" | import { BBox } from "../../Logic/BBox" | ||||||
|  | @ -37,6 +37,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|     readonly allowZooming: UIEventSource<true | boolean | undefined> |     readonly allowZooming: UIEventSource<true | boolean | undefined> | ||||||
|     readonly lastClickLocation: Store<undefined | { lon: number; lat: number }> |     readonly lastClickLocation: Store<undefined | { lon: number; lat: number }> | ||||||
|     readonly minzoom: UIEventSource<number> |     readonly minzoom: UIEventSource<number> | ||||||
|  |     readonly maxzoom: UIEventSource<number> | ||||||
|     private readonly _maplibreMap: Store<MLMap> |     private readonly _maplibreMap: Store<MLMap> | ||||||
|     /** |     /** | ||||||
|      * Used for internal bookkeeping (to remove a rasterLayer when done loading) |      * Used for internal bookkeeping (to remove a rasterLayer when done loading) | ||||||
|  | @ -50,12 +51,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|         this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) |         this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 }) | ||||||
|         this.zoom = state?.zoom ?? new UIEventSource(1) |         this.zoom = state?.zoom ?? new UIEventSource(1) | ||||||
|         this.minzoom = state?.minzoom ?? new UIEventSource(0) |         this.minzoom = state?.minzoom ?? new UIEventSource(0) | ||||||
|  |         this.maxzoom = state?.maxzoom ?? new UIEventSource(24) | ||||||
|         this.zoom.addCallbackAndRunD((z) => { |         this.zoom.addCallbackAndRunD((z) => { | ||||||
|             if (z < this.minzoom.data) { |             if (z < this.minzoom.data) { | ||||||
|                 this.zoom.setData(this.minzoom.data) |                 this.zoom.setData(this.minzoom.data) | ||||||
|             } |             } | ||||||
|             if (z > 24) { |             const max = Math.min(24, this.maxzoom.data ?? 24) | ||||||
|                 this.zoom.setData(24) |             if (z > max) { | ||||||
|  |                 this.zoom.setData(max) | ||||||
|             } |             } | ||||||
|         }) |         }) | ||||||
|         this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) |         this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined) | ||||||
|  | @ -90,6 +93,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|                 self.setAllowMoving(self.allowMoving.data) |                 self.setAllowMoving(self.allowMoving.data) | ||||||
|                 self.setAllowZooming(self.allowZooming.data) |                 self.setAllowZooming(self.allowZooming.data) | ||||||
|                 self.setMinzoom(self.minzoom.data) |                 self.setMinzoom(self.minzoom.data) | ||||||
|  |                 self.setMaxzoom(self.maxzoom.data) | ||||||
|                 self.setBounds(self.bounds.data) |                 self.setBounds(self.bounds.data) | ||||||
|             }) |             }) | ||||||
|             self.MoveMapToCurrentLoc(self.location.data) |             self.MoveMapToCurrentLoc(self.location.data) | ||||||
|  | @ -98,6 +102,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|             self.setAllowMoving(self.allowMoving.data) |             self.setAllowMoving(self.allowMoving.data) | ||||||
|             self.setAllowZooming(self.allowZooming.data) |             self.setAllowZooming(self.allowZooming.data) | ||||||
|             self.setMinzoom(self.minzoom.data) |             self.setMinzoom(self.minzoom.data) | ||||||
|  |             self.setMaxzoom(self.maxzoom.data) | ||||||
|             self.setBounds(self.bounds.data) |             self.setBounds(self.bounds.data) | ||||||
|             this.updateStores() |             this.updateStores() | ||||||
|             map.on("moveend", () => this.updateStores()) |             map.on("moveend", () => this.updateStores()) | ||||||
|  | @ -146,10 +151,23 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification { | ||||||
|  |         return { | ||||||
|  |             type: "raster", | ||||||
|  |             // use the tiles option to specify a 256WMS tile source URL
 | ||||||
|  |             // https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/
 | ||||||
|  |             tiles: [MapLibreAdaptor.prepareWmsURL(layer.url, layer["tile-size"] ?? 256)], | ||||||
|  |             tileSize: layer["tile-size"] ?? 256, | ||||||
|  |             minzoom: layer["min_zoom"] ?? 1, | ||||||
|  |             maxzoom: layer["max_zoom"] ?? 25, | ||||||
|  |             //  scheme: background["type"] === "tms" ? "tms" : "xyz",
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Prepares an ELI-URL to be compatible with mapbox |      * Prepares an ELI-URL to be compatible with mapbox | ||||||
|      */ |      */ | ||||||
|     private static prepareWmsURL(url: string, size: number = 256) { |     private static prepareWmsURL(url: string, size: number = 256): string { | ||||||
|         // ELI:  LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap
 |         // ELI:  LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&CRS={proj}&WIDTH={width}&HEIGHT={height}&BBOX={bbox}&VERSION=1.3.0&SERVICE=WMS&REQUEST=GetMap
 | ||||||
|         // PROD: SERVICE=WMS&REQUEST=GetMap&LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&TRANSPARENT=false&VERSION=1.3.0&WIDTH=256&HEIGHT=256&CRS=EPSG:3857&BBOX=488585.4847988467,6590094.830634755,489196.9810251281,6590706.32686104
 |         // PROD: SERVICE=WMS&REQUEST=GetMap&LAYERS=OGWRGB13_15VL&STYLES=&FORMAT=image/jpeg&TRANSPARENT=false&VERSION=1.3.0&WIDTH=256&HEIGHT=256&CRS=EPSG:3857&BBOX=488585.4847988467,6590094.830634755,489196.9810251281,6590706.32686104
 | ||||||
| 
 | 
 | ||||||
|  | @ -342,16 +360,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         map.addSource(background.id, { |         map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) | ||||||
|             type: "raster", |  | ||||||
|             // use the tiles option to specify a 256WMS tile source URL
 |  | ||||||
|             // https://maplibre.org/maplibre-gl-js-docs/style-spec/sources/
 |  | ||||||
|             tiles: [MapLibreAdaptor.prepareWmsURL(background.url, background["tile-size"] ?? 256)], |  | ||||||
|             tileSize: background["tile-size"] ?? 256, |  | ||||||
|             minzoom: background["min_zoom"] ?? 1, |  | ||||||
|             maxzoom: background["max_zoom"] ?? 25, |  | ||||||
|             //  scheme: background["type"] === "tms" ? "tms" : "xyz",
 |  | ||||||
|         }) |  | ||||||
| 
 | 
 | ||||||
|         map.addLayer( |         map.addLayer( | ||||||
|             { |             { | ||||||
|  | @ -405,6 +414,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { | ||||||
|         map.setMinZoom(minzoom) |         map.setMinZoom(minzoom) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private setMaxzoom(maxzoom: number) { | ||||||
|  |         const map = this._maplibreMap.data | ||||||
|  |         if (map === undefined) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         map.setMaxZoom(maxzoom) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private setAllowZooming(allow: true | boolean | undefined) { |     private setAllowZooming(allow: true | boolean | undefined) { | ||||||
|         const map = this._maplibreMap.data |         const map = this._maplibreMap.data | ||||||
|         if (map === undefined) { |         if (map === undefined) { | ||||||
|  |  | ||||||
							
								
								
									
										92
									
								
								UI/Map/ShowOverlayRasterLayer.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								UI/Map/ShowOverlayRasterLayer.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | import { RasterLayerProperties } from "../../Models/RasterLayers" | ||||||
|  | import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||||
|  | import { Map as MlMap } from "maplibre-gl" | ||||||
|  | import { Utils } from "../../Utils" | ||||||
|  | import { MapLibreAdaptor } from "./MapLibreAdaptor" | ||||||
|  | 
 | ||||||
|  | export default class ShowOverlayRasterLayer { | ||||||
|  |     private readonly _map: UIEventSource<MlMap> | ||||||
|  |     private readonly _layer: RasterLayerProperties | ||||||
|  |     private readonly _mapProperties?: { zoom: Store<number> } | ||||||
|  |     private _mllayer | ||||||
|  |     private readonly _isDisplayed?: Store<boolean> | ||||||
|  | 
 | ||||||
|  |     constructor( | ||||||
|  |         layer: RasterLayerProperties, | ||||||
|  |         map: UIEventSource<MlMap>, | ||||||
|  |         mapProperties?: { zoom: Store<number> }, | ||||||
|  |         options?: { | ||||||
|  |             isDisplayed?: Store<boolean> | ||||||
|  |         } | ||||||
|  |     ) { | ||||||
|  |         this._mapProperties = mapProperties | ||||||
|  |         this._layer = layer | ||||||
|  |         this._map = map | ||||||
|  |         this._isDisplayed = options?.isDisplayed | ||||||
|  |         const self = this | ||||||
|  |         map.addCallbackAndRunD((map) => { | ||||||
|  |             self.addLayer() | ||||||
|  |             map.on("load", () => { | ||||||
|  |                 self.addLayer() | ||||||
|  |             }) | ||||||
|  |         }) | ||||||
|  |         this.addLayer() | ||||||
|  | 
 | ||||||
|  |         options?.isDisplayed?.addCallbackAndRun(() => { | ||||||
|  |             self.setVisibility() | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |         mapProperties?.zoom?.addCallbackAndRun(() => { | ||||||
|  |             self.setVisibility() | ||||||
|  |         }) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private setVisibility() { | ||||||
|  |         let zoom = this._mapProperties?.zoom?.data | ||||||
|  |         let withinRange = zoom === undefined || zoom > this._layer.min_zoom | ||||||
|  |         let isDisplayed = (this._isDisplayed?.data ?? true) && withinRange | ||||||
|  |         this._map.data?.setLayoutProperty( | ||||||
|  |             this._layer.id, | ||||||
|  |             "visibility", | ||||||
|  |             isDisplayed ? "visible" : "none" | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private async awaitStyleIsLoaded(): Promise<void> { | ||||||
|  |         const map = this._map.data | ||||||
|  |         if (map === undefined) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         while (!map?.isStyleLoaded()) { | ||||||
|  |             await Utils.waitFor(250) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private async addLayer() { | ||||||
|  |         const map = this._map.data | ||||||
|  |         console.log("Attempting to add ", this._layer.id) | ||||||
|  |         if (map === undefined) { | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         await this.awaitStyleIsLoaded() | ||||||
|  |         if (this._mllayer) { | ||||||
|  |             // Already initialized
 | ||||||
|  |             return | ||||||
|  |         } | ||||||
|  |         const background: RasterLayerProperties = this._layer | ||||||
|  | 
 | ||||||
|  |         map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background)) | ||||||
|  |         this._mllayer = map.addLayer({ | ||||||
|  |             id: background.id, | ||||||
|  |             type: "raster", | ||||||
|  |             source: background.id, | ||||||
|  |             paint: {}, | ||||||
|  |         }) | ||||||
|  |         map.setLayoutProperty( | ||||||
|  |             this._layer.id, | ||||||
|  |             "visibility", | ||||||
|  |             this._isDisplayed?.data ?? true ? "visible" : "none" | ||||||
|  |         ) | ||||||
|  |         this.setVisibility() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -75,6 +75,7 @@ export class MinimapViz implements SpecialVisualization { | ||||||
|         const mlmap = new UIEventSource(undefined) |         const mlmap = new UIEventSource(undefined) | ||||||
|         const mla = new MapLibreAdaptor(mlmap) |         const mla = new MapLibreAdaptor(mlmap) | ||||||
| 
 | 
 | ||||||
|  |         mla.maxzoom.setData(17) | ||||||
|         let zoom = 18 |         let zoom = 18 | ||||||
|         if (args[0]) { |         if (args[0]) { | ||||||
|             const parsed = Number(args[0]) |             const parsed = Number(args[0]) | ||||||
|  |  | ||||||
|  | @ -1,43 +0,0 @@ | ||||||
| import * as L from "leaflet" |  | ||||||
| import TilesourceConfig from "../../Models/ThemeConfig/TilesourceConfig" |  | ||||||
| import { UIEventSource } from "../../Logic/UIEventSource" |  | ||||||
| import ShowOverlayLayer from "./ShowOverlayLayer" |  | ||||||
| 
 |  | ||||||
| // TODO port this to maplibre!
 |  | ||||||
| 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) |  | ||||||
|                 } |  | ||||||
|             }) |  | ||||||
|         }) |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  | @ -34,7 +34,7 @@ | ||||||
|   import Hotkeys from "./Base/Hotkeys"; |   import Hotkeys from "./Base/Hotkeys"; | ||||||
|   import { VariableUiElement } from "./Base/VariableUIElement"; |   import { VariableUiElement } from "./Base/VariableUIElement"; | ||||||
|   import SvelteUIElement from "./Base/SvelteUIElement"; |   import SvelteUIElement from "./Base/SvelteUIElement"; | ||||||
|   import { onDestroy } from "svelte"; |   import OverlayToggle from "./BigComponents/OverlayToggle.svelte"; | ||||||
| 
 | 
 | ||||||
|   export let state: ThemeViewState; |   export let state: ThemeViewState; | ||||||
|   let layout = state.layout; |   let layout = state.layout; | ||||||
|  | @ -53,7 +53,7 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const tags = state.featureProperties.getStore(selectedElement.properties.id); |     const tags = state.featureProperties.getStore(selectedElement.properties.id); | ||||||
|     return new SvelteUIElement(SelectedElementView, {state, layer, selectedElement, tags}) |     return new SvelteUIElement(SelectedElementView, { state, layer, selectedElement, tags }); | ||||||
|   }, [selectedLayer]); |   }, [selectedLayer]); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -160,6 +160,14 @@ | ||||||
|           <Filterview zoomlevel={state.mapProperties.zoom} filteredLayer={state.layerState.filteredLayers.get(layer.id)} |           <Filterview zoomlevel={state.mapProperties.zoom} filteredLayer={state.layerState.filteredLayers.get(layer.id)} | ||||||
|                       highlightedLayer={state.guistate.highlightedLayerInFilters}></Filterview> |                       highlightedLayer={state.guistate.highlightedLayerInFilters}></Filterview> | ||||||
|         {/each} |         {/each} | ||||||
|  |         {#each layout.tileLayerSources as tilesource} | ||||||
|  |           <OverlayToggle | ||||||
|  |             layerproperties={tilesource} | ||||||
|  |             state={state.overlayLayerStates.get(tilesource.id)} | ||||||
|  |             highlightedLayer={state.guistate.highlightedLayerInFilters} | ||||||
|  |             zoomlevel={state.mapProperties.zoom} | ||||||
|  |              /> | ||||||
|  |         {/each} | ||||||
|         <If condition={state.featureSwitches.featureSwitchBackgroundSelection}> |         <If condition={state.featureSwitches.featureSwitchBackgroundSelection}> | ||||||
|           <RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker> |           <RasterLayerPicker {availableLayers} value={mapproperties.rasterLayer}></RasterLayerPicker> | ||||||
|         </If> |         </If> | ||||||
|  |  | ||||||
|  | @ -30,10 +30,10 @@ | ||||||
|   "tileLayerSources": [ |   "tileLayerSources": [ | ||||||
|     { |     { | ||||||
|       "id": "property-boundaries", |       "id": "property-boundaries", | ||||||
|       "source": "https://tiles.osmuk.org/PropertyBoundaries/{z}/{x}/{y}.png", |       "url": "https://tiles.osmuk.org/PropertyBoundaries/{z}/{x}/{y}.png", | ||||||
|       "isOverlay": true, |       "isOverlay": true, | ||||||
|       "minZoom": 18, |       "min_zoom": 18, | ||||||
|       "maxZoom": 20, |       "max_zoom": 20, | ||||||
|       "defaultState": false, |       "defaultState": false, | ||||||
|       "name": { |       "name": { | ||||||
|         "en": "Property boundaries by osmuk.org", |         "en": "Property boundaries by osmuk.org", | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue