forked from MapComplete/MapComplete
UX: small maps now follow the rotation of the main map, fix #2433, add compass indicators to most of the minimaps
This commit is contained in:
parent
aed6defa16
commit
376ed60e6e
15 changed files with 71 additions and 33 deletions
|
|
@ -87,6 +87,8 @@ export class Stores {
|
||||||
})
|
})
|
||||||
return newStore
|
return newStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class Store<T> implements Readable<T> {
|
export abstract class Store<T> implements Readable<T> {
|
||||||
|
|
@ -347,6 +349,16 @@ export abstract class Store<T> implements Readable<T> {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new UIEVentSource. Whenever 'this.data' changes, the returned UIEventSource will get this value as well.
|
||||||
|
* However, this value can be overridden without affecting source
|
||||||
|
*/
|
||||||
|
public followingClone(): UIEventSource<T> {
|
||||||
|
const src = new UIEventSource(this.data)
|
||||||
|
this.addCallback((t) => src.setData(t))
|
||||||
|
return src
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ImmutableStore<T> extends Store<T> {
|
export class ImmutableStore<T> extends Store<T> {
|
||||||
|
|
@ -814,17 +826,6 @@ export class UIEventSource<T> extends Store<T> implements Writable<T> {
|
||||||
(b) => JSON.stringify(b) ?? ""
|
(b) => JSON.stringify(b) ?? ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new UIEVentSource. Whenever 'source' changes, the returned UIEventSource will get this value as well.
|
|
||||||
* However, this value can be overriden without affecting source
|
|
||||||
*/
|
|
||||||
static feedFrom<T>(store: Store<T>): UIEventSource<T> {
|
|
||||||
const src = new UIEventSource(store.data)
|
|
||||||
store.addCallback((t) => src.setData(t))
|
|
||||||
return src
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a callback
|
* Adds a callback
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ export interface MapProperties {
|
||||||
readonly maxbounds: UIEventSource<undefined | BBox>
|
readonly maxbounds: UIEventSource<undefined | BBox>
|
||||||
readonly allowMoving: UIEventSource<true | boolean>
|
readonly allowMoving: UIEventSource<true | boolean>
|
||||||
readonly allowRotating: UIEventSource<true | boolean>
|
readonly allowRotating: UIEventSource<true | boolean>
|
||||||
|
/**
|
||||||
|
* Current rotation of the map, ccw in degrees
|
||||||
|
*/
|
||||||
readonly rotation: UIEventSource<number>
|
readonly rotation: UIEventSource<number>
|
||||||
readonly pitch: UIEventSource<number>
|
readonly pitch: UIEventSource<number>
|
||||||
readonly lastClickLocation: Store<{
|
readonly lastClickLocation: Store<{
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
let compassLoaded = Orientation.singleton.gotMeasurement
|
let compassLoaded = Orientation.singleton.gotMeasurement
|
||||||
let allowRotation = mapProperties.allowRotating
|
let allowRotation = mapProperties.allowRotating
|
||||||
|
|
||||||
function clicked(e: Event) {
|
function clicked() {
|
||||||
if (mapProperties.rotation.data === 0) {
|
if (mapProperties.rotation.data === 0) {
|
||||||
if (mapProperties.allowRotating.data && compassLoaded.data) {
|
if (mapProperties.allowRotating.data && compassLoaded.data) {
|
||||||
mapProperties.rotation.set(orientation.data)
|
mapProperties.rotation.set(orientation.data)
|
||||||
|
|
@ -35,9 +35,8 @@
|
||||||
|
|
||||||
{#if $allowRotation || $gotNonZero}
|
{#if $allowRotation || $gotNonZero}
|
||||||
<button
|
<button
|
||||||
style="z-index: -1"
|
|
||||||
class={"as-link pointer-events-auto relative " + size}
|
class={"as-link pointer-events-auto relative " + size}
|
||||||
on:click={(e) => clicked(e)}
|
on:click={() => clicked()}
|
||||||
>
|
>
|
||||||
{#if $allowRotation && !$compassLoaded && !$gotNonZero}
|
{#if $allowRotation && !$compassLoaded && !$gotNonZero}
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,10 @@
|
||||||
allowMoving: new UIEventSource<boolean>(true),
|
allowMoving: new UIEventSource<boolean>(true),
|
||||||
allowZooming: new UIEventSource<boolean>(true),
|
allowZooming: new UIEventSource<boolean>(true),
|
||||||
minzoom: new UIEventSource<number>(18),
|
minzoom: new UIEventSource<number>(18),
|
||||||
rasterLayer: UIEventSource.feedFrom(state.mapProperties.rasterLayer),
|
rasterLayer: state.mapProperties.rasterLayer.followingClone(),
|
||||||
|
allowRotating: state.mapProperties.allowRotating,
|
||||||
|
rotation: state.mapProperties.rotation.followingClone()
|
||||||
|
|
||||||
}
|
}
|
||||||
state?.showCurrentLocationOn(map)
|
state?.showCurrentLocationOn(map)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
import type { Feature, LineString, Point } from "geojson"
|
import type { Feature, LineString, Point } from "geojson"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
||||||
import SmallZoomButtons from "../Map/SmallZoomButtons.svelte"
|
import SmallZoomButtons from "../Map/SmallZoomButtons.svelte"
|
||||||
|
import CompassWidget from "./CompassWidget.svelte"
|
||||||
|
|
||||||
const splitpoint_style = new LayerConfig(
|
const splitpoint_style = new LayerConfig(
|
||||||
<LayerConfigJson>split_point,
|
<LayerConfigJson>split_point,
|
||||||
|
|
@ -51,7 +52,7 @@
|
||||||
/**
|
/**
|
||||||
* Optional: use these properties to set e.g. background layer
|
* Optional: use these properties to set e.g. background layer
|
||||||
*/
|
*/
|
||||||
export let mapProperties: undefined | Partial<MapProperties> = undefined
|
export let mapProperties: undefined | Partial<Omit<MapProperties, "lastClickLocation">> = undefined
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reuse a point if the clicked location is within this amount of meter
|
* Reuse a point if the clicked location is within this amount of meter
|
||||||
|
|
@ -156,4 +157,7 @@
|
||||||
<div class="relative h-full w-full">
|
<div class="relative h-full w-full">
|
||||||
<MaplibreMap {map} mapProperties={adaptor} />
|
<MaplibreMap {map} mapProperties={adaptor} />
|
||||||
<SmallZoomButtons {adaptor} />
|
<SmallZoomButtons {adaptor} />
|
||||||
|
<div class="absolute top-0 left-0">
|
||||||
|
<CompassWidget mapProperties={adaptor} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,12 @@
|
||||||
state?.mapProperties.rasterLayer.addCallbackD((layer) => rasterLayer.set(layer))
|
state?.mapProperties.rasterLayer.addCallbackD((layer) => rasterLayer.set(layer))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mapProperties: Partial<MapProperties> = {
|
let mapProperties: Partial<MapProperties> & { location } = {
|
||||||
rasterLayer: rasterLayer,
|
rasterLayer: rasterLayer,
|
||||||
location: mapLocation,
|
location: mapLocation,
|
||||||
zoom: new UIEventSource(args?.zoom ?? 18),
|
zoom: new UIEventSource(args?.zoom ?? 18),
|
||||||
|
rotation: state.mapProperties.rotation.followingClone(),
|
||||||
|
lastClickLocation: new UIEventSource(undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
let start: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
|
let start: UIEventSource<{ lon: number; lat: number }> = new UIEventSource(undefined)
|
||||||
|
|
@ -98,6 +100,13 @@
|
||||||
layer: new LayerConfig(conflation),
|
layer: new LayerConfig(conflation),
|
||||||
features: new StaticFeatureSource(lengthFeature),
|
features: new StaticFeatureSource(lengthFeature),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mapProperties.lastClickLocation.addCallbackAndRunD(lonlat => {
|
||||||
|
if (start.data === undefined) {
|
||||||
|
start.set(lonlat)
|
||||||
|
}
|
||||||
|
mapProperties.location.set(lonlat)
|
||||||
|
}, onDestroy)
|
||||||
const t = Translations.t.input_helpers.distance
|
const t = Translations.t.input_helpers.distance
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
import { createEventDispatcher, onDestroy } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte"
|
||||||
import Move_arrows from "../../../assets/svg/Move_arrows.svelte"
|
import Move_arrows from "../../../assets/svg/Move_arrows.svelte"
|
||||||
import SmallZoomButtons from "../../Map/SmallZoomButtons.svelte"
|
import SmallZoomButtons from "../../Map/SmallZoomButtons.svelte"
|
||||||
|
import CompassWidget from "../../BigComponents/CompassWidget.svelte"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A visualisation to pick a location on a map background
|
* A visualisation to pick a location on a map background
|
||||||
|
|
@ -92,7 +93,6 @@
|
||||||
<div class="relative h-full min-h-32 cursor-pointer overflow-hidden">
|
<div class="relative h-full min-h-32 cursor-pointer overflow-hidden">
|
||||||
<div class="absolute left-0 top-0 h-full w-full cursor-pointer">
|
<div class="absolute left-0 top-0 h-full w-full cursor-pointer">
|
||||||
<MaplibreMap
|
<MaplibreMap
|
||||||
center={{ lng: initialCoordinate.lon, lat: initialCoordinate.lat }}
|
|
||||||
{map}
|
{map}
|
||||||
mapProperties={mla}
|
mapProperties={mla}
|
||||||
/>
|
/>
|
||||||
|
|
@ -108,4 +108,8 @@
|
||||||
|
|
||||||
<DragInvitation hideSignal={mla.location} />
|
<DragInvitation hideSignal={mla.location} />
|
||||||
<SmallZoomButtons adaptor={mla} />
|
<SmallZoomButtons adaptor={mla} />
|
||||||
|
<div class="absolute top-0 left-0 ">
|
||||||
|
<CompassWidget mapProperties={mla} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,14 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
maplibreMap: Store<MLMap>,
|
maplibreMap: Store<MLMap>,
|
||||||
state?: Partial<MapProperties>,
|
state?: Partial<MapProperties & {
|
||||||
|
lastClickLocation: UIEventSource<{
|
||||||
|
lon: number
|
||||||
|
lat: number
|
||||||
|
mode: "left" | "right" | "middle"
|
||||||
|
nearestFeature?: Feature
|
||||||
|
}>
|
||||||
|
}>,
|
||||||
options?: {
|
options?: {
|
||||||
correctClick?: number
|
correctClick?: number
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +121,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
|
state?.rasterLayer ?? new UIEventSource<RasterLayerPolygon | undefined>(undefined)
|
||||||
this.showScale = state?.showScale ?? new UIEventSource<boolean>(false)
|
this.showScale = state?.showScale ?? new UIEventSource<boolean>(false)
|
||||||
|
|
||||||
const lastClickLocation = new UIEventSource<{
|
const lastClickLocation = state?.lastClickLocation ?? new UIEventSource<{
|
||||||
lat: number
|
lat: number
|
||||||
lon: number
|
lon: number
|
||||||
mode: "left" | "right" | "middle"
|
mode: "left" | "right" | "middle"
|
||||||
|
|
@ -156,7 +163,6 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
const way = <Feature<LineString>>feature
|
const way = <Feature<LineString>>feature
|
||||||
const lngLat: [number, number] = [e.lngLat.lng, e.lngLat.lat]
|
const lngLat: [number, number] = [e.lngLat.lng, e.lngLat.lat]
|
||||||
const p = GeoOperations.nearestPoint(way, lngLat)
|
const p = GeoOperations.nearestPoint(way, lngLat)
|
||||||
console.log(">>>", p, way, lngLat)
|
|
||||||
if (!p) {
|
if (!p) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -182,7 +188,7 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
this.setMinzoom(this.minzoom.data)
|
this.setMinzoom(this.minzoom.data)
|
||||||
this.setMaxzoom(this.maxzoom.data)
|
this.setMaxzoom(this.maxzoom.data)
|
||||||
this.setBounds(this.bounds.data)
|
this.setBounds(this.bounds.data)
|
||||||
this.setRotation(this.rotation.data)
|
this.setRotation(Math.floor(this.rotation.data))
|
||||||
this.setScale(this.showScale.data)
|
this.setScale(this.showScale.data)
|
||||||
this.updateStores(true)
|
this.updateStores(true)
|
||||||
}
|
}
|
||||||
|
|
@ -562,6 +568,11 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
if (!map || bearing === undefined) {
|
if (!map || bearing === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (Math.abs(map.getBearing() - bearing) < 0.1) {
|
||||||
|
// We don't bother to actually rotate
|
||||||
|
// Aborting small changes helps to dampen changes
|
||||||
|
return
|
||||||
|
}
|
||||||
map.rotateTo(bearing, { duration: 500 })
|
map.rotateTo(bearing, { duration: 500 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -769,8 +780,8 @@ export class MapLibreAdaptor implements MapProperties, ExportableMap {
|
||||||
public installQuicklocation(ratelimitMs = 50) {
|
public installQuicklocation(ratelimitMs = 50) {
|
||||||
this._maplibreMap.addCallbackAndRunD((map) => {
|
this._maplibreMap.addCallbackAndRunD((map) => {
|
||||||
let lastUpdate = new Date().getTime()
|
let lastUpdate = new Date().getTime()
|
||||||
map.on("drag", (e) => {
|
map.on("drag", () => {
|
||||||
let now = new Date().getTime()
|
const now = new Date().getTime()
|
||||||
if (now - lastUpdate < ratelimitMs) {
|
if (now - lastUpdate < ratelimitMs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@
|
||||||
maxZoom: 24,
|
maxZoom: 24,
|
||||||
interactive: true,
|
interactive: true,
|
||||||
attributionControl: false,
|
attributionControl: false,
|
||||||
|
bearing: mapProperties?.rotation?.data ?? 0
|
||||||
}
|
}
|
||||||
_map = new maplibre.Map(options)
|
_map = new maplibre.Map(options)
|
||||||
window.requestAnimationFrame(() => {
|
window.requestAnimationFrame(() => {
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
|
let altmap: UIEventSource<MlMap> = new UIEventSource(undefined)
|
||||||
let altproperties = new MapLibreAdaptor(altmap, {
|
let altproperties = new MapLibreAdaptor(altmap, {
|
||||||
rasterLayer,
|
rasterLayer,
|
||||||
zoom: UIEventSource.feedFrom(placedOverMapProperties.zoom),
|
zoom: placedOverMapProperties.zoom.followingClone(),
|
||||||
rotation: UIEventSource.feedFrom(placedOverMapProperties.rotation),
|
rotation: placedOverMapProperties.rotation.followingClone(),
|
||||||
pitch: UIEventSource.feedFrom(placedOverMapProperties.pitch),
|
pitch: placedOverMapProperties.pitch.followingClone()
|
||||||
})
|
})
|
||||||
altproperties.allowMoving.setData(false)
|
altproperties.allowMoving.setData(false)
|
||||||
altproperties.allowZooming.setData(false)
|
altproperties.allowZooming.setData(false)
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
let rasterLayerOnMap = UIEventSource.feedFrom(rasterLayer)
|
let rasterLayerOnMap = rasterLayer.followingClone()
|
||||||
|
|
||||||
if (shown) {
|
if (shown) {
|
||||||
onDestroy(
|
onDestroy(
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@
|
||||||
const map = new UIEventSource<MlMap>(undefined)
|
const map = new UIEventSource<MlMap>(undefined)
|
||||||
const [lon, lat] = GeoOperations.centerpointCoordinates(importFlow.originalFeature)
|
const [lon, lat] = GeoOperations.centerpointCoordinates(importFlow.originalFeature)
|
||||||
const mla = new MapLibreAdaptor(map, {
|
const mla = new MapLibreAdaptor(map, {
|
||||||
allowMoving: UIEventSource.feedFrom(state.featureSwitchIsTesting),
|
allowMoving: state.featureSwitchIsTesting.followingClone(),
|
||||||
allowZooming: UIEventSource.feedFrom(state.featureSwitchIsTesting),
|
allowZooming: state.featureSwitchIsTesting.followingClone(),
|
||||||
rasterLayer: state.mapProperties.rasterLayer,
|
rasterLayer: state.mapProperties.rasterLayer,
|
||||||
location: new UIEventSource<{ lon: number; lat: number }>({ lon, lat }),
|
location: new UIEventSource<{ lon: number; lat: number }>({ lon, lat }),
|
||||||
zoom: new UIEventSource<number>(18),
|
zoom: new UIEventSource<number>(18),
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@
|
||||||
rasterLayer: state.mapProperties.rasterLayer,
|
rasterLayer: state.mapProperties.rasterLayer,
|
||||||
zoom: new UIEventSource<number>(17),
|
zoom: new UIEventSource<number>(17),
|
||||||
maxzoom: new UIEventSource<number>(17),
|
maxzoom: new UIEventSource<number>(17),
|
||||||
|
rotation: state.mapProperties.rotation.followingClone(),
|
||||||
|
allowRotating: state.mapProperties.allowRotating.followingClone()
|
||||||
})
|
})
|
||||||
|
|
||||||
mla.allowMoving.setData(false)
|
mla.allowMoving.setData(false)
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,14 @@
|
||||||
function initMapProperties(reason: MoveReason): Partial<MapProperties> & { location } {
|
function initMapProperties(reason: MoveReason): Partial<MapProperties> & { location } {
|
||||||
return {
|
return {
|
||||||
allowMoving: new UIEventSource(true),
|
allowMoving: new UIEventSource(true),
|
||||||
allowRotating: new UIEventSource(false),
|
allowRotating: state.mapProperties.allowRotating.followingClone(),
|
||||||
allowZooming: new UIEventSource(true),
|
allowZooming: new UIEventSource(true),
|
||||||
bounds: new UIEventSource(undefined),
|
bounds: new UIEventSource(undefined),
|
||||||
location: new UIEventSource({ lon, lat }),
|
location: new UIEventSource({ lon, lat }),
|
||||||
minzoom: new UIEventSource(reason.minZoom),
|
minzoom: new UIEventSource(reason.minZoom),
|
||||||
rasterLayer: state.mapProperties.rasterLayer,
|
rasterLayer: state.mapProperties.rasterLayer,
|
||||||
zoom: new UIEventSource(reason?.startZoom ?? 16),
|
zoom: new UIEventSource(reason?.startZoom ?? 16),
|
||||||
|
rotation: state.mapProperties.rotation.followingClone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@
|
||||||
let isSearching = new UIEventSource<boolean>(false)
|
let isSearching = new UIEventSource<boolean>(false)
|
||||||
let zoomedInEnough = currentMapProperties
|
let zoomedInEnough = currentMapProperties
|
||||||
.bindD((properties) => properties.zoom, onDestroy)
|
.bindD((properties) => properties.zoom, onDestroy)
|
||||||
.mapD((zoom) => zoom >= Constants.minZoomLevelToAddNewPoint, onDestroy)
|
.mapD((zoom: number) => zoom >= Constants.minZoomLevelToAddNewPoint, onDestroy)
|
||||||
const searcher = new NominatimGeocoding(1)
|
const searcher = new NominatimGeocoding(1)
|
||||||
|
|
||||||
async function searchPressed() {
|
async function searchPressed() {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@
|
||||||
{splitPoints}
|
{splitPoints}
|
||||||
{osmWay}
|
{osmWay}
|
||||||
{snapTolerance}
|
{snapTolerance}
|
||||||
mapProperties={{ rasterLayer: state.mapProperties.rasterLayer }}
|
mapProperties={{ rasterLayer: state.mapProperties.rasterLayer, rotation: state.mapProperties.rotation.followingClone() }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
|
<div class="flex w-full flex-wrap-reverse md:flex-nowrap">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue