forked from MapComplete/MapComplete
refactoring: more state splitting, basic layoutFeatureSource
This commit is contained in:
parent
8e2f04c0d0
commit
b94a8f5745
54 changed files with 1067 additions and 1969 deletions
|
@ -7,7 +7,6 @@ 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"
|
||||
|
||||
/**
|
||||
* The 'MapLibreAdaptor' bridges 'MapLibre' with the various properties of the `MapProperties`
|
||||
|
@ -51,7 +50,7 @@ export class MapLibreAdaptor implements MapProperties {
|
|||
})
|
||||
this.maxbounds = state?.maxbounds ?? new UIEventSource(undefined)
|
||||
this.allowMoving = state?.allowMoving ?? new UIEventSource(true)
|
||||
this._bounds = new UIEventSource(BBox.global)
|
||||
this._bounds = new UIEventSource(undefined)
|
||||
this.bounds = this._bounds
|
||||
this.rasterLayer =
|
||||
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
|
||||
|
@ -75,6 +74,12 @@ export class MapLibreAdaptor implements MapProperties {
|
|||
dt.lat = map.getCenter().lat
|
||||
this.location.ping()
|
||||
this.zoom.setData(Math.round(map.getZoom() * 10) / 10)
|
||||
const bounds = map.getBounds()
|
||||
const bbox = new BBox([
|
||||
[bounds.getEast(), bounds.getNorth()],
|
||||
[bounds.getWest(), bounds.getSouth()],
|
||||
])
|
||||
self._bounds.setData(bbox)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
||||
import type { Map as MlMap } from "maplibre-gl"
|
||||
import { Marker } from "maplibre-gl"
|
||||
import { GeoJSONSource, Marker } from "maplibre-gl"
|
||||
import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
|
||||
import { GeoOperations } from "../../Logic/GeoOperations"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
@ -19,7 +19,7 @@ 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 _onClick: (feature: Feature) => void
|
||||
private readonly _allMarkers: Map<string, Marker> = new Map<string, Marker>()
|
||||
|
||||
constructor(
|
||||
|
@ -28,7 +28,7 @@ class PointRenderingLayer {
|
|||
config: PointRenderingConfig,
|
||||
visibility?: Store<boolean>,
|
||||
fetchStore?: (id: string) => Store<OsmTags>,
|
||||
onClick?: (id: string) => void
|
||||
onClick?: (feature: Feature) => void
|
||||
) {
|
||||
this._config = config
|
||||
this._map = map
|
||||
|
@ -109,7 +109,7 @@ class PointRenderingLayer {
|
|||
if (this._onClick) {
|
||||
const self = this
|
||||
el.addEventListener("click", function () {
|
||||
self._onClick(feature.properties.id)
|
||||
self._onClick(feature)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ class LineRenderingLayer {
|
|||
private readonly _config: LineRenderingConfig
|
||||
private readonly _visibility?: Store<boolean>
|
||||
private readonly _fetchStore?: (id: string) => Store<OsmTags>
|
||||
private readonly _onClick?: (id: string) => void
|
||||
private readonly _onClick?: (feature: Feature) => void
|
||||
private readonly _layername: string
|
||||
private readonly _listenerInstalledOn: Set<string> = new Set<string>()
|
||||
|
||||
|
@ -155,7 +155,7 @@ class LineRenderingLayer {
|
|||
config: LineRenderingConfig,
|
||||
visibility?: Store<boolean>,
|
||||
fetchStore?: (id: string) => Store<OsmTags>,
|
||||
onClick?: (id: string) => void
|
||||
onClick?: (feature: Feature) => void
|
||||
) {
|
||||
this._layername = layername
|
||||
this._map = map
|
||||
|
@ -174,20 +174,17 @@ class LineRenderingLayer {
|
|||
const config = this._config
|
||||
|
||||
for (const key of LineRenderingLayer.lineConfigKeys) {
|
||||
const v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
calculatedProps[key] = v
|
||||
calculatedProps[key] = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
}
|
||||
for (const key of LineRenderingLayer.lineConfigKeysColor) {
|
||||
let v = config[key]?.GetRenderValue(properties)?.Subs(properties).txt
|
||||
if (v === undefined) {
|
||||
continue
|
||||
}
|
||||
console.log("Color", v)
|
||||
if (v.length == 9 && v.startsWith("#")) {
|
||||
// This includes opacity
|
||||
calculatedProps[key + "-opacity"] = parseInt(v.substring(7), 16) / 256
|
||||
v = v.substring(0, 7)
|
||||
console.log("Color >", v, calculatedProps[key + "-opacity"])
|
||||
}
|
||||
calculatedProps[key] = v
|
||||
}
|
||||
|
@ -196,7 +193,6 @@ class LineRenderingLayer {
|
|||
calculatedProps[key] = Number(v)
|
||||
}
|
||||
|
||||
console.log("Calculated props:", calculatedProps, "for", properties.id)
|
||||
return calculatedProps
|
||||
}
|
||||
|
||||
|
@ -205,52 +201,53 @@ class LineRenderingLayer {
|
|||
while (!map.isStyleLoaded()) {
|
||||
await Utils.waitFor(100)
|
||||
}
|
||||
map.addSource(this._layername, {
|
||||
type: "geojson",
|
||||
data: {
|
||||
const src = <GeoJSONSource>map.getSource(this._layername)
|
||||
if (src === undefined) {
|
||||
map.addSource(this._layername, {
|
||||
type: "geojson",
|
||||
data: {
|
||||
type: "FeatureCollection",
|
||||
features,
|
||||
},
|
||||
promoteId: "id",
|
||||
})
|
||||
// @ts-ignore
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: this._layername + "_line",
|
||||
type: "line",
|
||||
paint: {
|
||||
"line-color": ["feature-state", "color"],
|
||||
"line-opacity": ["feature-state", "color-opacity"],
|
||||
"line-width": ["feature-state", "width"],
|
||||
"line-offset": ["feature-state", "offset"],
|
||||
},
|
||||
layout: {
|
||||
"line-cap": "round",
|
||||
},
|
||||
})
|
||||
|
||||
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"],
|
||||
"fill-opacity": 0.1,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
src.setData({
|
||||
type: "FeatureCollection",
|
||||
features,
|
||||
},
|
||||
promoteId: "id",
|
||||
})
|
||||
|
||||
map.addLayer({
|
||||
source: this._layername,
|
||||
id: this._layername + "_line",
|
||||
type: "line",
|
||||
paint: {
|
||||
"line-color": ["feature-state", "color"],
|
||||
"line-opacity": ["feature-state", "color-opacity"],
|
||||
"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"],
|
||||
"fill-opacity": 0.1,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
const feature = features[i]
|
||||
const id = feature.properties.id ?? feature.id
|
||||
console.log("ID is", id)
|
||||
if (id === undefined) {
|
||||
console.trace(
|
||||
"Got a feature without ID; this causes rendering bugs:",
|
||||
|
@ -310,23 +307,6 @@ export default class ShowDataLayer {
|
|||
})
|
||||
}
|
||||
|
||||
private openOrReusePopup(id: string): void {
|
||||
if (!this._popupCache || !this._options.fetchStore) {
|
||||
return
|
||||
}
|
||||
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
|
||||
|
@ -338,8 +318,8 @@ export default class ShowDataLayer {
|
|||
}
|
||||
|
||||
private initDrawFeatures(map: MlMap) {
|
||||
const { features, doShowLayer, fetchStore, buildPopup } = this._options
|
||||
const onClick = buildPopup === undefined ? undefined : (id) => this.openOrReusePopup(id)
|
||||
const { features, doShowLayer, fetchStore, selectedElement } = this._options
|
||||
const onClick = (feature: Feature) => selectedElement?.setData(feature)
|
||||
for (let i = 0; i < this._options.layer.lineRendering.length; i++) {
|
||||
const lineRenderingConfig = this._options.layer.lineRendering[i]
|
||||
new LineRenderingLayer(
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
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 {
|
||||
|
@ -11,15 +8,10 @@ export interface ShowDataLayerOptions {
|
|||
*/
|
||||
features: FeatureSource
|
||||
/**
|
||||
* Indication of the current selected element; overrides some filters
|
||||
* Indication of the current selected element; overrides some filters.
|
||||
* When a feature is tapped, the feature will be put in there
|
||||
*/
|
||||
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
|
||||
|
@ -31,7 +23,8 @@ export interface ShowDataLayerOptions {
|
|||
doShowLayer?: Store<true | boolean>
|
||||
|
||||
/**
|
||||
* Function which fetches the relevant store
|
||||
* Function which fetches the relevant store.
|
||||
* If given, the map will update when a property is changed
|
||||
*/
|
||||
fetchStore?: (id: string) => UIEventSource<OsmTags>
|
||||
}
|
||||
|
|
|
@ -1,24 +1,35 @@
|
|||
/**
|
||||
* SHows geojson on the given leaflet map, but attempts to figure out the correct layer first
|
||||
*/
|
||||
import { Store } from "../../Logic/UIEventSource"
|
||||
import { ImmutableStore, 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"
|
||||
import FilteringFeatureSource from "../../Logic/FeatureSource/Sources/FilteringFeatureSource"
|
||||
import { GlobalFilter } from "../../Models/GlobalFilter"
|
||||
|
||||
export default class ShowDataMultiLayer {
|
||||
constructor(
|
||||
map: Store<MlMap>,
|
||||
options: ShowDataLayerOptions & { layers: Store<FilteredLayer[]> }
|
||||
options: ShowDataLayerOptions & {
|
||||
layers: FilteredLayer[]
|
||||
globalFilters?: Store<GlobalFilter[]>
|
||||
}
|
||||
) {
|
||||
new PerLayerFeatureSourceSplitter(
|
||||
options.layers,
|
||||
(perLayer) => {
|
||||
new ImmutableStore(options.layers),
|
||||
(features, layer) => {
|
||||
const newOptions = {
|
||||
...options,
|
||||
layer: perLayer.layer.layerDef,
|
||||
features: perLayer,
|
||||
layer: layer.layerDef,
|
||||
features: new FilteringFeatureSource(
|
||||
layer,
|
||||
features,
|
||||
options.fetchStore,
|
||||
options.globalFilters
|
||||
),
|
||||
}
|
||||
new ShowDataLayer(map, newOptions)
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue