From e3dec8aafada699be3cddfc86aeb63938036e5c5 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 3 Feb 2024 14:33:10 +0100 Subject: [PATCH] Themes: add possibility to enable terrain, some fixes to overlaymap and pitch control --- .../layers/aerialway/chair_lift.svg.license | 2 +- assets/themes/ski/ski.json | 1 + src/Logic/Actors/InitialMapPositioning.ts | 6 ++- src/Models/Constants.ts | 1 + src/Models/MapProperties.ts | 2 + src/Models/RasterLayers.ts | 3 +- .../ThemeConfig/Json/LayoutConfigJson.ts | 12 +++++ src/Models/ThemeConfig/LayoutConfig.ts | 2 + src/UI/Map/MapLibreAdaptor.ts | 46 +++++++++++++++++-- src/UI/Map/OverlayMap.svelte | 2 + 10 files changed, 70 insertions(+), 7 deletions(-) diff --git a/assets/layers/aerialway/chair_lift.svg.license b/assets/layers/aerialway/chair_lift.svg.license index 52a4a188f3..07a65ef1c4 100644 --- a/assets/layers/aerialway/chair_lift.svg.license +++ b/assets/layers/aerialway/chair_lift.svg.license @@ -1,2 +1,2 @@ SPDX-FileCopyrightText: Gouvernement fran��ais, Roulex 45 -SPDX-License-Identifier: CC0 \ No newline at end of file +SPDX-License-Identifier: CC0-1.0 \ No newline at end of file diff --git a/assets/themes/ski/ski.json b/assets/themes/ski/ski.json index c6e1ac0ced..8e1cad364a 100644 --- a/assets/themes/ski/ski.json +++ b/assets/themes/ski/ski.json @@ -7,6 +7,7 @@ "en": "Everything you need to go skiing" }, "icon": "./assets/layers/aerialway/chair_lift.svg", + "enableTerrain": true, "layers": [ "ski_piste", "aerialway", diff --git a/src/Logic/Actors/InitialMapPositioning.ts b/src/Logic/Actors/InitialMapPositioning.ts index a51aea6c21..55b9aaf4be 100644 --- a/src/Logic/Actors/InitialMapPositioning.ts +++ b/src/Logic/Actors/InitialMapPositioning.ts @@ -1,4 +1,4 @@ -import { UIEventSource } from "../UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { LocalStorageSource } from "../Web/LocalStorageSource" import { QueryParameters } from "../Web/QueryParameters" @@ -15,6 +15,7 @@ import { QueryParameters } from "../Web/QueryParameters" export default class InitialMapPositioning { public zoom: UIEventSource public location: UIEventSource<{ lon: number; lat: number }> + public useTerrain: Store constructor(layoutToUse: LayoutConfig) { function localStorageSynced( key: string, @@ -55,10 +56,11 @@ export default class InitialMapPositioning { ) this.location = new UIEventSource({ lon: lon.data, lat: lat.data }) + // Note: this syncs only in one direction this.location.addCallbackD((loc) => { lat.setData(loc.lat) lon.setData(loc.lon) }) - // Note: this syncs only in one direction + this.useTerrain = new ImmutableStore(layoutToUse.enableTerrain) } } diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index 1b3b71937b..f815756d0c 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -156,6 +156,7 @@ export default class Constants { "addSmall", ] as const public static readonly defaultPinIcons: string[] = Constants._defaultPinIcons + public static readonly maptilerApiKey = "GvoVAJgu46I5rZapJuAy" private static isRetina(): boolean { if (Utils.runningFromConsole) { diff --git a/src/Models/MapProperties.ts b/src/Models/MapProperties.ts index 2492c1b509..eb86c31d64 100644 --- a/src/Models/MapProperties.ts +++ b/src/Models/MapProperties.ts @@ -18,8 +18,10 @@ export interface MapProperties { readonly allowMoving: UIEventSource readonly allowRotating: UIEventSource readonly rotation: UIEventSource + readonly pitch: UIEventSource readonly lastClickLocation: Store<{ lon: number; lat: number }> readonly allowZooming: UIEventSource + readonly useTerrain: Store /** * Triggered when the user navigated by using the keyboard. diff --git a/src/Models/RasterLayers.ts b/src/Models/RasterLayers.ts index f5c8d90b64..39adfd8471 100644 --- a/src/Models/RasterLayers.ts +++ b/src/Models/RasterLayers.ts @@ -5,6 +5,7 @@ import { BBox } from "../Logic/BBox" import { Store, Stores } from "../Logic/UIEventSource" import { GeoOperations } from "../Logic/GeoOperations" import { RasterLayerProperties } from "./RasterLayerProperties" +import Constants from "./Constants" export class AvailableRasterLayers { public static EditorLayerIndex: (Feature & @@ -51,7 +52,7 @@ export class AvailableRasterLayers { type: "Feature", properties: { name: "MapTiler", - url: "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key=GvoVAJgu46I5rZapJuAy", + url: "https://api.maptiler.com/maps/15cc8f61-0353-4be6-b8da-13daea5f7432/style.json?key="+Constants.maptilerApiKey, category: "osmbasedmap", id: "maptiler", type: "vector", diff --git a/src/Models/ThemeConfig/Json/LayoutConfigJson.ts b/src/Models/ThemeConfig/Json/LayoutConfigJson.ts index a3ad24addb..ad9764bced 100644 --- a/src/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -388,6 +388,18 @@ export interface LayoutConfigJson { */ enableNoteImports?: true | boolean + /** + * question: Should the map use elevation data to give a 3D-feel? + * + * This is especially useful for hiking maps, skiing maps etc... + * + * funset: MapComplete default: don't use terrain + * iftrue: Use elevation and render 3D + * iffalse: Do not use terrain + * group: advanced + */ + enableTerrain?: false | boolean + /** * question: What overpass-api instance should be used for this layout? * diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts index c9859b2c54..01beaab12a 100644 --- a/src/Models/ThemeConfig/LayoutConfig.ts +++ b/src/Models/ThemeConfig/LayoutConfig.ts @@ -61,6 +61,7 @@ export default class LayoutConfig implements LayoutInformation { public readonly enableShowAllQuestions: boolean public readonly enableExportButton: boolean public readonly enablePdfDownload: boolean + public readonly enableTerrain: boolean public readonly customCss?: string @@ -208,6 +209,7 @@ export default class LayoutConfig implements LayoutInformation { this.enableShowAllQuestions = json.enableShowAllQuestions ?? false this.enableExportButton = json.enableDownload ?? true this.enablePdfDownload = json.enablePdfDownload ?? true + this.enableTerrain = json.enableTerrain ?? false this.customCss = json.customCss this.overpassUrl = json.overpassUrl ?? Constants.defaultOverpassUrls this.overpassTimeout = json.overpassTimeout ?? 30 diff --git a/src/UI/Map/MapLibreAdaptor.ts b/src/UI/Map/MapLibreAdaptor.ts index 20d21597d0..2ce307fd8d 100644 --- a/src/UI/Map/MapLibreAdaptor.ts +++ b/src/UI/Map/MapLibreAdaptor.ts @@ -1,7 +1,7 @@ -import { Store, UIEventSource } from "../../Logic/UIEventSource" +import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import type { Map as MLMap } from "maplibre-gl" import { Map as MlMap, SourceSpecification } from "maplibre-gl" -import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers" +import { RasterLayerPolygon } from "../../Models/RasterLayers" import { Utils } from "../../Utils" import { BBox } from "../../Logic/BBox" import { ExportableMap, KeyNavigationEvent, MapProperties } from "../../Models/MapProperties" @@ -10,6 +10,7 @@ import MaplibreMap from "./MaplibreMap.svelte" import { RasterLayerProperties } from "../../Models/RasterLayerProperties" import * as htmltoimage from "html-to-image" import RasterLayerHandler from "./RasterLayerHandler" +import Constants from "../../Models/Constants" /** * The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties` @@ -42,6 +43,8 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { readonly minzoom: UIEventSource readonly maxzoom: UIEventSource readonly rotation: UIEventSource + readonly pitch: UIEventSource + readonly useTerrain: Store /** * Functions that are called when one of those actions has happened @@ -78,6 +81,8 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { this.allowZooming = state?.allowZooming ?? new UIEventSource(true) this.bounds = state?.bounds ?? new UIEventSource(undefined) this.rotation = state?.rotation ?? new UIEventSource(0) + this.pitch = state?.pitch ?? new UIEventSource(0) + this.useTerrain = state?.useTerrain ?? new ImmutableStore(false) this.rasterLayer = state?.rasterLayer ?? new UIEventSource(undefined) @@ -108,6 +113,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMinzoom(self.minzoom.data) self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) + self.setTerrain(self.useTerrain.data) rasterLayerHandler.setBackground() this.updateStores(true) }) @@ -121,6 +127,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { self.setMaxzoom(self.maxzoom.data) self.setBounds(self.bounds.data) self.SetRotation(self.rotation.data) + self.setTerrain(self.useTerrain.data) rasterLayerHandler.setBackground() this.updateStores(true) map.on("moveend", () => this.updateStores()) @@ -136,6 +143,9 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { map.on("rotateend", (_) => { this.updateStores() }) + map.on("pitchend", () => { + this.updateStores() + }) map.getContainer().addEventListener("keydown", (event) => { let locked: "islocked" = undefined if (!this.allowMoving.data) { @@ -183,6 +193,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { ) this.allowZooming.addCallbackAndRun((allowZooming) => self.setAllowZooming(allowZooming)) this.bounds.addCallbackAndRunD((bounds) => self.setBounds(bounds)) + this.useTerrain?.addCallbackAndRun(useTerrain => self.setTerrain(useTerrain)) } /** @@ -248,7 +259,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { const w = map.getContainer().getBoundingClientRect().width const h = map.getContainer().getBoundingClientRect().height - let dpi = map.getPixelRatio() + const dpi = map.getPixelRatio() // The 'css'-size stays constant... drawOn.style.width = w + "px" drawOn.style.height = h + "px" @@ -423,6 +434,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { this.bounds.setData(bbox) } this.rotation.setData(map.getBearing()) + this.pitch.setData(map.getPitch()) } private SetZoom(z: number): void { @@ -579,4 +591,32 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap { } map.fitBounds(bounds.toLngLat()) } + + private async setTerrain(useTerrain: boolean) { + const map = this._maplibreMap.data + if (!map) { + return + } + const id = "maptiler-terrain-data" + if (useTerrain) { + if(map.getTerrain()){ + return + } + map.addSource(id, { + "type": "raster-dem", + "url": "https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=" + Constants.maptilerApiKey + }) + try{ + while (!map?.isStyleLoaded()) { + await Utils.waitFor(250) + } + map.setTerrain({ + source: id + }) + }catch (e) { + console.error(e) + } + } + + } } diff --git a/src/UI/Map/OverlayMap.svelte b/src/UI/Map/OverlayMap.svelte index 12b17c6ddf..03a665dfcc 100644 --- a/src/UI/Map/OverlayMap.svelte +++ b/src/UI/Map/OverlayMap.svelte @@ -24,6 +24,8 @@ let altproperties = new MapLibreAdaptor(altmap, { rasterLayer, zoom: UIEventSource.feedFrom(placedOverMapProperties.zoom), + rotation: UIEventSource.feedFrom(placedOverMapProperties.rotation), + pitch: UIEventSource.feedFrom(placedOverMapProperties.pitch) }) altproperties.allowMoving.setData(false) altproperties.allowZooming.setData(false)