forked from MapComplete/MapComplete
refactoring(maplibre): WIP
This commit is contained in:
parent
231d67361e
commit
4d48b1cf2b
89 changed files with 1166 additions and 3973 deletions
|
@ -1,34 +1,56 @@
|
|||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { Map as MLMap } from "maplibre-gl"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
import { RasterLayerPolygon, RasterLayerProperties } from "../../Models/RasterLayers"
|
||||
import { Utils } from "../../Utils"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
import { MapProperties } from "../../Models/MapProperties"
|
||||
import SvelteUIElement from "../Base/SvelteUIElement"
|
||||
import MaplibreMap from "./MaplibreMap.svelte"
|
||||
import Constants from "../../Models/Constants"
|
||||
|
||||
export interface MapState {
|
||||
/**
|
||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||
*/
|
||||
export class MapLibreAdaptor implements MapProperties {
|
||||
private static maplibre_control_handlers = [
|
||||
"scrollZoom",
|
||||
"boxZoom",
|
||||
"dragRotate",
|
||||
"dragPan",
|
||||
"keyboard",
|
||||
"doubleClickZoom",
|
||||
"touchZoomRotate",
|
||||
]
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
readonly zoom: UIEventSource<number>
|
||||
readonly bounds: Store<BBox>
|
||||
readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined>
|
||||
}
|
||||
export class MapLibreAdaptor implements MapState {
|
||||
readonly maxbounds: UIEventSource<BBox | undefined>
|
||||
readonly allowMoving: UIEventSource<true | boolean | undefined>
|
||||
private readonly _maplibreMap: Store<MLMap>
|
||||
|
||||
readonly location: UIEventSource<{ lon: number; lat: number }>
|
||||
readonly zoom: UIEventSource<number>
|
||||
readonly bounds: Store<BBox>
|
||||
readonly rasterLayer: UIEventSource<RasterLayerPolygon | undefined>
|
||||
private readonly _bounds: UIEventSource<BBox>
|
||||
|
||||
/**
|
||||
* Used for internal bookkeeping (to remove a rasterLayer when done loading)
|
||||
* @private
|
||||
*/
|
||||
private _currentRasterLayer: string
|
||||
constructor(maplibreMap: Store<MLMap>, state?: Partial<Omit<MapState, "bounds">>) {
|
||||
|
||||
constructor(maplibreMap: Store<MLMap>, state?: Partial<Omit<MapProperties, "bounds">>) {
|
||||
this._maplibreMap = maplibreMap
|
||||
|
||||
this.location = state?.location ?? new UIEventSource({ lon: 0, lat: 0 })
|
||||
this.zoom = state?.zoom ?? new UIEventSource(1)
|
||||
this.zoom.addCallbackAndRunD((z) => {
|
||||
if (z < 0) {
|
||||
this.zoom.setData(0)
|
||||
}
|
||||
if (z > 24) {
|
||||
this.zoom.setData(24)
|
||||
}
|
||||
})
|
||||
this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined)
|
||||
this.allowMoving = state?.allowMoving ?? new UIEventSource(true)
|
||||
this._bounds = new UIEventSource(BBox.global)
|
||||
this.bounds = this._bounds
|
||||
this.rasterLayer =
|
||||
|
@ -38,20 +60,26 @@ export class MapLibreAdaptor implements MapState {
|
|||
maplibreMap.addCallbackAndRunD((map) => {
|
||||
map.on("load", () => {
|
||||
self.setBackground()
|
||||
self.MoveMapToCurrentLoc(self.location.data)
|
||||
self.SetZoom(self.zoom.data)
|
||||
self.setMaxBounds(self.maxbounds.data)
|
||||
self.setAllowMoving(self.allowMoving.data)
|
||||
})
|
||||
self.MoveMapToCurrentLoc(this.location.data)
|
||||
self.SetZoom(this.zoom.data)
|
||||
self.MoveMapToCurrentLoc(self.location.data)
|
||||
self.SetZoom(self.zoom.data)
|
||||
self.setMaxBounds(self.maxbounds.data)
|
||||
self.setAllowMoving(self.allowMoving.data)
|
||||
map.on("moveend", () => {
|
||||
const dt = this.location.data
|
||||
dt.lon = map.getCenter().lng
|
||||
dt.lat = map.getCenter().lat
|
||||
this.location.ping()
|
||||
this.zoom.setData(map.getZoom())
|
||||
this.zoom.setData(Math.round(map.getZoom() * 10) / 10)
|
||||
})
|
||||
})
|
||||
|
||||
this.rasterLayer.addCallback((_) =>
|
||||
self.setBackground().catch((e) => {
|
||||
self.setBackground().catch((_) => {
|
||||
console.error("Could not set background")
|
||||
})
|
||||
)
|
||||
|
@ -60,25 +88,25 @@ export class MapLibreAdaptor implements MapState {
|
|||
self.MoveMapToCurrentLoc(loc)
|
||||
})
|
||||
this.zoom.addCallbackAndRunD((z) => self.SetZoom(z))
|
||||
this.maxbounds.addCallbackAndRun((bbox) => self.setMaxBounds(bbox))
|
||||
this.allowMoving.addCallbackAndRun((allowMoving) => self.setAllowMoving(allowMoving))
|
||||
}
|
||||
private SetZoom(z: number) {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined || z === undefined) {
|
||||
return
|
||||
}
|
||||
if (map.getZoom() !== z) {
|
||||
map.setZoom(z)
|
||||
}
|
||||
}
|
||||
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined || loc === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const center = map.getCenter()
|
||||
if (center.lng !== loc.lon || center.lat !== loc.lat) {
|
||||
map.setCenter({ lng: loc.lon, lat: loc.lat })
|
||||
/**
|
||||
* Convenience constructor
|
||||
*/
|
||||
public static construct(): {
|
||||
map: Store<MLMap>
|
||||
ui: SvelteUIElement
|
||||
mapproperties: MapProperties
|
||||
} {
|
||||
const mlmap = new UIEventSource<MlMap>(undefined)
|
||||
return {
|
||||
map: mlmap,
|
||||
ui: new SvelteUIElement(MaplibreMap, {
|
||||
map: mlmap,
|
||||
}),
|
||||
mapproperties: new MapLibreAdaptor(mlmap),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +131,6 @@ export class MapLibreAdaptor implements MapState {
|
|||
|
||||
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)
|
||||
|
@ -112,6 +139,28 @@ export class MapLibreAdaptor implements MapState {
|
|||
return url
|
||||
}
|
||||
|
||||
private SetZoom(z: number) {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map || z === undefined) {
|
||||
return
|
||||
}
|
||||
if (Math.abs(map.getZoom() - z) > 0.01) {
|
||||
map.setZoom(z)
|
||||
}
|
||||
}
|
||||
|
||||
private MoveMapToCurrentLoc(loc: { lat: number; lon: number }) {
|
||||
const map = this._maplibreMap.data
|
||||
if (!map || loc === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const center = map.getCenter()
|
||||
if (center.lng !== loc.lon || center.lat !== loc.lat) {
|
||||
map.setCenter({ lng: loc.lon, lat: loc.lat })
|
||||
}
|
||||
}
|
||||
|
||||
private async awaitStyleIsLoaded(): Promise<void> {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined) {
|
||||
|
@ -125,7 +174,6 @@ export class MapLibreAdaptor implements MapState {
|
|||
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)
|
||||
}
|
||||
|
@ -185,4 +233,32 @@ export class MapLibreAdaptor implements MapState {
|
|||
this.removeCurrentLayer(map)
|
||||
this._currentRasterLayer = background?.id
|
||||
}
|
||||
|
||||
private setMaxBounds(bbox: undefined | BBox) {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined) {
|
||||
return
|
||||
}
|
||||
if (bbox) {
|
||||
map.setMaxBounds(bbox.toLngLat())
|
||||
} else {
|
||||
map.setMaxBounds(null)
|
||||
}
|
||||
}
|
||||
|
||||
private setAllowMoving(allow: true | boolean | undefined) {
|
||||
const map = this._maplibreMap.data
|
||||
if (map === undefined) {
|
||||
return
|
||||
}
|
||||
if (allow === false) {
|
||||
for (const id of MapLibreAdaptor.maplibre_control_handlers) {
|
||||
map[id].disable()
|
||||
}
|
||||
} else {
|
||||
for (const id of MapLibreAdaptor.maplibre_control_handlers) {
|
||||
map[id].enable()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
import { Map } from "@onsvisual/svelte-maps";
|
||||
import type { Map as MaplibreMap } from "maplibre-gl";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type Loc from "../../Models/Loc";
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
|
||||
|
||||
/**
|
||||
|
@ -30,7 +28,6 @@
|
|||
<main>
|
||||
<Map bind:center={center}
|
||||
bind:map={$map}
|
||||
controls="true"
|
||||
id="map" location={{lng: 0, lat: 0, zoom: 0}} maxzoom=24 style={styleUrl} />
|
||||
</main>
|
||||
|
||||
|
|
|
@ -1,108 +1,294 @@
|
|||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import type { Map as MlMap } from "maplibre-gl"
|
||||
import { Marker } from "maplibre-gl"
|
||||
import { ShowDataLayerOptions } from "../ShowDataLayer/ShowDataLayerOptions"
|
||||
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import PointRenderingConfig from "../../Models/ThemeConfig/PointRenderingConfig"
|
||||
import { OsmFeature, OsmTags } from "../../Models/OsmFeature"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource"
|
||||
import { BBox } from "../../Logic/BBox"
|
||||
|
||||
import { Feature, LineString } from "geojson"
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
||||
import LineRenderingConfig from "../../Models/ThemeConfig/LineRenderingConfig"
|
||||
import { Utils } from "../../Utils"
|
||||
import * as range_layer from "../../assets/layers/range/range.json"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
class PointRenderingLayer {
|
||||
private readonly _config: PointRenderingConfig
|
||||
private readonly _fetchStore?: (id: string) => Store<OsmTags>
|
||||
private readonly _map: MlMap
|
||||
private readonly _onClick: (id: string) => void
|
||||
private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>()
|
||||
|
||||
constructor(
|
||||
map: MlMap,
|
||||
features: FeatureSource,
|
||||
config: PointRenderingConfig,
|
||||
fetchStore?: (id: string) => Store<OsmTags>
|
||||
visibility?: Store<boolean>,
|
||||
fetchStore?: (id: string) => Store<OsmTags>,
|
||||
onClick?: (id: string) => void
|
||||
) {
|
||||
this._config = config
|
||||
this._map = map
|
||||
this._fetchStore = fetchStore
|
||||
const cache: Map<string, Marker> = new Map<string, Marker>()
|
||||
this._onClick = onClick
|
||||
const self = this
|
||||
features.features.addCallbackAndRunD((features) => {
|
||||
const unseenKeys = new Set(cache.keys())
|
||||
for (const { feature } of features) {
|
||||
const id = feature.properties.id
|
||||
|
||||
features.features.addCallbackAndRunD((features) => self.updateFeatures(features))
|
||||
visibility?.addCallbackAndRunD((visible) => self.setVisibility(visible))
|
||||
}
|
||||
|
||||
private updateFeatures(features: Feature[]) {
|
||||
const cache = this._allMarkers
|
||||
const unseenKeys = new Set(cache.keys())
|
||||
for (const location of this._config.location) {
|
||||
for (const feature of features) {
|
||||
const loc = GeoOperations.featureToCoordinateWithRenderingType(
|
||||
<any>feature,
|
||||
location
|
||||
)
|
||||
if (loc === undefined) {
|
||||
continue
|
||||
}
|
||||
const id = feature.properties.id + "-" + location
|
||||
unseenKeys.delete(id)
|
||||
const loc = GeoOperations.centerpointCoordinates(feature)
|
||||
|
||||
if (cache.has(id)) {
|
||||
console.log("Not creating a marker for ", id)
|
||||
const cached = cache.get(id)
|
||||
const oldLoc = cached.getLngLat()
|
||||
console.log("OldLoc vs newLoc", oldLoc, loc)
|
||||
if (loc[0] !== oldLoc.lng && loc[1] !== oldLoc.lat) {
|
||||
cached.setLngLat(loc)
|
||||
console.log("MOVED")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
console.log("Creating a marker for ", id)
|
||||
const marker = self.addPoint(feature)
|
||||
const marker = this.addPoint(feature, loc)
|
||||
cache.set(id, marker)
|
||||
}
|
||||
}
|
||||
|
||||
for (const unseenKey of unseenKeys) {
|
||||
cache.get(unseenKey).remove()
|
||||
cache.delete(unseenKey)
|
||||
}
|
||||
})
|
||||
for (const unseenKey of unseenKeys) {
|
||||
cache.get(unseenKey).remove()
|
||||
cache.delete(unseenKey)
|
||||
}
|
||||
}
|
||||
|
||||
private addPoint(feature: OsmFeature): Marker {
|
||||
private setVisibility(visible: boolean) {
|
||||
for (const marker of this._allMarkers.values()) {
|
||||
if (visible) {
|
||||
marker.getElement().classList.remove("hidden")
|
||||
} else {
|
||||
marker.getElement().classList.add("hidden")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private addPoint(feature: Feature, loc: [number, number]): Marker {
|
||||
let store: Store<OsmTags>
|
||||
if (this._fetchStore) {
|
||||
store = this._fetchStore(feature.properties.id)
|
||||
} else {
|
||||
store = new ImmutableStore(feature.properties)
|
||||
store = new ImmutableStore(<OsmTags>feature.properties)
|
||||
}
|
||||
const { html, iconAnchor } = this._config.GenerateLeafletStyle(store, true)
|
||||
const { html, iconAnchor } = this._config.RenderIcon(store, true)
|
||||
html.SetClass("marker")
|
||||
const el = html.ConstructElement()
|
||||
|
||||
el.addEventListener("click", function () {
|
||||
window.alert("Hello world!")
|
||||
})
|
||||
if (this._onClick) {
|
||||
const self = this
|
||||
el.addEventListener("click", function () {
|
||||
self._onClick(feature.properties.id)
|
||||
})
|
||||
}
|
||||
|
||||
return new Marker(el)
|
||||
.setLngLat(GeoOperations.centerpointCoordinates(feature))
|
||||
.setOffset(iconAnchor)
|
||||
.addTo(this._map)
|
||||
return new Marker(el).setLngLat(loc).setOffset(iconAnchor).addTo(this._map)
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowDataLayer {
|
||||
class LineRenderingLayer {
|
||||
/**
|
||||
* These are dynamic properties
|
||||
* @private
|
||||
*/
|
||||
private static readonly lineConfigKeys = [
|
||||
"color",
|
||||
"width",
|
||||
"lineCap",
|
||||
"offset",
|
||||
"fill",
|
||||
"fillColor",
|
||||
]
|
||||
private readonly _map: MlMap
|
||||
private readonly _config: LineRenderingConfig
|
||||
private readonly _visibility?: Store<boolean>
|
||||
private readonly _fetchStore?: (id: string) => Store<OsmTags>
|
||||
private readonly _onClick?: (id: string) => void
|
||||
private readonly _layername: string
|
||||
|
||||
constructor(
|
||||
map: MlMap,
|
||||
features: FeatureSource,
|
||||
layername: string,
|
||||
config: LineRenderingConfig,
|
||||
visibility?: Store<boolean>,
|
||||
fetchStore?: (id: string) => Store<OsmTags>,
|
||||
onClick?: (id: string) => void
|
||||
) {
|
||||
this._layername = layername
|
||||
this._map = map
|
||||
this._config = config
|
||||
this._visibility = visibility
|
||||
this._fetchStore = fetchStore
|
||||
this._onClick = onClick
|
||||
const self = this
|
||||
features.features.addCallbackAndRunD((features) => self.update(features))
|
||||
}
|
||||
|
||||
private async update(features: Feature[]) {
|
||||
const map = this._map
|
||||
while (!map.isStyleLoaded()) {
|
||||
await Utils.waitFor(100)
|
||||
}
|
||||
map.addSource(this._layername, {
|
||||
type: "geojson",
|
||||
data: {
|
||||
type: "FeatureCollection",
|
||||
features,
|
||||
},
|
||||
promoteId: "id",
|
||||
})
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const feature = features[i]
|
||||
const id = feature.properties.id ?? "" + i
|
||||
const tags = this._fetchStore(id)
|
||||
tags.addCallbackAndRunD((properties) => {
|
||||
const config = this._config
|
||||
|
||||
const calculatedProps = {}
|
||||
for (const key of LineRenderingLayer.lineConfigKeys) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
|
||||
map.setFeatureState({ source: this._layername, id }, calculatedProps)
|
||||
})
|
||||
}
|
||||
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: this._layername + "_line",
|
||||
type: "line",
|
||||
filter: ["in", ["geometry-type"], ["literal", ["LineString", "MultiLineString"]]],
|
||||
layout: {},
|
||||
paint: {
|
||||
"line-color": ["feature-state", "color"],
|
||||
"line-width": ["feature-state", "width"],
|
||||
"line-offset": ["feature-state", "offset"],
|
||||
},
|
||||
})
|
||||
|
||||
/*[
|
||||
"color",
|
||||
"width",
|
||||
"dashArray",
|
||||
"lineCap",
|
||||
"offset",
|
||||
"fill",
|
||||
"fillColor",
|
||||
]*/
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: this._layername + "_polygon",
|
||||
type: "fill",
|
||||
filter: ["in", ["geometry-type"], ["literal", ["Polygon", "MultiPolygon"]]],
|
||||
layout: {},
|
||||
paint: {
|
||||
"fill-color": ["feature-state", "fillColor"],
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default class ShowDataLayer {
|
||||
private readonly _map: Store<MlMap>
|
||||
private _options: ShowDataLayerOptions & { layer: LayerConfig }
|
||||
private readonly _options: ShowDataLayerOptions & { layer: LayerConfig }
|
||||
private readonly _popupCache: Map<string, ScrollableFullScreen>
|
||||
|
||||
constructor(map: Store<MlMap>, options: ShowDataLayerOptions & { layer: LayerConfig }) {
|
||||
this._map = map
|
||||
this._options = options
|
||||
this._popupCache = new Map()
|
||||
const self = this
|
||||
map.addCallbackAndRunD((map) => self.initDrawFeatures(map))
|
||||
}
|
||||
|
||||
private initDrawFeatures(map: MlMap) {
|
||||
for (const pointRenderingConfig of this._options.layer.mapRendering) {
|
||||
new PointRenderingLayer(
|
||||
map,
|
||||
this._options.features,
|
||||
pointRenderingConfig,
|
||||
this._options.fetchStore
|
||||
)
|
||||
private static rangeLayer = new LayerConfig(
|
||||
<LayerConfigJson>range_layer,
|
||||
"ShowDataLayer.ts:range.json"
|
||||
)
|
||||
|
||||
public static showRange(
|
||||
map: Store<MlMap>,
|
||||
features: FeatureSource,
|
||||
doShowLayer?: Store<boolean>
|
||||
): ShowDataLayer {
|
||||
return new ShowDataLayer(map, {
|
||||
layer: ShowDataLayer.rangeLayer,
|
||||
features,
|
||||
doShowLayer,
|
||||
})
|
||||
}
|
||||
|
||||
private openOrReusePopup(id: string): void {
|
||||
if (this._popupCache.has(id)) {
|
||||
this._popupCache.get(id).Activate()
|
||||
return
|
||||
}
|
||||
const tags = this._options.fetchStore(id)
|
||||
if (!tags) {
|
||||
return
|
||||
}
|
||||
const popup = this._options.buildPopup(tags, this._options.layer)
|
||||
this._popupCache.set(id, popup)
|
||||
popup.Activate()
|
||||
}
|
||||
|
||||
private zoomToCurrentFeatures(map: MlMap) {
|
||||
if (this._options.zoomToFeatures) {
|
||||
const features = this._options.features.features.data
|
||||
const bbox = BBox.bboxAroundAll(features.map((f) => BBox.get(f.feature)))
|
||||
const bbox = BBox.bboxAroundAll(features.map(BBox.get))
|
||||
map.fitBounds(bbox.toLngLat(), {
|
||||
padding: { top: 10, bottom: 10, left: 10, right: 10 },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private initDrawFeatures(map: MlMap) {
|
||||
const { features, doShowLayer, fetchStore, buildPopup } = this._options
|
||||
const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id)
|
||||
for (const lineRenderingConfig of this._options.layer.lineRendering) {
|
||||
new LineRenderingLayer(
|
||||
map,
|
||||
features,
|
||||
"test",
|
||||
lineRenderingConfig,
|
||||
doShowLayer,
|
||||
fetchStore,
|
||||
onClick
|
||||
)
|
||||
}
|
||||
|
||||
for (const pointRenderingConfig of this._options.layer.mapRendering) {
|
||||
new PointRenderingLayer(
|
||||
map,
|
||||
features,
|
||||
pointRenderingConfig,
|
||||
doShowLayer,
|
||||
fetchStore,
|
||||
onClick
|
||||
)
|
||||
}
|
||||
features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map))
|
||||
}
|
||||
}
|
||||
|
|
37
UI/Map/ShowDataLayerOptions.ts
Normal file
37
UI/Map/ShowDataLayerOptions.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import FeatureSource from "../../Logic/FeatureSource/FeatureSource"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import { ElementStorage } from "../../Logic/ElementStorage"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
|
||||
import { OsmTags } from "../../Models/OsmFeature"
|
||||
|
||||
export interface ShowDataLayerOptions {
|
||||
/**
|
||||
* Features to show
|
||||
*/
|
||||
features: FeatureSource
|
||||
/**
|
||||
* Indication of the current selected element; overrides some filters
|
||||
*/
|
||||
selectedElement?: UIEventSource<any>
|
||||
/**
|
||||
* What popup to build when a feature is selected
|
||||
*/
|
||||
buildPopup?:
|
||||
| undefined
|
||||
| ((tags: UIEventSource<any>, layer: LayerConfig) => ScrollableFullScreen)
|
||||
|
||||
/**
|
||||
* If set, zoom to the features when initially loaded and when they are changed
|
||||
*/
|
||||
zoomToFeatures?: false | boolean
|
||||
/**
|
||||
* Toggles the layer on/off
|
||||
*/
|
||||
doShowLayer?: Store<true | boolean>
|
||||
|
||||
/**
|
||||
* Function which fetches the relevant store
|
||||
*/
|
||||
fetchStore?: (id: string) => UIEventSource<OsmTags>
|
||||
}
|
28
UI/Map/ShowDataMultiLayer.ts
Normal file
28
UI/Map/ShowDataMultiLayer.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* SHows geojson on the given leaflet map, but attempts to figure out the correct layer first
|
||||
*/
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import ShowDataLayer from "./ShowDataLayer"
|
||||
import PerLayerFeatureSourceSplitter from "../../Logic/FeatureSource/PerLayerFeatureSourceSplitter"
|
||||
import FilteredLayer from "../../Models/FilteredLayer"
|
||||
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
|
||||
import { Map as MlMap } from "maplibre-gl"
|
||||
export default class ShowDataMultiLayer {
|
||||
constructor(
|
||||
map: Store<MlMap>,
|
||||
options: ShowDataLayerOptions & { layers: Store<FilteredLayer[]> }
|
||||
) {
|
||||
new PerLayerFeatureSourceSplitter(
|
||||
options.layers,
|
||||
(perLayer) => {
|
||||
const newOptions = {
|
||||
...options,
|
||||
layer: perLayer.layer.layerDef,
|
||||
features: perLayer,
|
||||
}
|
||||
new ShowDataLayer(map, newOptions)
|
||||
},
|
||||
options.features
|
||||
)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue