From 4f2bbf4b54f700353ccc4e2928549c853d656202 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 11 Mar 2023 02:37:07 +0100 Subject: [PATCH] refactoring(maplibre): add RasterLayer info, add background switch, add default global layers --- Logic/Actors/AvailableBaseLayers.ts | 51 --- .../AvailableBaseLayersImplementation.ts | 309 ------------- Logic/Actors/BackgroundLayerResetter.ts | 55 +-- Models/BaseLayer.ts | 10 - Models/RasterLayers.ts | 276 ++++++++++++ UI/Base/MinimapImplementation.ts | 2 - UI/BigComponents/BackgroundSelector.ts | 41 -- UI/Map/MapLibreAdaptor.ts | 172 +++++++ UI/Map/MaplibreMap.svelte | 43 ++ UI/Map/RasterLayerPicker.svelte | 18 + assets/global-raster-layers.json | 97 ++++ package-lock.json | 418 +++++++++++++++++- package.json | 2 + test.ts | 81 +++- 14 files changed, 1103 insertions(+), 472 deletions(-) delete mode 100644 Logic/Actors/AvailableBaseLayers.ts delete mode 100644 Logic/Actors/AvailableBaseLayersImplementation.ts delete mode 100644 Models/BaseLayer.ts create mode 100644 Models/RasterLayers.ts delete mode 100644 UI/BigComponents/BackgroundSelector.ts create mode 100644 UI/Map/MapLibreAdaptor.ts create mode 100644 UI/Map/MaplibreMap.svelte create mode 100644 UI/Map/RasterLayerPicker.svelte create mode 100644 assets/global-raster-layers.json diff --git a/Logic/Actors/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts deleted file mode 100644 index f6de0451af..0000000000 --- a/Logic/Actors/AvailableBaseLayers.ts +++ /dev/null @@ -1,51 +0,0 @@ -import BaseLayer from "../../Models/BaseLayer" -import { ImmutableStore, Store, UIEventSource } from "../UIEventSource" -import Loc from "../../Models/Loc" - -export interface AvailableBaseLayersObj { - readonly osmCarto: BaseLayer - layerOverview: BaseLayer[] - - AvailableLayersAt(location: Store): Store - - SelectBestLayerAccordingTo( - location: Store, - preferedCategory: Store - ): Store -} - -/** - * Calculates which layers are available at the current location - * Changes the basemap - */ -export default class AvailableBaseLayers { - public static layerOverview: BaseLayer[] - public static osmCarto: BaseLayer - - private static implementation: AvailableBaseLayersObj - - static AvailableLayersAt(location: Store): Store { - return ( - AvailableBaseLayers.implementation?.AvailableLayersAt(location) ?? - new ImmutableStore([]) - ) - } - - static SelectBestLayerAccordingTo( - location: Store, - preferedCategory: UIEventSource - ): Store { - return ( - AvailableBaseLayers.implementation?.SelectBestLayerAccordingTo( - location, - preferedCategory - ) ?? new ImmutableStore(undefined) - ) - } - - public static implement(backend: AvailableBaseLayersObj) { - AvailableBaseLayers.layerOverview = backend.layerOverview - AvailableBaseLayers.osmCarto = backend.osmCarto - AvailableBaseLayers.implementation = backend - } -} diff --git a/Logic/Actors/AvailableBaseLayersImplementation.ts b/Logic/Actors/AvailableBaseLayersImplementation.ts deleted file mode 100644 index 8551db5245..0000000000 --- a/Logic/Actors/AvailableBaseLayersImplementation.ts +++ /dev/null @@ -1,309 +0,0 @@ -import BaseLayer from "../../Models/BaseLayer" -import { Store, Stores } from "../UIEventSource" -import Loc from "../../Models/Loc" -import { GeoOperations } from "../GeoOperations" -import * as editorlayerindex from "../../assets/editor-layer-index.json" -import * as L from "leaflet" -import { TileLayer } from "leaflet" -import * as X from "leaflet-providers" -import { Utils } from "../../Utils" -import { AvailableBaseLayersObj } from "./AvailableBaseLayers" -import { BBox } from "../BBox" - -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: true, // Of course, OpenStreetMap is the best map! - category: "osmbasedmap", - } - - public readonly layerOverview = AvailableBaseLayersImplementation.LoadRasterIndex().concat( - AvailableBaseLayersImplementation.LoadProviderIndex() - ) - public readonly globalLayers = this.layerOverview.filter( - (layer) => layer.feature?.geometry === undefined || layer.feature?.geometry === null - ) - public readonly localLayers = this.layerOverview.filter( - (layer) => layer.feature?.geometry !== undefined && layer.feature?.geometry !== null - ) - - 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.type === "bing") { - // A lot of work to implement - see https://github.com/pietervdvn/MapComplete/issues/648 - 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 ?? 19, - min_zoom: props.min_zoom ?? 1, - name: props.name, - layer: leafletLayer, - feature: layer.geometry !== null ? layer : null, - 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: () => - L.tileLayer.provider(id, { - maxNativeZoom: layer.options?.maxZoom, - maxZoom: Math.max(layer.options?.maxZoom ?? 19, 21), - }), - min_zoom: 1, - max_zoom: layer.options.maxZoom, - category: "osmbasedmap", - isBest: false, - } - } catch (e) { - console.error("Could not find provided layer", name, e) - return null - } - } - - const layers = [ - l("Stamen.TonerLite", "Toner Lite (by Stamen)"), - l("Stamen.TonerBackground", "Toner Background - no labels (by Stamen)"), - l("Stamen.Watercolor", "Watercolor (by Stamen)"), - 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)"), - l("CartoDB.DarkMatter", "Dark Matter (by CartoDB)"), - l("CartoDB.DarkMatterNoLabels", "Dark Matter - 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: Math.max(maxZoom ?? 19, 21), - maxNativeZoom: 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 = `${attribution}` - } - - return L.tileLayer(url, { - attribution: attribution, - maxZoom: Math.max(21, maxZoom ?? 19), - maxNativeZoom: maxZoom ?? 19, - minZoom: 1, - // @ts-ignore - wmts: isWMTS ?? false, - subdomains: domains, - }) - } - - public AvailableLayersAt(location: Store): Store { - return Stores.ListStabilized( - location.map((currentLocation) => { - if (currentLocation === undefined) { - return this.layerOverview - } - return this.CalculateAvailableLayersAt(currentLocation?.lon, currentLocation?.lat) - }) - ) - } - - public SelectBestLayerAccordingTo( - location: Store, - preferedCategory: Store - ): Store { - 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(/*New list, inplace reverse is fine*/) - 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] - }, - [preferedCategory] - ) - } - - private CalculateAvailableLayersAt(lon: number, lat: number): BaseLayer[] { - const availableLayers = [this.osmCarto] - if (lon === undefined || lat === undefined) { - return availableLayers.concat(this.globalLayers) - } - const lonlat: [number, number] = [lon, lat] - for (const layerOverviewItem of this.localLayers) { - const layer = layerOverviewItem - const bbox = BBox.get(layer.feature) - - if (!bbox.contains(lonlat)) { - continue - } - - if (GeoOperations.inside(lonlat, layer.feature)) { - availableLayers.push(layer) - } - } - - return availableLayers.concat(this.globalLayers) - } -} diff --git a/Logic/Actors/BackgroundLayerResetter.ts b/Logic/Actors/BackgroundLayerResetter.ts index ee559b4fb2..f2bf154d3b 100644 --- a/Logic/Actors/BackgroundLayerResetter.ts +++ b/Logic/Actors/BackgroundLayerResetter.ts @@ -1,49 +1,42 @@ -import { UIEventSource } from "../UIEventSource" -import BaseLayer from "../../Models/BaseLayer" -import AvailableBaseLayers from "./AvailableBaseLayers" -import Loc from "../../Models/Loc" +import { Store, UIEventSource } from "../UIEventSource" import { Utils } from "../../Utils" +import { + AvailableRasterLayers, + RasterLayerPolygon, + RasterLayerUtils, +} from "../../Models/RasterLayers" /** - * Sets the current background layer to a layer that is actually available + * When a user pans around on the map, they might pan out of the range of the current background raster layer. + * This actor will then quickly select a (best) raster layer of the same category which is available */ export default class BackgroundLayerResetter { constructor( - currentBackgroundLayer: UIEventSource, - location: UIEventSource, - availableLayers: UIEventSource, - defaultLayerId: string = undefined + currentBackgroundLayer: UIEventSource, + availableLayers: Store ) { if (Utils.runningFromConsole) { return } - defaultLayerId = defaultLayerId ?? AvailableBaseLayers.osmCarto.id + // Change the baseLayer back to OSM if we go out of the current range of the layer + availableLayers.addCallbackAndRunD((availableLayers) => { + // We only check on move/on change of the availableLayers + const currentBgPolygon: RasterLayerPolygon | undefined = currentBackgroundLayer.data - // Change the baselayer back to OSM if we go out of the current range of the layer - availableLayers.addCallbackAndRun((availableLayers) => { - let defaultLayer = undefined - const currentLayer = currentBackgroundLayer.data.id - for (const availableLayer of availableLayers) { - if (availableLayer.id === currentLayer) { - if (availableLayer.max_zoom < location.data.zoom) { - break - } - - if (availableLayer.min_zoom > location.data.zoom) { - break - } - if (availableLayer.id === defaultLayerId) { - defaultLayer = availableLayer - } - return // All good - the current layer still works! - } + if (availableLayers.findIndex((available) => currentBgPolygon == available) >= 0) { + // Still available! + return } + // Oops, we panned out of range for this layer! - console.log( - "AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard" + // What is the 'best' map of the same category which is available? + const availableInSameCat = RasterLayerUtils.SelectBestLayerAccordingTo( + availableLayers, + currentBgPolygon?.properties?.category ?? "osmbasedmap" ) - currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto) + console.log("Selecting a different layer:", availableInSameCat.properties.id) + currentBackgroundLayer.setData(availableInSameCat ?? AvailableRasterLayers.osmCarto) }) } } diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts deleted file mode 100644 index 1e4c2a9335..0000000000 --- a/Models/BaseLayer.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default interface BaseLayer { - id: string - name: string - layer: () => any /*leaflet.TileLayer - not importing as it breaks scripts*/ - max_zoom: number - min_zoom: number - feature: any - isBest?: boolean - category?: "map" | "osmbasedmap" | "photo" | "historicphoto" | string -} diff --git a/Models/RasterLayers.ts b/Models/RasterLayers.ts new file mode 100644 index 0000000000..602af9c3b0 --- /dev/null +++ b/Models/RasterLayers.ts @@ -0,0 +1,276 @@ +import { Feature, Polygon } from "geojson" +import * as editorlayerindex from "../assets/editor-layer-index.json" +import * as globallayers from "../assets/global-raster-layers.json" +import { BBox } from "../Logic/BBox" + +export class AvailableRasterLayers { + public static EditorLayerIndex: (Feature & + RasterLayerPolygon)[] = editorlayerindex.features + public static globalLayers: RasterLayerPolygon[] = globallayers.layers.map( + (properties) => + { + type: "Feature", + properties, + geometry: BBox.global.asGeometry(), + } + ) + public static readonly osmCartoProperties: RasterLayerProperties = { + id: "osm", + name: "OpenStreetMap", + url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", + attribution: { + text: "OpenStreetMap", + url: "https://openStreetMap.org/copyright", + }, + best: true, + max_zoom: 19, + min_zoom: 0, + category: "osmbasedmap", + } + + public static readonly osmCarto: RasterLayerPolygon = { + type: "Feature", + properties: AvailableRasterLayers.osmCartoProperties, + geometry: BBox.global.asGeometry(), + } +} + +export class RasterLayerUtils { + public static SelectBestLayerAccordingTo( + available: RasterLayerPolygon[], + preferredCategory: string | string[] + ): RasterLayerPolygon { + available = [...available] + + if (preferredCategory === undefined) { + return available[0] + } + + let prefered: string[] + if (typeof preferredCategory === "string") { + prefered = [preferredCategory] + } else { + prefered = preferredCategory + } + + for (let i = prefered.length - 1; i >= 0; i--) { + const category = prefered[i] + //Then sort all layers of the preferred type to the top. Stability of the sorting will force a 'best' photo layer on top + available.sort((ap, bp) => { + const a = ap.properties + const b = bp.properties + if (a.category === category && b.category === category) { + return 0 + } + if (a.category !== category) { + return 1 + } + + return -1 + }) + } + const best = available.find((l) => l.properties.best) + if (best) { + return best + } + return available[0] + } +} + +export type RasterLayerPolygon = Feature + +export interface RasterLayerProperties { + /** + * The name of the imagery source + */ + readonly name: string + + readonly id: string + + readonly url: string + readonly category?: + | string + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + + readonly min_zoom?: number + readonly max_zoom?: number + + readonly best?: boolean +} + +/** + * Information about a raster tile layer + * + * Based on the spec here https://github.com/osmlab/editor-layer-index/blob/gh-pages/schema.json + * which was then converted with http://borischerny.com/json-schema-to-typescript-browser/ + */ +export interface EditorLayerIndexProperties extends RasterLayerProperties { + /** + * The name of the imagery source + */ + readonly name: string + /** + * Whether the imagery name should be translated + */ + readonly i18n?: boolean + readonly type: "tms" | "wms" | "bing" | "scanex" | "wms_endpoint" | "wmts" + /** + * A rough categorisation of different types of layers. See https://github.com/osmlab/editor-layer-index/blob/gh-pages/CONTRIBUTING.md#categories for a description of the individual categories. + */ + readonly category?: + | "photo" + | "map" + | "historicmap" + | "osmbasedmap" + | "historicphoto" + | "qa" + | "elevation" + | "other" + /** + * A URL template for imagery tiles + */ + readonly url: string + readonly min_zoom?: number + readonly max_zoom?: number + /** + * explicit/implicit permission by the owner for use in OSM + */ + readonly permission_osm?: "explicit" | "implicit" | "no" + /** + * A URL for the license or permissions for the imagery + */ + readonly license_url?: string + /** + * A URL for the privacy policy of the operator or false if there is no existing privacy policy for tis imagery. + */ + readonly privacy_policy_url?: string | boolean + /** + * A unique identifier for the source; used in imagery_used changeset tag + */ + readonly id: string + /** + * A short English-language description of the source + */ + readonly description?: string + /** + * The ISO 3166-1 alpha-2 two letter country code in upper case. Use ZZ for unknown or multiple. + */ + readonly country_code?: string + /** + * Whether this imagery should be shown in the default world-wide menu + */ + readonly default?: boolean + /** + * Whether this imagery is the best source for the region + */ + readonly best?: boolean + /** + * The age of the oldest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly start_date?: string + /** + * The age of the newest imagery or data in the source, as an RFC3339 date or leading portion of one + */ + readonly end_date?: string + /** + * HTTP header to check for information if the tile is invalid + */ + readonly no_tile_header?: { + /** + * This interface was referenced by `undefined`'s JSON-Schema definition + * via the `patternProperty` "^.*$". + */ + [k: string]: string[] | null + } + /** + * 'true' if tiles are transparent and can be overlaid on another source + */ + readonly overlay?: boolean & string + readonly available_projections?: string[] + readonly attribution?: { + readonly url?: string + readonly text?: string + readonly html?: string + readonly required?: boolean + } + /** + * A URL for an image, that can be displayed in the list of imagery layers next to the name + */ + readonly icon?: string + /** + * A link to an EULA text that has to be accepted by the user, before the imagery source is added. Can contain {lang} to be replaced by a current user language wiki code (like FR:) or an empty string for the default English text. + */ + readonly eula?: string + /** + * A URL for an image, that is displayed in the mapview for attribution + */ + readonly "logo-image"?: string + /** + * Customized text for the terms of use link (default is "Background Terms of Use") + */ + readonly "terms-of-use-text"?: string + /** + * Specify a checksum for tiles, which aren't real tiles. `type` is the digest type and can be MD5, SHA-1, SHA-256, SHA-384 and SHA-512, value is the hex encoded checksum in lower case. To create a checksum save the tile as file and upload it to e.g. https://defuse.ca/checksums.htm. + */ + readonly "no-tile-checksum"?: string + /** + * header-name attribute specifies a header returned by tile server, that will be shown as `metadata-key` attribute in Show Tile Info dialog + */ + readonly "metadata-header"?: string + /** + * Set to `true` if imagery source is properly aligned and does not need imagery offset adjustments. This is used for OSM based sources too. + */ + readonly "valid-georeference"?: boolean + /** + * Size of individual tiles delivered by a TMS service + */ + readonly "tile-size"?: number + /** + * Whether tiles status can be accessed by appending /status to the tile URL and can be submitted for re-rendering by appending /dirty. + */ + readonly "mod-tile-features"?: string + /** + * HTTP headers to be sent to server. It has two attributes header-name and header-value. May be specified multiple times. + */ + readonly "custom-http-headers"?: { + readonly "header-name"?: string + readonly "header-value"?: string + } + /** + * Default layer to open (when using WMS_ENDPOINT type). Contains list of layer tag with two attributes - name and style, e.g. `"default-layers": ["layer": { name="Basisdata_NP_Basiskart_JanMayen_WMTS_25829" "style":"default" } ]` (not allowed in `mirror` attribute) + */ + readonly "default-layers"?: { + layer?: { + "layer-name"?: string + "layer-style"?: string + [k: string]: unknown + } + [k: string]: unknown + }[] + /** + * format to use when connecting tile server (when using WMS_ENDPOINT type) + */ + readonly format?: string + /** + * If `true` transparent tiles will be requested from WMS server + */ + readonly transparent?: boolean & string + /** + * minimum expiry time for tiles in seconds. The larger the value, the longer entry in cache will be considered valid + */ + readonly "minimum-tile-expire"?: number +} diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 271bd72539..a830f6b1d3 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -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 = ( diff --git a/UI/BigComponents/BackgroundSelector.ts b/UI/BigComponents/BackgroundSelector.ts deleted file mode 100644 index 035812f685..0000000000 --- a/UI/BigComponents/BackgroundSelector.ts +++ /dev/null @@ -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 }) { - 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", - } - ) - }) - ) - } -} diff --git a/UI/Map/MapLibreAdaptor.ts b/UI/Map/MapLibreAdaptor.ts new file mode 100644 index 0000000000..f57b076b1a --- /dev/null +++ b/UI/Map/MapLibreAdaptor.ts @@ -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 + private readonly _backgroundLayer?: Store + + private _currentRasterLayer: string = undefined + + constructor( + maplibreMap: Store, + state?: { + // availableBackgroundLayers: Store + /** + * The current background layer + */ + readonly backgroundLayer?: Store + readonly locationControl?: UIEventSource + } + ) { + 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 { + 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 + } +} diff --git a/UI/Map/MaplibreMap.svelte b/UI/Map/MaplibreMap.svelte new file mode 100644 index 0000000000..25d9462d23 --- /dev/null +++ b/UI/Map/MaplibreMap.svelte @@ -0,0 +1,43 @@ + +
+ +
+ + diff --git a/UI/Map/RasterLayerPicker.svelte b/UI/Map/RasterLayerPicker.svelte new file mode 100644 index 0000000000..129d992e0b --- /dev/null +++ b/UI/Map/RasterLayerPicker.svelte @@ -0,0 +1,18 @@ + + + diff --git a/assets/global-raster-layers.json b/assets/global-raster-layers.json new file mode 100644 index 0000000000..c33f71e080 --- /dev/null +++ b/assets/global-raster-layers.json @@ -0,0 +1,97 @@ +{ + "layers": [ + { + "id": "Stamen.TonerLite", + "name": "Toner Lite (by Stamen)", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "Stamen.TonerBackground", + "name": "Toner Background - no labels (by Stamen)", + "category": "osmbasedmap", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/toner-background/{z}/{x}/{y}.png", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "Stamen.Watercolor", + "name": "Watercolor (by Stamen)", + "category": "osmbasedmap", + "url": "https://stamen-tiles-{switch:a,b,c,d}.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.png", + "attribution": { + "html": "Map tiles by Stamen Design, CC BY 3.0 — Map data {attribution.OpenStreetMap}" + }, + "min_zoom": 0, + "max_zoom": 20 + }, + { + "id": "CartoDB.Positron", + "name": "Positron (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20, + "category": "osmbasedmap" + }, + { + "id": "CartoDB.PositronNoLabels", + "name": "Positron - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.Voyager", + "name": "Voyager (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.VoyagerNoLabels", + "name": "Voyager - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/rastertiles/voyager_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.DarkMatter", + "name": "Dark Matter (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + }, + { + "id": "CartoDB.DarkMatterNoLabels", + "name": "Dark Matter - no labels (by CartoDB)", + "url": "https://{switch:a,b,c,d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png", + "category": "osmbasedmap", + "attribution": { + "html": "CARTO" + }, + "max_zoom": 20 + } + ] +} diff --git a/package-lock.json b/package-lock.json index bd84a33a34..2f1747ea64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.25.1", "license": "GPL-3.0-or-later", "dependencies": { + "@onsvisual/svelte-maps": "^1.1.6", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", @@ -37,6 +38,7 @@ "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", + "maplibre-gl": "^2.4.0", "opening_hours": "^3.6.0", "osm-auth": "^1.0.2", "osmtogeojson": "^3.0.0-beta.5", @@ -1799,6 +1801,24 @@ "geojson-rewind": "geojson-rewind" } }, + "node_modules/@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, + "node_modules/@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, "node_modules/@mapbox/sphericalmercator": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz", @@ -1810,6 +1830,32 @@ "xyz": "bin/xyz.js" } }, + "node_modules/@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "node_modules/@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "node_modules/@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "dependencies": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "node_modules/@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1842,6 +1888,16 @@ "node": ">= 8" } }, + "node_modules/@onsvisual/svelte-maps": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@onsvisual/svelte-maps/-/svelte-maps-1.1.6.tgz", + "integrity": "sha512-qrt/Z7SvOLzPdz+q3vL6VEVQp3ayavmPE7Rqbtbhicq3JFx/l/1W2VS8ryHgFGXi5nuuZtAzxW7/p8ZM0L/VOw==", + "peerDependencies": { + "maplibre-gl": "^2.4.0", + "pmtiles": "^2.7.0", + "svelte": "^3.32.2" + } + }, "node_modules/@parcel/service-worker": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@parcel/service-worker/-/service-worker-2.8.2.tgz", @@ -3572,8 +3628,7 @@ "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", - "devOptional": true + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -3642,6 +3697,21 @@ "integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==", "dev": true }, + "node_modules/@types/mapbox__point-geometry": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", + "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA==" + }, + "node_modules/@types/mapbox__vector-tile": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz", + "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==", + "dependencies": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "node_modules/@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -3656,6 +3726,11 @@ "@types/node": "*" } }, + "node_modules/@types/pbf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz", + "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==" + }, "node_modules/@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -4811,6 +4886,11 @@ "utrie": "^1.0.2" } }, + "node_modules/csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5975,6 +6055,11 @@ "quickselect": "^2.0.0" } }, + "node_modules/geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "node_modules/geojson2svg": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/geojson2svg/-/geojson2svg-1.3.3.tgz", @@ -6037,6 +6122,11 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -6076,6 +6166,30 @@ "process": "^0.11.10" } }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -6487,8 +6601,7 @@ "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "node_modules/inquirer": { "version": "8.2.0", @@ -7144,6 +7257,14 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", "integrity": "sha512-Y75c18KdvLKRmqHc0u2WUYud1vEj54i+8SNBxsowr6LJJsnNUJ8KK8cH7uHDpC5U66NNlieEzVxeWipZaYfN0w==" }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -7382,6 +7503,43 @@ "typescript": "^4.9.4" } }, + "node_modules/maplibre-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", + "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.10", + "@types/mapbox__point-geometry": "^0.1.2", + "@types/mapbox__vector-tile": "^1.3.0", + "@types/pbf": "^3.0.2", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.2", + "quickselect": "^2.0.0", + "supercluster": "^7.1.5", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + } + }, + "node_modules/maplibre-gl/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -7667,6 +7825,11 @@ "resolved": "https://registry.npmjs.org/multigeojson/-/multigeojson-0.0.1.tgz", "integrity": "sha512-FbCR4K9xp+0lbcHmJk1TLjXW+l82VcEhDDIU7g3DWm47WyGSpuGX8lJx58pOPa61T0b1zQUJVjllPJ6eXe54lg==" }, + "node_modules/murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "node_modules/mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -8195,6 +8358,21 @@ "pathe": "^1.0.0" } }, + "node_modules/pmtiles": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.7.0.tgz", + "integrity": "sha512-/0WERHBKCt9P8dlQaROQd1CE/J1sqYhdGkDgOROaxCjnpm/U+guWNNxZPPduUy6Wu7wQtPghA7sS4t0T18FKAA==", + "peer": true, + "dependencies": { + "fflate": "^0.7.3" + } + }, + "node_modules/pmtiles/node_modules/fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "peer": true + }, "node_modules/point-in-polygon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", @@ -8220,6 +8398,11 @@ "node": ">=4" } }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "node_modules/prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -9466,6 +9649,19 @@ "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" }, + "node_modules/supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "dependencies": { + "kdbush": "^3.0.0" + } + }, + "node_modules/supercluster/node_modules/kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9493,7 +9689,6 @@ "version": "3.55.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", - "dev": true, "engines": { "node": ">= 8" } @@ -11446,6 +11641,16 @@ "node": ">=0.4.0" } }, + "node_modules/vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "dependencies": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -13036,11 +13241,49 @@ "minimist": "^1.2.6" } }, + "@mapbox/jsonlint-lines-primitives": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz", + "integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==" + }, + "@mapbox/mapbox-gl-supported": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-2.0.1.tgz", + "integrity": "sha512-HP6XvfNIzfoMVfyGjBckjiAOQK9WfX0ywdLubuPMPv+Vqf5fj0uCbgBQYpiqcWZT6cbyyRnTSXDheT1ugvF6UQ==" + }, + "@mapbox/point-geometry": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", + "integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==" + }, "@mapbox/sphericalmercator": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.2.0.tgz", "integrity": "sha512-ZTOuuwGuMOJN+HEmG/68bSEw15HHaMWmQ5gdTsWdWsjDe56K1kGvLOK6bOSC8gWgIvEO0w6un/2Gvv1q5hJSkQ==" }, + "@mapbox/tiny-sdf": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz", + "integrity": "sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==" + }, + "@mapbox/unitbezier": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.1.tgz", + "integrity": "sha512-nMkuDXFv60aBr9soUG5q+GvZYL+2KZHVvsqFCzqnkGEf46U2fvmytHaEVc1/YZbiLn8X+eR3QzX1+dwDO1lxlw==" + }, + "@mapbox/vector-tile": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", + "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", + "requires": { + "@mapbox/point-geometry": "~0.1.0" + } + }, + "@mapbox/whoots-js": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz", + "integrity": "sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -13064,6 +13307,12 @@ "fastq": "^1.6.0" } }, + "@onsvisual/svelte-maps": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@onsvisual/svelte-maps/-/svelte-maps-1.1.6.tgz", + "integrity": "sha512-qrt/Z7SvOLzPdz+q3vL6VEVQp3ayavmPE7Rqbtbhicq3JFx/l/1W2VS8ryHgFGXi5nuuZtAzxW7/p8ZM0L/VOw==", + "requires": {} + }, "@parcel/service-worker": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/@parcel/service-worker/-/service-worker-2.8.2.tgz", @@ -14417,8 +14666,7 @@ "@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", - "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==", - "devOptional": true + "integrity": "sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==" }, "@types/istanbul-lib-coverage": { "version": "2.0.4", @@ -14486,6 +14734,21 @@ "integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==", "dev": true }, + "@types/mapbox__point-geometry": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.2.tgz", + "integrity": "sha512-D0lgCq+3VWV85ey1MZVkE8ZveyuvW5VAfuahVTQRpXFQTxw03SuIf1/K4UQ87MMIXVKzpFjXFiFMZzLj2kU+iA==" + }, + "@types/mapbox__vector-tile": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@types/mapbox__vector-tile/-/mapbox__vector-tile-1.3.0.tgz", + "integrity": "sha512-kDwVreQO5V4c8yAxzZVQLE5tyWF+IPToAanloQaSnwfXmIcJ7cyOrv8z4Ft4y7PsLYmhWXmON8MBV8RX0Rgr8g==", + "requires": { + "@types/geojson": "*", + "@types/mapbox__point-geometry": "*", + "@types/pbf": "*" + } + }, "@types/node": { "version": "18.11.18", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz", @@ -14500,6 +14763,11 @@ "@types/node": "*" } }, + "@types/pbf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/pbf/-/pbf-3.0.2.tgz", + "integrity": "sha512-EDrLIPaPXOZqDjrkzxxbX7UlJSeQVgah3i0aA4pOSzmK9zq3BIh7/MZIQxED7slJByvKM4Gc6Hypyu2lJzh3SQ==" + }, "@types/prompt-sync": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/prompt-sync/-/prompt-sync-4.2.0.tgz", @@ -15382,6 +15650,11 @@ "utrie": "^1.0.2" } }, + "csscolorparser": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz", + "integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==" + }, "cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -16288,6 +16561,11 @@ } } }, + "geojson-vt": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz", + "integrity": "sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==" + }, "geojson2svg": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/geojson2svg/-/geojson2svg-1.3.3.tgz", @@ -16335,6 +16613,11 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "dev": true }, + "gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==" + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -16365,6 +16648,26 @@ "process": "^0.11.10" } }, + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -16668,8 +16971,7 @@ "ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "inquirer": { "version": "8.2.0", @@ -17149,6 +17451,11 @@ "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-1.0.1.tgz", "integrity": "sha512-Y75c18KdvLKRmqHc0u2WUYud1vEj54i+8SNBxsowr6LJJsnNUJ8KK8cH7uHDpC5U66NNlieEzVxeWipZaYfN0w==" }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, "kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -17335,6 +17642,44 @@ "typescript": "^4.9.4" } }, + "maplibre-gl": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-2.4.0.tgz", + "integrity": "sha512-csNFylzntPmHWidczfgCZpvbTSmhaWvLRj9e1ezUDBEPizGgshgm3ea1T5TCNEEBq0roauu7BPuRZjA3wO4KqA==", + "requires": { + "@mapbox/geojson-rewind": "^0.5.2", + "@mapbox/jsonlint-lines-primitives": "^2.0.2", + "@mapbox/mapbox-gl-supported": "^2.0.1", + "@mapbox/point-geometry": "^0.1.0", + "@mapbox/tiny-sdf": "^2.0.5", + "@mapbox/unitbezier": "^0.0.1", + "@mapbox/vector-tile": "^1.3.1", + "@mapbox/whoots-js": "^3.1.0", + "@types/geojson": "^7946.0.10", + "@types/mapbox__point-geometry": "^0.1.2", + "@types/mapbox__vector-tile": "^1.3.0", + "@types/pbf": "^3.0.2", + "csscolorparser": "~1.0.3", + "earcut": "^2.2.4", + "geojson-vt": "^3.2.1", + "gl-matrix": "^3.4.3", + "global-prefix": "^3.0.0", + "murmurhash-js": "^1.0.0", + "pbf": "^3.2.1", + "potpack": "^1.0.2", + "quickselect": "^2.0.0", + "supercluster": "^7.1.5", + "tinyqueue": "^2.0.3", + "vt-pbf": "^3.1.3" + }, + "dependencies": { + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + } + } + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -17547,6 +17892,11 @@ "resolved": "https://registry.npmjs.org/multigeojson/-/multigeojson-0.0.1.tgz", "integrity": "sha512-FbCR4K9xp+0lbcHmJk1TLjXW+l82VcEhDDIU7g3DWm47WyGSpuGX8lJx58pOPa61T0b1zQUJVjllPJ6eXe54lg==" }, + "murmurhash-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/murmurhash-js/-/murmurhash-js-1.0.0.tgz", + "integrity": "sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==" + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -17945,6 +18295,23 @@ "pathe": "^1.0.0" } }, + "pmtiles": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pmtiles/-/pmtiles-2.7.0.tgz", + "integrity": "sha512-/0WERHBKCt9P8dlQaROQd1CE/J1sqYhdGkDgOROaxCjnpm/U+guWNNxZPPduUy6Wu7wQtPghA7sS4t0T18FKAA==", + "peer": true, + "requires": { + "fflate": "^0.7.3" + }, + "dependencies": { + "fflate": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz", + "integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==", + "peer": true + } + } + }, "point-in-polygon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", @@ -17967,6 +18334,11 @@ "util-deprecate": "^1.0.2" } }, + "potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==" + }, "prebuild-install": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", @@ -18902,6 +19274,21 @@ "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.9.0.tgz", "integrity": "sha512-vMJ8Byp1uIPoj+wb9c1AdK4jpkSKVAywgHX0lqY7zt6+EWRRC3Z+0Ucfjy/0yxTVO1hwwchZe4uoFNqrIC24+A==" }, + "supercluster": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.5.tgz", + "integrity": "sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==", + "requires": { + "kdbush": "^3.0.0" + }, + "dependencies": { + "kdbush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-3.0.0.tgz", + "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==" + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18919,8 +19306,7 @@ "svelte": { "version": "3.55.1", "resolved": "https://registry.npmjs.org/svelte/-/svelte-3.55.1.tgz", - "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==", - "dev": true + "integrity": "sha512-S+87/P0Ve67HxKkEV23iCdAh/SX1xiSfjF1HOglno/YTbSTW7RniICMCofWGdJJbdjw3S+0PfFb1JtGfTXE0oQ==" }, "svelte-check": { "version": "3.0.3", @@ -20360,6 +20746,16 @@ } } }, + "vt-pbf": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", + "integrity": "sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==", + "requires": { + "@mapbox/point-geometry": "0.1.0", + "@mapbox/vector-tile": "^1.3.1", + "pbf": "^3.2.1" + } + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index 3fa44de645..a224b3ac7d 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "not op_mini all" ], "dependencies": { + "@onsvisual/svelte-maps": "^1.1.6", "@rollup/plugin-typescript": "^11.0.0", "@turf/boolean-intersects": "^6.5.0", "@turf/buffer": "^6.5.0", @@ -89,6 +90,7 @@ "libphonenumber-js": "^1.10.8", "lz-string": "^1.4.4", "mangrove-reviews-typescript": "^1.1.0", + "maplibre-gl": "^2.4.0", "opening_hours": "^3.6.0", "osm-auth": "^1.0.2", "osmtogeojson": "^3.0.0-beta.5", diff --git a/test.ts b/test.ts index 7c1a0d9bef..80b3de758e 100644 --- a/test.ts +++ b/test.ts @@ -1,25 +1,72 @@ -import ContactLink from "./UI/BigComponents/ContactLink.svelte" import SvelteUIElement from "./UI/Base/SvelteUIElement" -import { Utils } from "./Utils" -import List from "./UI/Base/List" +import MaplibreMap from "./UI/Map/MaplibreMap.svelte" +import { Store, Stores, UIEventSource } from "./Logic/UIEventSource" +import { MapLibreAdaptor } from "./UI/Map/MapLibreAdaptor" +import { + EditorLayerIndexProperties, + RasterLayerPolygon, + RasterLayerProperties, +} from "./Models/RasterLayers" +import type { Map as MlMap } from "maplibre-gl" +import { AvailableRasterLayers } from "./Models/RasterLayers" +import Loc from "./Models/Loc" +import { BBox } from "./Logic/BBox" import { GeoOperations } from "./Logic/GeoOperations" -import { Tiles } from "./Models/TileRange" -import { Stores } from "./Logic/UIEventSource" +import RasterLayerPicker from "./UI/Map/RasterLayerPicker.svelte" +import BackgroundLayerResetter from "./Logic/Actors/BackgroundLayerResetter" async function main() { - const location: [number, number] = [3.21, 51.2] - const t = Tiles.embedded_tile(location[1], location[0], 6) - const url = `https://raw.githubusercontent.com/pietervdvn/MapComplete-data/main/community_index/tile_${t.z}_${t.x}_${t.y}.geojson` - const be = Stores.FromPromise(Utils.downloadJson(url)).mapD( - (data) => data.features.find((f) => GeoOperations.inside(location, f)).properties + const mlmap = new UIEventSource(undefined) + const locationControl = new UIEventSource({ + zoom: 14, + lat: 51.1, + lon: 3.1, + }) + new SvelteUIElement(MaplibreMap, { + map: mlmap, + }) + .SetClass("border border-black") + .SetStyle("height: 50vh; width: 90%; margin: 1%") + .AttachTo("maindiv") + const bg = new UIEventSource(undefined) + new MapLibreAdaptor(mlmap, { + backgroundLayer: bg, + locationControl, + }) + + const availableLayersBboxes = Stores.ListStabilized( + locationControl.mapD((loc) => { + const lonlat: [number, number] = [loc.lon, loc.lat] + return AvailableRasterLayers.EditorLayerIndex.filter((eliPolygon) => + BBox.get(eliPolygon).contains(lonlat) + ) + }) ) - new SvelteUIElement(ContactLink, { country: be }).AttachTo("maindiv") - /* - const links = data.features - .filter((f) => GeoOperations.inside(location, f)) - .map((f) => new SvelteUIElement(ContactLink, { country: f.properties })) - new List(links).AttachTo("maindiv") - //*/ + const availableLayers: Store = Stores.ListStabilized( + availableLayersBboxes.map((eliPolygons) => { + const loc = locationControl.data + const lonlat: [number, number] = [loc.lon, loc.lat] + const matching: RasterLayerPolygon[] = eliPolygons.filter((eliPolygon) => { + if (eliPolygon.geometry === null) { + return true // global ELI-layer + } + return GeoOperations.inside(lonlat, eliPolygon) + }) + matching.unshift(AvailableRasterLayers.osmCarto) + matching.push(...AvailableRasterLayers.globalLayers) + return matching + }) + ) + + availableLayers.map((a) => + console.log( + "Availabe layers at current location:", + a.map((al) => al.properties.id) + ) + ) + + new BackgroundLayerResetter(bg, availableLayers) + new SvelteUIElement(RasterLayerPicker, { availableLayers, value: bg }).AttachTo("extradiv") } main().then((_) => {})