forked from MapComplete/MapComplete
Refactoring: new background selector
This commit is contained in:
parent
5427a4cb05
commit
82093ffdf4
25 changed files with 658 additions and 269 deletions
|
@ -275,7 +275,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
return new Promise<Blob>((resolve) => drawOn.toBlob((data) => resolve(data)))
|
||||
}
|
||||
|
||||
private updateStores() {
|
||||
private updateStores(): void {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map) {
|
||||
return
|
||||
|
@ -293,7 +293,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
this.bounds.setData(bbox)
|
||||
}
|
||||
|
||||
private SetZoom(z: number) {
|
||||
private SetZoom(z: number): void {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map || z === undefined) {
|
||||
return
|
||||
|
@ -303,7 +303,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
}
|
||||
|
||||
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) {
|
||||
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }): void {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map || loc === undefined) {
|
||||
return
|
||||
|
@ -325,7 +325,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
}
|
||||
|
||||
private removeCurrentLayer(map: MLMap) {
|
||||
private removeCurrentLayer(map: MLMap): void {
|
||||
if (this._currentRasterLayer) {
|
||||
// hide the previous layer
|
||||
map.removeLayer(this._currentRasterLayer)
|
||||
|
@ -333,7 +333,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
}
|
||||
|
||||
private async setBackground() {
|
||||
private async setBackground(): Promise<void> {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map) {
|
||||
return
|
||||
|
@ -363,6 +363,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
|
||||
map.addSource(background.id, MapLibreAdaptor.prepareWmsSource(background))
|
||||
|
||||
map.resize()
|
||||
map.addLayer(
|
||||
{
|
||||
id: background.id,
|
||||
|
|
69
UI/Map/OverlayMap.svelte
Normal file
69
UI/Map/OverlayMap.svelte
Normal file
|
@ -0,0 +1,69 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* The overlay map is a bit a weird map:
|
||||
* it is a HTML-component which is intended to be placed _over_ another map.
|
||||
* It will align itself in order to seamlessly show the same location; but possibly in a different style
|
||||
*/
|
||||
import MaplibreMap from "./MaplibreMap.svelte";
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Map as MlMap} from "maplibre-gl";
|
||||
import {MapLibreAdaptor} from "./MapLibreAdaptor";
|
||||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import {onDestroy} from "svelte";
|
||||
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||
|
||||
|
||||
export let placedOverMapProperties: MapProperties
|
||||
export let placedOverMap: UIEventSource<MlMap>
|
||||
|
||||
export let rasterLayer: UIEventSource<RasterLayerPolygon>
|
||||
|
||||
export let visible: Store<boolean> = undefined
|
||||
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
|
||||
let altproperties = new MapLibreAdaptor(altmap, {
|
||||
rasterLayer,
|
||||
zoom: UIEventSource.feedFrom(placedOverMapProperties.zoom)
|
||||
})
|
||||
altproperties.allowMoving.setData(false)
|
||||
altproperties.allowZooming.setData(false)
|
||||
|
||||
function pixelCenterOf(map: UIEventSource<MlMap>): [number, number] {
|
||||
const rect = map?.data?.getCanvas()?.getBoundingClientRect()
|
||||
if (!rect) {
|
||||
return undefined
|
||||
}
|
||||
const x = (rect.left + rect.right) / 2
|
||||
const y = (rect.top + rect.bottom) / 2
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
function updateLocation() {
|
||||
if (!placedOverMap.data || !altmap.data) {
|
||||
return
|
||||
}
|
||||
altmap.data.resize()
|
||||
const {lon, lat} = placedOverMapProperties.location.data
|
||||
const altMapCenter = pixelCenterOf(altmap)
|
||||
const c = placedOverMap.data.unproject(altMapCenter)
|
||||
altproperties.location.setData({lon: c.lng, lat: c.lat})
|
||||
}
|
||||
|
||||
onDestroy(placedOverMapProperties.location.addCallbackAndRunD(updateLocation))
|
||||
updateLocation()
|
||||
window.setTimeout(updateLocation, 150)
|
||||
window.setTimeout(updateLocation, 500)
|
||||
|
||||
if (visible) {
|
||||
onDestroy(visible?.addCallbackAndRunD(v => {
|
||||
if (!v) {
|
||||
return
|
||||
}
|
||||
updateLocation()
|
||||
window.setTimeout(updateLocation, 150)
|
||||
window.setTimeout(updateLocation, 500)
|
||||
}))
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<MaplibreMap map={altmap}/>
|
73
UI/Map/RasterLayerOverview.svelte
Normal file
73
UI/Map/RasterLayerOverview.svelte
Normal file
|
@ -0,0 +1,73 @@
|
|||
<script lang="ts">
|
||||
/**
|
||||
* The RasterLayerOverview shows the available 4 categories of maps with a RasterLayerPicker
|
||||
*/
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import {Map as MlMap} from "maplibre-gl";
|
||||
import RasterLayerPicker from "./RasterLayerPicker.svelte";
|
||||
import type {EliCategory} from "../../Models/RasterLayerProperties";
|
||||
import UserRelatedState from "../../Logic/State/UserRelatedState";
|
||||
import Translations from "../i18n/Translations";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
|
||||
export let availableLayers: Store<RasterLayerPolygon[]>
|
||||
export let mapproperties: MapProperties
|
||||
export let userstate: UserRelatedState
|
||||
export let map: Store<MlMap>
|
||||
/**
|
||||
* Used to toggle the background layers on/off
|
||||
*/
|
||||
export let visible: UIEventSource<boolean> = undefined
|
||||
|
||||
type CategoryType = "photo" | "map" | "other" | "osmbasedmap"
|
||||
const categories: Record<CategoryType, EliCategory[]> = {
|
||||
"photo": ["photo", "historicphoto"],
|
||||
"map": ["map", "historicmap"],
|
||||
"other": ["other", "elevation"],
|
||||
"osmbasedmap": ["osmbasedmap"]
|
||||
}
|
||||
|
||||
function availableForCategory(type: CategoryType): Store<RasterLayerPolygon[]> {
|
||||
const keywords = categories[type]
|
||||
return availableLayers.mapD(available => available.filter(layer =>
|
||||
keywords.indexOf(<EliCategory>layer.properties.category) >= 0
|
||||
))
|
||||
}
|
||||
|
||||
const mapLayers = availableForCategory("map")
|
||||
const osmbasedmapLayers = availableForCategory("osmbasedmap")
|
||||
const photoLayers = availableForCategory("photo")
|
||||
const otherLayers = availableForCategory("other")
|
||||
|
||||
function onApply() {
|
||||
visible.setData(false)
|
||||
}
|
||||
|
||||
function getPref(type: CategoryType): UIEventSource<string> {
|
||||
return userstate.osmConnection.GetPreference("preferred-layer-" + type)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
<slot name="title">
|
||||
<h2>
|
||||
<Tr t={Translations.t.general.backgroundMap}/>
|
||||
</h2>
|
||||
</slot>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-2 h-full w-full">
|
||||
<RasterLayerPicker availableLayers={photoLayers} favourite={getPref("photo")} {map} {mapproperties}
|
||||
on:appliedLayer={onApply} {visible}/>
|
||||
<RasterLayerPicker availableLayers={mapLayers} favourite={getPref("map")} {map} {mapproperties}
|
||||
on:appliedLayer={onApply} {visible}/>
|
||||
<RasterLayerPicker availableLayers={osmbasedmapLayers} favourite={getPref("osmbasedmap")}
|
||||
{map} {mapproperties} on:appliedLayer={onApply} {visible}/>
|
||||
<RasterLayerPicker availableLayers={otherLayers} favourite={getPref("other")} {map} {mapproperties}
|
||||
on:appliedLayer={onApply} {visible}/>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,18 +1,77 @@
|
|||
<script lang="ts">
|
||||
import type { Readable, Writable } from "svelte/store";
|
||||
import type { RasterLayerPolygon } from "../../Models/RasterLayers";
|
||||
import type {RasterLayerPolygon} from "../../Models/RasterLayers";
|
||||
import OverlayMap from "./OverlayMap.svelte";
|
||||
import type {MapProperties} from "../../Models/MapProperties";
|
||||
import {Store, UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {Map as MlMap} from "maplibre-gl"
|
||||
import {createEventDispatcher, onDestroy} from "svelte";
|
||||
|
||||
/***
|
||||
* Chooses a background-layer out of available options
|
||||
*/
|
||||
export let availableLayers: Readable<RasterLayerPolygon[]>
|
||||
export let value: Writable<RasterLayerPolygon>
|
||||
/***
|
||||
* Chooses a background-layer out of available options
|
||||
*/
|
||||
export let availableLayers: Store<RasterLayerPolygon[]>
|
||||
export let mapproperties: MapProperties
|
||||
export let map: Store<MlMap>
|
||||
|
||||
export let visible: Store<boolean> = undefined
|
||||
|
||||
let dispatch = createEventDispatcher<{appliedLayer}>()
|
||||
|
||||
export let favourite : UIEventSource<string> = undefined
|
||||
|
||||
|
||||
let rasterLayer = new UIEventSource<RasterLayerPolygon>(availableLayers.data?.[0])
|
||||
let hasLayers = true
|
||||
onDestroy(availableLayers.addCallbackAndRun(layers => {
|
||||
if (layers === undefined || layers.length === 0) {
|
||||
hasLayers = false
|
||||
return
|
||||
}
|
||||
hasLayers = true
|
||||
rasterLayer.setData(layers[0])
|
||||
}))
|
||||
|
||||
if(favourite){
|
||||
onDestroy(favourite.addCallbackAndRunD(favourite => {
|
||||
const fav = availableLayers.data?.find(l => l.properties.id === favourite)
|
||||
if(!fav){
|
||||
return
|
||||
}
|
||||
rasterLayer.setData(fav)
|
||||
}))
|
||||
}
|
||||
|
||||
onDestroy(rasterLayer.addCallbackAndRunD(selected => {
|
||||
favourite?.setData(selected.properties.id)
|
||||
}))
|
||||
|
||||
let rasterLayerOnMap = UIEventSource.feedFrom(rasterLayer)
|
||||
|
||||
if (visible) {
|
||||
onDestroy(visible?.addCallbackAndRunD(visible => {
|
||||
if (visible) {
|
||||
rasterLayerOnMap.setData(rasterLayer.data ?? availableLayers.data[0])
|
||||
} else {
|
||||
rasterLayerOnMap.setData(undefined)
|
||||
}
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<select bind:value={$value}>
|
||||
{#each $availableLayers as availableLayer }
|
||||
<option value={availableLayer}>
|
||||
{availableLayer.properties.name}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if hasLayers}
|
||||
<div class="h-full w-full flex flex-col">
|
||||
<button on:click={() => {mapproperties.rasterLayer.setData(rasterLayer.data);
|
||||
dispatch("appliedLayer")
|
||||
}} class="w-full h-full m-0 p-0">
|
||||
<OverlayMap rasterLayer={rasterLayerOnMap} placedOverMap={map} placedOverMapProperties={mapproperties}
|
||||
{visible}/>
|
||||
</button>
|
||||
<select bind:value={$rasterLayer} class="w-full">
|
||||
{#each $availableLayers as availableLayer }
|
||||
<option value={availableLayer}>
|
||||
{availableLayer.properties.name}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue