forked from MapComplete/MapComplete
Merge master
This commit is contained in:
commit
890816d2dd
424 changed files with 40595 additions and 3354 deletions
|
@ -1,6 +1,7 @@
|
|||
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Map as MLMap } from "maplibre-gl"
|
||||
import { Map as MLMap } from "maplibre-gl"
|
||||
import { Map as MlMap, SourceSpecification } from "maplibre-gl"
|
||||
import maplibregl from "maplibre-gl"
|
||||
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
@ -11,6 +12,8 @@ import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
|||
import * as htmltoimage from "html-to-image"
|
||||
import RasterLayerHandler from "./RasterLayerHandler"
|
||||
import Constants from "../../Models/Constants"
|
||||
import { Protocol } from "pmtiles"
|
||||
import { bool } from "sharp"
|
||||
|
||||
/**
|
||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||
|
@ -23,13 +26,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
"dragRotate",
|
||||
"dragPan",
|
||||
"keyboard",
|
||||
"touchZoomRotate",
|
||||
"touchZoomRotate"
|
||||
]
|
||||
private static maplibre_zoom_handlers = [
|
||||
"scrollZoom",
|
||||
"boxZoom",
|
||||
"doubleClickZoom",
|
||||
"touchZoomRotate",
|
||||
"touchZoomRotate"
|
||||
]
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
readonly zoom: UIEventSource<number>
|
||||
|
@ -46,6 +49,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
readonly pitch: UIEventSource<number>
|
||||
readonly useTerrain: Store<boolean>
|
||||
|
||||
private static pmtilesInited = false
|
||||
/**
|
||||
* Functions that are called when one of those actions has happened
|
||||
* @private
|
||||
|
@ -55,6 +59,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
private readonly _maplibreMap: Store<MLMap>
|
||||
|
||||
constructor(maplibreMap: Store<MLMap>, state?: Partial<MapProperties>) {
|
||||
if (!MapLibreAdaptor.pmtilesInited) {
|
||||
maplibregl.addProtocol("pmtiles", new Protocol().tile)
|
||||
MapLibreAdaptor.pmtilesInited = true
|
||||
console.log("PM-tiles protocol added" +
|
||||
"")
|
||||
}
|
||||
this._maplibreMap = maplibreMap
|
||||
|
||||
this.location = state?.location ?? new UIEventSource(undefined)
|
||||
|
@ -103,6 +113,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
}
|
||||
|
||||
maplibreMap.addCallbackAndRunD((map) => {
|
||||
|
||||
map.on("load", () => {
|
||||
self.MoveMapToCurrentLoc(self.location.data)
|
||||
self.SetZoom(self.zoom.data)
|
||||
|
@ -205,14 +216,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
return {
|
||||
map: mlmap,
|
||||
ui: new SvelteUIElement(MaplibreMap, {
|
||||
map: mlmap,
|
||||
map: mlmap
|
||||
}),
|
||||
mapproperties: new MapLibreAdaptor(mlmap),
|
||||
mapproperties: new MapLibreAdaptor(mlmap)
|
||||
}
|
||||
}
|
||||
|
||||
public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification {
|
||||
return RasterLayerHandler.prepareWmsSource(layer)
|
||||
return RasterLayerHandler.prepareSource(layer)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -275,7 +286,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
) {
|
||||
const event = {
|
||||
date: new Date(),
|
||||
key: key,
|
||||
key: key
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._onKeyNavigation.length; i++) {
|
||||
|
@ -319,22 +330,51 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
rescaleIcons: number,
|
||||
pixelRatio: number
|
||||
) {
|
||||
|
||||
{
|
||||
const allimages = element.getElementsByTagName("img")
|
||||
for (const img of Array.from(allimages)) {
|
||||
let isLoaded: boolean = false
|
||||
while (!isLoaded) {
|
||||
console.log("Waiting for image", img.src, "to load", img.complete, img.naturalWidth, img)
|
||||
await Utils.waitFor(250)
|
||||
isLoaded = img.complete && img.width > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const style = element.style.transform
|
||||
let x = element.getBoundingClientRect().x
|
||||
let y = element.getBoundingClientRect().y
|
||||
element.style.transform = ""
|
||||
const offset = style.match(/translate\(([-0-9]+)%, ?([-0-9]+)%\)/)
|
||||
|
||||
let labels =<HTMLElement[]> Array.from(element.getElementsByClassName("marker-label"))
|
||||
const origLabelTransforms = labels.map(l => l.style.transform)
|
||||
// We save the original width (`w`) and height (`h`) in order to restore them later on
|
||||
const w = element.style.width
|
||||
const h = element.style.height
|
||||
const h = Number(element.style.height)
|
||||
const targetW = Math.max(element.getBoundingClientRect().width * 4,
|
||||
...labels.map(l => l.getBoundingClientRect().width))
|
||||
const targetH = element.getBoundingClientRect().height +
|
||||
Math.max(...labels.map(l => l.getBoundingClientRect().height * 2 /* A bit of buffer to catch eventual 'margin-top'*/))
|
||||
|
||||
// Force a wider view for icon badges
|
||||
element.style.width = element.getBoundingClientRect().width * 4 + "px"
|
||||
element.style.height = element.getBoundingClientRect().height + "px"
|
||||
element.style.width = targetW + "px"
|
||||
// Force more height to include labels
|
||||
element.style.height = targetH + "px"
|
||||
element.classList.add("w-full", "flex", "flex-col", "items-center")
|
||||
labels.forEach(l => {
|
||||
l.style.transform = ""
|
||||
})
|
||||
await Utils.awaitAnimationFrame()
|
||||
const svgSource = await htmltoimage.toSvg(element)
|
||||
const img = await MapLibreAdaptor.createImage(svgSource)
|
||||
element.style.width = w
|
||||
element.style.height = h
|
||||
for (let i = 0; i < labels.length; i++) {
|
||||
labels[i].style.transform = origLabelTransforms[i]
|
||||
}
|
||||
element.style.width = "" + w
|
||||
element.style.height = "" + h
|
||||
|
||||
if (offset && rescaleIcons !== 1) {
|
||||
const [_, __, relYStr] = offset
|
||||
|
@ -346,10 +386,13 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
y *= pixelRatio
|
||||
|
||||
try {
|
||||
drawOn.drawImage(img, x, y, img.width * rescaleIcons, img.height * rescaleIcons)
|
||||
const xdiff = img.width * rescaleIcons / 2
|
||||
drawOn.drawImage(img, x - xdiff, y, img.width * rescaleIcons, img.height * rescaleIcons)
|
||||
} catch (e) {
|
||||
console.log("Could not draw image because of", e)
|
||||
}
|
||||
element.classList.remove("w-full", "flex", "flex-col", "items-center")
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -384,19 +427,12 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
const markers = Array.from(container.getElementsByClassName("marker"))
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
const marker = <HTMLElement>markers[i]
|
||||
const labels = Array.from(marker.getElementsByClassName("marker-label"))
|
||||
const style = marker.style.transform
|
||||
|
||||
if (isDisplayed(marker)) {
|
||||
await this.drawElement(drawOn, marker, rescaleIcons, pixelRatio)
|
||||
}
|
||||
|
||||
for (const label of labels) {
|
||||
if (isDisplayed(label)) {
|
||||
await this.drawElement(drawOn, <HTMLElement>label, rescaleIcons, pixelRatio)
|
||||
}
|
||||
}
|
||||
|
||||
if (progress) {
|
||||
progress.setData({ current: i, total: markers.length })
|
||||
}
|
||||
|
@ -425,7 +461,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
const bounds = map.getBounds()
|
||||
const bbox = new BBox([
|
||||
[bounds.getEast(), bounds.getNorth()],
|
||||
[bounds.getWest(), bounds.getSouth()],
|
||||
[bounds.getWest(), bounds.getSouth()]
|
||||
])
|
||||
if (this.bounds.data === undefined || !isSetup) {
|
||||
this.bounds.setData(bbox)
|
||||
|
@ -603,14 +639,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
|||
type: "raster-dem",
|
||||
url:
|
||||
"https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=" +
|
||||
Constants.maptilerApiKey,
|
||||
Constants.maptilerApiKey
|
||||
})
|
||||
try {
|
||||
while (!map?.isStyleLoaded()) {
|
||||
await Utils.waitFor(250)
|
||||
}
|
||||
map.setTerrain({
|
||||
source: id,
|
||||
source: id
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy, onMount } from "svelte"
|
||||
import type { Map } from "maplibre-gl"
|
||||
import type { Map, MapOptions } from "maplibre-gl"
|
||||
import * as maplibre from "maplibre-gl"
|
||||
import type { Readable, Writable } from "svelte/store"
|
||||
import { get, writable } from "svelte/store"
|
||||
import type { Writable } from "svelte/store"
|
||||
import { AvailableRasterLayers } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||
import Translations from "../i18n/Translations"
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
import type { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||
|
||||
/**
|
||||
* The 'MaplibreMap' maps various event sources onto MapLibre.
|
||||
|
@ -17,40 +18,43 @@
|
|||
* 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<Map>
|
||||
export let map: Writable<Map> = undefined
|
||||
export let mapProperties: MapProperties = undefined
|
||||
|
||||
export let interactive: boolean = true
|
||||
|
||||
let container: HTMLElement
|
||||
|
||||
export let center: { lng: number; lat: number } | Readable<{ lng: number; lat: number }> =
|
||||
writable({ lng: 0, lat: 0 })
|
||||
export let zoom: Readable<number> = writable(1)
|
||||
|
||||
const styleUrl = AvailableRasterLayers.maptilerDefaultLayer.properties.url
|
||||
|
||||
let _map: Map
|
||||
onMount(() => {
|
||||
let _center: { lng: number; lat: number }
|
||||
if (typeof center["lng"] === "number" && typeof center["lat"] === "number") {
|
||||
_center = <any>center
|
||||
const { lon, lat } = mapProperties?.location?.data ?? { lon: 0, lat: 0 }
|
||||
|
||||
const rasterLayer: RasterLayerProperties = mapProperties?.rasterLayer?.data?.properties
|
||||
let styleUrl: string
|
||||
if (rasterLayer?.type === "vector") {
|
||||
styleUrl = rasterLayer?.style ?? rasterLayer?.url ?? AvailableRasterLayers.defaultBackgroundLayer.properties.url
|
||||
} else {
|
||||
_center = get(<any>center)
|
||||
const defaultLayer = AvailableRasterLayers.defaultBackgroundLayer.properties
|
||||
styleUrl = defaultLayer.style ?? defaultLayer.url
|
||||
}
|
||||
|
||||
_map = new maplibre.Map({
|
||||
console.log("Initing mapLIbremap with style", styleUrl)
|
||||
|
||||
const options: MapOptions = {
|
||||
container,
|
||||
style: styleUrl,
|
||||
zoom: get(zoom),
|
||||
center: _center,
|
||||
zoom: mapProperties?.zoom?.data ?? 1,
|
||||
center: { lng: lon, lat },
|
||||
maxZoom: 24,
|
||||
interactive: true,
|
||||
attributionControl: false,
|
||||
})
|
||||
attributionControl: false
|
||||
}
|
||||
_map = new maplibre.Map(options)
|
||||
window.requestAnimationFrame(() => {
|
||||
_map.resize()
|
||||
})
|
||||
_map.on("load", function () {
|
||||
_map.on("load", function() {
|
||||
_map.resize()
|
||||
const canvas = _map.getCanvas()
|
||||
if (interactive) {
|
||||
|
|
|
@ -74,4 +74,4 @@
|
|||
style="z-index: 100">
|
||||
<StyleLoadingIndicator map={altmap} />
|
||||
</div>
|
||||
<MaplibreMap {interactive} map={altmap} />
|
||||
<MaplibreMap {interactive} map={altmap} mapProperties={altproperties} />
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Map as MLMap, SourceSpecification } from "maplibre-gl"
|
||||
import { Map as MLMap, RasterSourceSpecification, VectorTileSource } from "maplibre-gl"
|
||||
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { AvailableRasterLayers, RasterLayerPolygon } from "../../Models/RasterLayers"
|
||||
import { RasterLayerProperties } from "../../Models/RasterLayerProperties"
|
||||
import { Utils } from "../../Utils"
|
||||
import { VectorSourceSpecification } from "@maplibre/maplibre-gl-style-spec"
|
||||
|
||||
class SingleBackgroundHandler {
|
||||
// Value between 0 and 1.0
|
||||
|
@ -17,6 +18,7 @@ class SingleBackgroundHandler {
|
|||
*/
|
||||
public static readonly DEACTIVATE_AFTER = 60
|
||||
private fadeStep = 0.1
|
||||
|
||||
constructor(
|
||||
map: Store<MLMap>,
|
||||
targetLayer: RasterLayerPolygon,
|
||||
|
@ -75,6 +77,7 @@ class SingleBackgroundHandler {
|
|||
this.fadeIn()
|
||||
}
|
||||
}
|
||||
|
||||
private async awaitStyleIsLoaded(): Promise<void> {
|
||||
const map = this._map.data
|
||||
if (!map) {
|
||||
|
@ -85,11 +88,11 @@ class SingleBackgroundHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private async enable(){
|
||||
private async enable() {
|
||||
let ttl = 15
|
||||
await this.awaitStyleIsLoaded()
|
||||
while(!this.tryEnable() && ttl > 0){
|
||||
ttl --;
|
||||
while (!this.tryEnable() && ttl > 0) {
|
||||
ttl--
|
||||
await Utils.waitFor(250)
|
||||
}
|
||||
}
|
||||
|
@ -105,14 +108,19 @@ class SingleBackgroundHandler {
|
|||
}
|
||||
const background = this._targetLayer.properties
|
||||
console.debug("Enabling", background.id)
|
||||
let addLayerBeforeId = "aeroway_fill" // this is the first non-landuse item in the stylesheet, we add the raster layer before the roads but above the landuse
|
||||
let addLayerBeforeId = "transit_pier" // this is the first non-landuse item in the stylesheet, we add the raster layer before the roads but above the landuse
|
||||
if(!map.getLayer(addLayerBeforeId)){
|
||||
console.warn("Layer", addLayerBeforeId,"not foundhttp://127.0.0.1:1234/theme.html?layout=cyclofix&z=14.8&lat=51.05282501324558&lon=3.720591622281745&layer-range=true")
|
||||
addLayerBeforeId = undefined
|
||||
}
|
||||
if (background.category === "osmbasedmap" || background.category === "map") {
|
||||
// The background layer is already an OSM-based map or another map, so we don't want anything from the baselayer
|
||||
addLayerBeforeId = undefined
|
||||
}
|
||||
|
||||
if (!map.getSource(background.id)) {
|
||||
try {
|
||||
map.addSource(background.id, RasterLayerHandler.prepareWmsSource(background))
|
||||
map.addSource(background.id, RasterLayerHandler.prepareSource(background))
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
@ -122,21 +130,30 @@ class SingleBackgroundHandler {
|
|||
.getStyle()
|
||||
.layers.find((l) => l.id.startsWith("mapcomplete_"))?.id
|
||||
|
||||
map.addLayer(
|
||||
{
|
||||
id: background.id,
|
||||
type: "raster",
|
||||
source: background.id,
|
||||
paint: {
|
||||
"raster-opacity": 0,
|
||||
if (background.type === "vector") {
|
||||
const styleToSet = background.style ?? background.url
|
||||
map.setStyle(styleToSet)
|
||||
} else {
|
||||
map.addLayer(
|
||||
{
|
||||
id: background.id,
|
||||
type: "raster",
|
||||
source: background.id,
|
||||
paint: {
|
||||
"raster-opacity": 0
|
||||
}
|
||||
},
|
||||
},
|
||||
addLayerBeforeId
|
||||
)
|
||||
|
||||
this.opacity.addCallbackAndRun((o) => {
|
||||
map.setPaintProperty(background.id, "raster-opacity", o)
|
||||
})
|
||||
addLayerBeforeId
|
||||
)
|
||||
this.opacity.addCallbackAndRun((o) => {
|
||||
try{
|
||||
map.setPaintProperty(background.id, "raster-opacity", o)
|
||||
}catch (e) {
|
||||
console.debug("Could not set raster-opacity of", background.id)
|
||||
return true // This layer probably doesn't exist anymore, so we unregister
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -168,7 +185,14 @@ export default class RasterLayerHandler {
|
|||
})
|
||||
}
|
||||
|
||||
public static prepareWmsSource(layer: RasterLayerProperties): SourceSpecification {
|
||||
public static prepareSource(layer: RasterLayerProperties): RasterSourceSpecification | VectorSourceSpecification {
|
||||
if (layer.type === "vector") {
|
||||
const vs: VectorSourceSpecification = {
|
||||
type: "vector",
|
||||
url: layer.url
|
||||
}
|
||||
return vs
|
||||
}
|
||||
return {
|
||||
type: "raster",
|
||||
// use the tiles option to specify a 256WMS tile source URL
|
||||
|
@ -178,7 +202,7 @@ export default class RasterLayerHandler {
|
|||
minzoom: layer["min_zoom"] ?? 1,
|
||||
maxzoom: layer["max_zoom"] ?? 25,
|
||||
// Bit of a hack, but seems to work
|
||||
scheme: layer.url.includes("{-y}") ? "tms" : "xyz",
|
||||
scheme: layer.url.includes("{-y}") ? "tms" : "xyz"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,7 +216,7 @@ export default class RasterLayerHandler {
|
|||
"{width}": "" + size,
|
||||
"{height}": "" + size,
|
||||
"{zoom}": "{z}",
|
||||
"{-y}": "{y}",
|
||||
"{-y}": "{y}"
|
||||
}
|
||||
|
||||
for (const key in toReplace) {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import StyleLoadingIndicator from "./StyleLoadingIndicator.svelte"
|
||||
|
||||
/***
|
||||
* Chooses a background-layer out of available options
|
||||
|
|
|
@ -154,7 +154,7 @@ class PointRenderingLayer {
|
|||
|
||||
if (this._onClick) {
|
||||
const self = this
|
||||
el.addEventListener("click", function (ev) {
|
||||
el.addEventListener("click", function(ev) {
|
||||
ev.preventDefault()
|
||||
self._onClick(feature)
|
||||
// Workaround to signal the MapLibreAdaptor to ignore this click
|
||||
|
@ -200,7 +200,7 @@ class LineRenderingLayer {
|
|||
"lineCap",
|
||||
"offset",
|
||||
"fill",
|
||||
"fillColor",
|
||||
"fillColor"
|
||||
] as const
|
||||
|
||||
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
|
||||
|
@ -249,16 +249,8 @@ class LineRenderingLayer {
|
|||
imageAlongWay.map(async (img, i) => {
|
||||
const imgId = img.then.replaceAll(/[/.-]/g, "_")
|
||||
if (map.getImage(imgId) === undefined) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
map.loadImage(img.then, (err, image) => {
|
||||
if (err) {
|
||||
console.error("Could not add symbol layer to line due to", err)
|
||||
return
|
||||
}
|
||||
map.addImage(imgId, image)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
const loadedImage = await map.loadImage(img.then)
|
||||
map.addImage(imgId, loadedImage.data)
|
||||
}
|
||||
|
||||
const spec: AddLayerObject = {
|
||||
|
@ -272,11 +264,10 @@ class LineRenderingLayer {
|
|||
"icon-rotation-alignment": "map",
|
||||
"icon-pitch-alignment": "map",
|
||||
"icon-image": imgId,
|
||||
"icon-size": 0.055,
|
||||
},
|
||||
"icon-size": 0.055
|
||||
}
|
||||
}
|
||||
const filter = img.if?.asMapboxExpression()
|
||||
console.log(">>>", this._layername, imgId, img.if, "-->", filter)
|
||||
if (filter) {
|
||||
spec.filter = filter
|
||||
}
|
||||
|
@ -347,12 +338,12 @@ class LineRenderingLayer {
|
|||
type: "geojson",
|
||||
data: {
|
||||
type: "FeatureCollection",
|
||||
features,
|
||||
features
|
||||
},
|
||||
promoteId: "id",
|
||||
promoteId: "id"
|
||||
})
|
||||
const linelayer = this._layername + "_line"
|
||||
map.addLayer({
|
||||
const layer: AddLayerObject = {
|
||||
source: this._layername,
|
||||
id: linelayer,
|
||||
type: "line",
|
||||
|
@ -360,12 +351,17 @@ class LineRenderingLayer {
|
|||
"line-color": ["feature-state", "color"],
|
||||
"line-opacity": ["feature-state", "color-opacity"],
|
||||
"line-width": ["feature-state", "width"],
|
||||
"line-offset": ["feature-state", "offset"],
|
||||
"line-offset": ["feature-state", "offset"]
|
||||
},
|
||||
layout: {
|
||||
"line-cap": "round",
|
||||
},
|
||||
})
|
||||
"line-cap": "round"
|
||||
}
|
||||
}
|
||||
if (this._config.dashArray) {
|
||||
|
||||
layer.paint["line-dasharray"] = this._config.dashArray?.split(" ")?.map(s => Number(s)) ?? null
|
||||
}
|
||||
map.addLayer(layer)
|
||||
|
||||
if (this._config.imageAlongWay) {
|
||||
this.addSymbolLayer(this._layername, this._config.imageAlongWay)
|
||||
|
@ -397,8 +393,8 @@ class LineRenderingLayer {
|
|||
layout: {},
|
||||
paint: {
|
||||
"fill-color": ["feature-state", "fillColor"],
|
||||
"fill-opacity": ["feature-state", "fillColor-opacity"],
|
||||
},
|
||||
"fill-opacity": ["feature-state", "fillColor-opacity"]
|
||||
}
|
||||
})
|
||||
if (this._onClick) {
|
||||
map.on("click", polylayer, (e) => {
|
||||
|
@ -429,7 +425,7 @@ class LineRenderingLayer {
|
|||
this.currentSourceData = features
|
||||
src.setData({
|
||||
type: "FeatureCollection",
|
||||
features: this.currentSourceData,
|
||||
features: this.currentSourceData
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -513,14 +509,14 @@ export default class ShowDataLayer {
|
|||
layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)),
|
||||
features,
|
||||
{
|
||||
constructStore: (features, layer) => new SimpleFeatureSource(layer, features),
|
||||
constructStore: (features, layer) => new SimpleFeatureSource(layer, features)
|
||||
}
|
||||
)
|
||||
perLayer.forEach((fs) => {
|
||||
new ShowDataLayer(mlmap, {
|
||||
layer: fs.layer.layerDef,
|
||||
features: fs,
|
||||
...(options ?? {}),
|
||||
...(options ?? {})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -533,11 +529,12 @@ export default class ShowDataLayer {
|
|||
return new ShowDataLayer(map, {
|
||||
layer: ShowDataLayer.rangeLayer,
|
||||
features,
|
||||
doShowLayer,
|
||||
doShowLayer
|
||||
})
|
||||
}
|
||||
|
||||
public destruct() {}
|
||||
public destruct() {
|
||||
}
|
||||
|
||||
private zoomToCurrentFeatures(map: MlMap) {
|
||||
if (this._options.zoomToFeatures) {
|
||||
|
@ -546,21 +543,21 @@ export default class ShowDataLayer {
|
|||
map.resize()
|
||||
map.fitBounds(bbox.toLngLat(), {
|
||||
padding: { top: 10, bottom: 10, left: 10, right: 10 },
|
||||
animate: false,
|
||||
animate: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private initDrawFeatures(map: MlMap) {
|
||||
let { features, doShowLayer, fetchStore, selectedElement, selectedLayer } = this._options
|
||||
const onClick =
|
||||
this._options.onClick ??
|
||||
(this._options.layer.title === undefined
|
||||
let { features, doShowLayer, fetchStore, selectedElement } = this._options
|
||||
let onClick = this._options.onClick
|
||||
if (!onClick && selectedElement) {
|
||||
onClick = (this._options.layer.title === undefined
|
||||
? undefined
|
||||
: (feature: Feature) => {
|
||||
selectedElement?.setData(feature)
|
||||
selectedLayer?.setData(this._options.layer)
|
||||
})
|
||||
selectedElement?.setData(feature)
|
||||
})
|
||||
}
|
||||
if (this._options.drawLines !== false) {
|
||||
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
|
||||
const lineRenderingConfig = this._options.layer.lineRendering[i]
|
||||
|
|
29
src/UI/Map/SmallZoomButtons.svelte
Normal file
29
src/UI/Map/SmallZoomButtons.svelte
Normal file
|
@ -0,0 +1,29 @@
|
|||
<script lang="ts">
|
||||
import Translations from "../i18n/Translations.js";
|
||||
import Min from "../../assets/svg/Min.svelte";
|
||||
import MapControlButton from "../Base/MapControlButton.svelte";
|
||||
import Plus from "../../assets/svg/Plus.svelte";
|
||||
import type { MapProperties } from "../../Models/MapProperties"
|
||||
|
||||
export let adaptor: MapProperties
|
||||
let canZoomIn = adaptor.maxzoom.map(mz => adaptor.zoom.data < mz, [adaptor.zoom] )
|
||||
let canZoomOut = adaptor.minzoom.map(mz => adaptor.zoom.data > mz, [adaptor.zoom] )
|
||||
</script>
|
||||
<div class="absolute bottom-0 right-0 pointer-events-none flex flex-col">
|
||||
<MapControlButton
|
||||
enabled={canZoomIn}
|
||||
cls="m-0.5 p-1"
|
||||
arialabel={Translations.t.general.labels.zoomIn}
|
||||
on:click={() => adaptor.zoom.update((z) => z + 1)}
|
||||
>
|
||||
<Plus class="h-5 w-5" />
|
||||
</MapControlButton>
|
||||
<MapControlButton
|
||||
enabled={canZoomOut}
|
||||
cls={"m-0.5 p-1"}
|
||||
arialabel={Translations.t.general.labels.zoomOut}
|
||||
on:click={() => adaptor.zoom.update((z) => z - 1)}
|
||||
>
|
||||
<Min class="h-5 w-5" />
|
||||
</MapControlButton>
|
||||
</div>
|
|
@ -6,14 +6,30 @@
|
|||
|
||||
let isLoading = false
|
||||
export let map: UIEventSource<MlMap>
|
||||
/**
|
||||
* Optional. Only used for the 'global' change indicator so that it won't spin on pan/zoom but only when a change _actually_ occured
|
||||
*/
|
||||
export let rasterLayer: UIEventSource<any> = undefined
|
||||
|
||||
let didChange = undefined
|
||||
onDestroy(rasterLayer?.addCallback(() => {
|
||||
didChange = true
|
||||
}) ??( () => {}))
|
||||
|
||||
onDestroy(Stores.Chronic(250).addCallback(
|
||||
() => {
|
||||
isLoading = !map.data?.isStyleLoaded()
|
||||
const mapIsLoading = !map.data?.isStyleLoaded()
|
||||
isLoading = mapIsLoading && (didChange || rasterLayer === undefined)
|
||||
if(didChange && !mapIsLoading){
|
||||
didChange = false
|
||||
}
|
||||
},
|
||||
))
|
||||
</script>
|
||||
|
||||
|
||||
{#if isLoading}
|
||||
<Loading />
|
||||
<Loading cls="h-6 w-6" />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue