forked from MapComplete/MapComplete
refactoring(maplibre): add RasterLayer info, add background switch, add default global layers
This commit is contained in:
parent
703d561324
commit
4f2bbf4b54
14 changed files with 1103 additions and 472 deletions
|
@ -11,7 +11,6 @@ import { BBox } from "../../Logic/BBox"
|
|||
import "leaflet-polylineoffset"
|
||||
import { SimpleMapScreenshoter } from "leaflet-simple-map-screenshoter"
|
||||
import BackgroundMapSwitch from "../BigComponents/BackgroundMapSwitch"
|
||||
import AvailableBaseLayersImplementation from "../../Logic/Actors/AvailableBaseLayersImplementation"
|
||||
import ShowDataLayer from "../ShowDataLayer/ShowDataLayer"
|
||||
import ShowDataLayerImplementation from "../ShowDataLayer/ShowDataLayerImplementation"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
|
@ -127,7 +126,6 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
|
|||
}
|
||||
|
||||
public static initialize() {
|
||||
AvailableBaseLayers.implement(new AvailableBaseLayersImplementation())
|
||||
Minimap.createMiniMap = (options) => new MinimapImplementation(options)
|
||||
ShowDataLayer.actualContstructor = (options) => new ShowDataLayerImplementation(options)
|
||||
StrayClickHandler.construct = (
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import { DropDown } from "../Input/DropDown"
|
||||
import Translations from "../i18n/Translations"
|
||||
import State from "../../State"
|
||||
import BaseLayer from "../../Models/BaseLayer"
|
||||
import { VariableUiElement } from "../Base/VariableUIElement"
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
|
||||
export default class BackgroundSelector extends VariableUiElement {
|
||||
constructor(state: { availableBackgroundLayers?: Store<BaseLayer[]> }) {
|
||||
const available = state.availableBackgroundLayers?.map((available) => {
|
||||
if (available === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const baseLayers: { value: BaseLayer; shown: string }[] = []
|
||||
for (const i in available) {
|
||||
if (!available.hasOwnProperty(i)) {
|
||||
continue
|
||||
}
|
||||
const layer: BaseLayer = available[i]
|
||||
baseLayers.push({ value: layer, shown: layer.name ?? "id:" + layer.id })
|
||||
}
|
||||
return baseLayers
|
||||
})
|
||||
|
||||
super(
|
||||
available?.map((baseLayers) => {
|
||||
if (baseLayers === undefined || baseLayers.length <= 1) {
|
||||
return undefined
|
||||
}
|
||||
return new DropDown(
|
||||
Translations.t.general.backgroundMap.Clone(),
|
||||
baseLayers,
|
||||
State.state.backgroundLayer,
|
||||
{
|
||||
select_class: "bg-indigo-100 p-1 rounded hover:bg-indigo-200 w-full",
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
172
UI/Map/MapLibreAdaptor.ts
Normal file
172
UI/Map/MapLibreAdaptor.ts
Normal file
|
@ -0,0 +1,172 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Map as MLMap } from "maplibre-gl"
|
||||
import {
|
||||
EditorLayerIndexProperties,
|
||||
RasterLayerPolygon,
|
||||
RasterLayerProperties,
|
||||
} from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import Loc from "../../Models/Loc"
|
||||
|
||||
export class MapLibreAdaptor {
|
||||
private readonly _maplibreMap: Store<MLMap>
|
||||
private readonly _backgroundLayer?: Store<RasterLayerPolygon>
|
||||
|
||||
private _currentRasterLayer: string = undefined
|
||||
|
||||
constructor(
|
||||
maplibreMap: Store<MLMap>,
|
||||
state?: {
|
||||
// availableBackgroundLayers: Store<BaseLayer[]>
|
||||
/**
|
||||
* The current background layer
|
||||
*/
|
||||
readonly backgroundLayer?: Store<RasterLayerPolygon>
|
||||
readonly locationControl?: UIEventSource<Loc>
|
||||
}
|
||||
) {
|
||||
this._maplibreMap = maplibreMap
|
||||
this._backgroundLayer = state.backgroundLayer
|
||||
|
||||
const self = this
|
||||
this._backgroundLayer?.addCallback((_) => self.setBackground())
|
||||
|
||||
maplibreMap.addCallbackAndRunD((map) => {
|
||||
map.on("load", () => {
|
||||
self.setBackground()
|
||||
})
|
||||
if (state.locationControl) {
|
||||
self.MoveMapToCurrentLoc(state.locationControl.data)
|
||||
map.on("moveend", () => {
|
||||
const dt = state.locationControl.data
|
||||
dt.lon = map.getCenter().lng
|
||||
dt.lat = map.getCenter().lat
|
||||
dt.zoom = map.getZoom()
|
||||
state.locationControl.ping()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
state.locationControl.addCallbackAndRunD((loc) => {
|
||||
self.MoveMapToCurrentLoc(loc)
|
||||
})
|
||||
}
|
||||
|
||||
private MoveMapToCurrentLoc(loc: Loc) {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined || loc === undefined) {
|
||||
return
|
||||
}
|
||||
if (map.getZoom() !== loc.zoom) {
|
||||
map.setZoom(loc.zoom)
|
||||
}
|
||||
const center = map.getCenter()
|
||||
if (center.lng !== loc.lon || center.lat !== loc.lat) {
|
||||
map.setCenter({ lng: loc.lon, lat: loc.lat })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an ELI-URL to be compatible with mapbox
|
||||
*/
|
||||
private static prepareWmsURL(url: string, size: number = 256) {
|
||||
// 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
|
||||
|
||||
const toReplace = {
|
||||
"{bbox}": "{bbox-epsg-3857}",
|
||||
"{proj}": "EPSG:3857",
|
||||
"{width}": "" + size,
|
||||
"{height}": "" + size,
|
||||
"{zoom}": "{z}",
|
||||
}
|
||||
|
||||
for (const key in toReplace) {
|
||||
url = url.replace(new RegExp(key), toReplace[key])
|
||||
}
|
||||
|
||||
const subdomains = url.match(/\{switch:([a-zA-Z0-9,]*)}/)
|
||||
if (subdomains !== null) {
|
||||
console.log("Found a switch:", subdomains)
|
||||
const options = subdomains[1].split(",")
|
||||
const option = options[Math.floor(Math.random() * options.length)]
|
||||
url = url.replace(subdomains[0], option)
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
private async awaitStyleIsLoaded(): Promise<void> {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined) {
|
||||
return
|
||||
}
|
||||
while (!map.isStyleLoaded()) {
|
||||
await Utils.waitFor(250)
|
||||
}
|
||||
}
|
||||
|
||||
private removeCurrentLayer(map: MLMap) {
|
||||
if (this._currentRasterLayer) {
|
||||
// hide the previous layer
|
||||
console.log("Removing previous layer", this._currentRasterLayer)
|
||||
map.removeLayer(this._currentRasterLayer)
|
||||
map.removeSource(this._currentRasterLayer)
|
||||
}
|
||||
}
|
||||
|
||||
private async setBackground() {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined) {
|
||||
return
|
||||
}
|
||||
const background: RasterLayerProperties = this._backgroundLayer?.data?.properties
|
||||
if (background !== undefined && this._currentRasterLayer === background.id) {
|
||||
// already the correct background layer, nothing to do
|
||||
return
|
||||
}
|
||||
await this.awaitStyleIsLoaded()
|
||||
|
||||
if (background !== this._backgroundLayer?.data?.properties) {
|
||||
// User selected another background in the meantime... abort
|
||||
return
|
||||
}
|
||||
|
||||
if (background !== undefined && this._currentRasterLayer === background.id) {
|
||||
// already the correct background layer, nothing to do
|
||||
return
|
||||
}
|
||||
if (background === undefined) {
|
||||
// no background to set
|
||||
this.removeCurrentLayer(map)
|
||||
this._currentRasterLayer = undefined
|
||||
return
|
||||
}
|
||||
|
||||
map.addSource(background.id, {
|
||||
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(
|
||||
{
|
||||
id: background.id,
|
||||
type: "raster",
|
||||
source: background.id,
|
||||
paint: {},
|
||||
},
|
||||
background.category === "osmbasedmap" || background.category === "map"
|
||||
? undefined
|
||||
: "aeroway_fill"
|
||||
)
|
||||
await this.awaitStyleIsLoaded()
|
||||
this.removeCurrentLayer(map)
|
||||
this._currentRasterLayer = background?.id
|
||||
}
|
||||
}
|
43
UI/Map/MaplibreMap.svelte
Normal file
43
UI/Map/MaplibreMap.svelte
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* The 'MaplibreMap' maps various event sources onto MapLibre.
|
||||
*
|
||||
* As it replaces the old 'MinimapObj' onto MapLibre and the existing codebase, this is sometimes a bit awkward
|
||||
*/
|
||||
import { onMount } from "svelte";
|
||||
import { Map } from "@onsvisual/svelte-maps";
|
||||
import type { Map as MaplibreMap } from "maplibre-gl";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type Loc from "../../Models/Loc";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
|
||||
|
||||
/**
|
||||
* Beware: this map will _only_ be set by this component
|
||||
* It should thus be treated as a 'store' by external parties
|
||||
*/
|
||||
export let map: Writable<MaplibreMap>
|
||||
|
||||
let center = {};
|
||||
|
||||
onMount(() => {
|
||||
$map.on("load", function() {
|
||||
$map.resize();
|
||||
});
|
||||
});
|
||||
const styleUrl = "https://api.maptiler.com/maps/streets/style.json?key=GvoVAJgu46I5rZapJuAy";
|
||||
</script>
|
||||
<main>
|
||||
<Map bind:center={center}
|
||||
bind:map={$map}
|
||||
controls="true"
|
||||
id="map" location={{lng: 0, lat: 0, zoom: 0}} maxzoom=24 style={styleUrl} />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
18
UI/Map/RasterLayerPicker.svelte
Normal file
18
UI/Map/RasterLayerPicker.svelte
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import type { Readable, Writable } from "svelte/store";
|
||||
import type { RasterLayerPolygon } from "../../Models/RasterLayers";
|
||||
|
||||
/***
|
||||
* Chooses a background-layer out of available options
|
||||
*/
|
||||
export let availableLayers: Readable<RasterLayerPolygon[]>
|
||||
export let value: Writable<RasterLayerPolygon>
|
||||
</script>
|
||||
|
||||
<select bind:value={$value}>
|
||||
{#each $availableLayers as availableLayer }
|
||||
<option value={availableLayer}>
|
||||
{availableLayer.properties.name}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
Loading…
Add table
Add a link
Reference in a new issue