UX: fix zooming of minimaps, fix #1892

This commit is contained in:
Pieter Vander Vennet 2024-04-24 00:28:29 +02:00
parent 3968a1e841
commit e9b7df6d2d
2 changed files with 62 additions and 51 deletions

View file

@ -154,7 +154,7 @@ class PointRenderingLayer {
if (this._onClick) { if (this._onClick) {
const self = this const self = this
el.addEventListener("click", function (ev) { el.addEventListener("click", function(ev) {
ev.preventDefault() ev.preventDefault()
self._onClick(feature) self._onClick(feature)
// Workaround to signal the MapLibreAdaptor to ignore this click // Workaround to signal the MapLibreAdaptor to ignore this click
@ -200,7 +200,7 @@ class LineRenderingLayer {
"lineCap", "lineCap",
"offset", "offset",
"fill", "fill",
"fillColor", "fillColor"
] as const ] as const
private static readonly lineConfigKeysColor = ["color", "fillColor"] as const private static readonly lineConfigKeysColor = ["color", "fillColor"] as const
@ -264,8 +264,8 @@ class LineRenderingLayer {
"icon-rotation-alignment": "map", "icon-rotation-alignment": "map",
"icon-pitch-alignment": "map", "icon-pitch-alignment": "map",
"icon-image": imgId, "icon-image": imgId,
"icon-size": 0.055, "icon-size": 0.055
}, }
} }
const filter = img.if?.asMapboxExpression() const filter = img.if?.asMapboxExpression()
if (filter) { if (filter) {
@ -338,9 +338,9 @@ class LineRenderingLayer {
type: "geojson", type: "geojson",
data: { data: {
type: "FeatureCollection", type: "FeatureCollection",
features, features
}, },
promoteId: "id", promoteId: "id"
}) })
const linelayer = this._layername + "_line" const linelayer = this._layername + "_line"
const layer: AddLayerObject = { const layer: AddLayerObject = {
@ -351,11 +351,11 @@ class LineRenderingLayer {
"line-color": ["feature-state", "color"], "line-color": ["feature-state", "color"],
"line-opacity": ["feature-state", "color-opacity"], "line-opacity": ["feature-state", "color-opacity"],
"line-width": ["feature-state", "width"], "line-width": ["feature-state", "width"],
"line-offset": ["feature-state", "offset"], "line-offset": ["feature-state", "offset"]
}, },
layout: { layout: {
"line-cap": "round", "line-cap": "round"
}, }
} }
if (this._config.dashArray) { if (this._config.dashArray) {
layer.paint["line-dasharray"] = layer.paint["line-dasharray"] =
@ -393,8 +393,8 @@ class LineRenderingLayer {
layout: {}, layout: {},
paint: { paint: {
"fill-color": ["feature-state", "fillColor"], "fill-color": ["feature-state", "fillColor"],
"fill-opacity": ["feature-state", "fillColor-opacity"], "fill-opacity": ["feature-state", "fillColor-opacity"]
}, }
}) })
if (this._onClick) { if (this._onClick) {
map.on("click", polylayer, (e) => { map.on("click", polylayer, (e) => {
@ -425,7 +425,7 @@ class LineRenderingLayer {
this.currentSourceData = features this.currentSourceData = features
src.setData({ src.setData({
type: "FeatureCollection", type: "FeatureCollection",
features: this.currentSourceData, features: this.currentSourceData
}) })
} }
} }
@ -494,8 +494,7 @@ export default class ShowDataLayer {
} }
) { ) {
this._options = options this._options = options
const self = this this.onDestroy.push(map.addCallbackAndRunD((map) => this.initDrawFeatures(map)))
this.onDestroy.push(map.addCallbackAndRunD((map) => self.initDrawFeatures(map)))
} }
public static showMultipleLayers( public static showMultipleLayers(
@ -509,14 +508,24 @@ export default class ShowDataLayer {
layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)), layers.filter((l) => l.source !== null).map((l) => new FilteredLayer(l)),
features, features,
{ {
constructStore: (features, layer) => new SimpleFeatureSource(layer, features), constructStore: (features, layer) => new SimpleFeatureSource(layer, features)
} }
) )
if (options?.zoomToFeatures) {
options.zoomToFeatures = false
features.features.addCallbackD(features => {
ShowDataLayer.zoomToCurrentFeatures(mlmap.data, features)
})
mlmap.addCallbackD(map => {
ShowDataLayer.zoomToCurrentFeatures(map, features.features.data)
})
}
perLayer.forEach((fs) => { perLayer.forEach((fs) => {
new ShowDataLayer(mlmap, { new ShowDataLayer(mlmap, {
layer: fs.layer.layerDef, layer: fs.layer.layerDef,
features: fs, features: fs,
...(options ?? {}), ...(options ?? {})
}) })
}) })
} }
@ -529,26 +538,31 @@ export default class ShowDataLayer {
return new ShowDataLayer(map, { return new ShowDataLayer(map, {
layer: ShowDataLayer.rangeLayer, layer: ShowDataLayer.rangeLayer,
features, features,
doShowLayer, doShowLayer
}) })
} }
public destruct() {} public destruct() {
}
private zoomToCurrentFeatures(map: MlMap) { private static zoomToCurrentFeatures(map: MlMap, features: Feature[]) {
if (this._options.zoomToFeatures) { if (!features || !map || features.length == 0) {
const features = this._options.features.features.data return
}
const bbox = BBox.bboxAroundAll(features.map(BBox.get)) const bbox = BBox.bboxAroundAll(features.map(BBox.get))
console.log("Zooming to features", bbox.asGeoJson())
window.requestAnimationFrame(() => {
map.resize() map.resize()
map.fitBounds(bbox.toLngLat(), { map.fitBounds(bbox.toLngLat(), {
padding: { top: 10, bottom: 10, left: 10, right: 10 }, padding: { top: 10, bottom: 10, left: 10, right: 10 },
animate: false, animate: false
})
}) })
}
} }
private initDrawFeatures(map: MlMap) { private initDrawFeatures(map: MlMap) {
let { features, doShowLayer, fetchStore, selectedElement } = this._options const { features, doShowLayer, fetchStore, selectedElement } = this._options
let onClick = this._options.onClick let onClick = this._options.onClick
if (!onClick && selectedElement) { if (!onClick && selectedElement) {
onClick = onClick =
@ -587,6 +601,8 @@ export default class ShowDataLayer {
) )
} }
} }
features.features.addCallbackAndRunD((_) => this.zoomToCurrentFeatures(map)) if (this._options.zoomToFeatures) {
features.features.addCallbackAndRunD((features) => ShowDataLayer.zoomToCurrentFeatures(map, features))
}
} }
} }

View file

@ -17,13 +17,13 @@ export class MinimapViz implements SpecialVisualization {
{ {
doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close", doc: "The (maximum) zoomlevel: the target zoomlevel after fitting the entire feature. The minimap will fit the entire feature, then zoom out to this zoom level. The higher, the more zoomed in with 1 being the entire world and 19 being really close",
name: "zoomlevel", name: "zoomlevel",
defaultValue: "18", defaultValue: "18"
}, },
{ {
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)", doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap. (Note: if the key is 'id', list interpration is disabled)",
name: "idKey", name: "idKey",
defaultValue: "id", defaultValue: "id"
}, }
] ]
example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`" example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`"
@ -78,41 +78,36 @@ export class MinimapViz implements SpecialVisualization {
) )
const mlmap = new UIEventSource(undefined) const mlmap = new UIEventSource(undefined)
const [lon, lat] = GeoOperations.centerpointCoordinates(feature)
const mla = new MapLibreAdaptor(mlmap, { const mla = new MapLibreAdaptor(mlmap, {
rasterLayer: state.mapProperties.rasterLayer, rasterLayer: state.mapProperties.rasterLayer,
zoom: new UIEventSource<number>(18)
}) })
mla.maxzoom.setData(17) mla.allowMoving.setData(false)
let zoom = 18 mla.allowZooming.setData(false)
mla.location.setData({lon, lat})
if (args[0]) { if (args[0]) {
const parsed = Number(args[0]) const parsed = Number(args[0])
if (!isNaN(parsed) && parsed > 0 && parsed < 25) { if (!isNaN(parsed) && parsed > 0 && parsed < 25) {
zoom = parsed mla.zoom.setData(parsed)
} }
} }
featuresToShow.addCallbackAndRunD((features) => { mlmap.addCallbackAndRun(map => console.log("Map for minimap vis is now", map))
if (features.length === 0) {
return
}
const bboxGeojson = GeoOperations.bbox({ features, type: "FeatureCollection" })
const [lon, lat] = GeoOperations.centerpointCoordinates(bboxGeojson)
mla.bounds.setData(BBox.get(bboxGeojson))
mla.location.setData({ lon, lat })
})
mla.zoom.setData(zoom)
mla.allowMoving.setData(false)
mla.allowZooming.setData(false)
ShowDataLayer.showMultipleLayers( ShowDataLayer.showMultipleLayers(
mlmap, mlmap,
new StaticFeatureSource(featuresToShow), new StaticFeatureSource(featuresToShow),
state.layout.layers state.layout.layers,
{zoomToFeatures: true}
) )
return new SvelteUIElement(MaplibreMap, { return new SvelteUIElement(MaplibreMap, {
interactive: false, interactive: false,
map: mlmap, map: mlmap,
mapProperties: mla, mapProperties: mla
}) })
.SetClass("h-40 rounded") .SetClass("h-40 rounded")
.SetStyle("overflow: hidden; pointer-events: none;") .SetStyle("overflow: hidden; pointer-events: none;")