forked from MapComplete/MapComplete
UX: fix zooming of minimaps, fix #1892
This commit is contained in:
parent
3968a1e841
commit
e9b7df6d2d
2 changed files with 62 additions and 51 deletions
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue