mapcomplete/UI/InputElement/Helpers/LocationInput.svelte

96 lines
3.8 KiB
Svelte

<script lang="ts">
import {Store, UIEventSource} from "../../../Logic/UIEventSource"
import type {MapProperties} from "../../../Models/MapProperties"
import {Map as MlMap} from "maplibre-gl"
import {MapLibreAdaptor} from "../../Map/MapLibreAdaptor"
import MaplibreMap from "../../Map/MaplibreMap.svelte"
import DragInvitation from "../../Base/DragInvitation.svelte"
import {GeoOperations} from "../../../Logic/GeoOperations"
import ShowDataLayer from "../../Map/ShowDataLayer"
import * as boundsdisplay from "../../../assets/layers/range/range.json"
import StaticFeatureSource from "../../../Logic/FeatureSource/Sources/StaticFeatureSource"
import * as turf from "@turf/turf"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import {createEventDispatcher, onDestroy} from "svelte"
/**
* A visualisation to pick a location on a map background
*/
export let value: UIEventSource<{ lon: number; lat: number }>
export let initialCoordinate: { lon: number; lat: number }
initialCoordinate = initialCoordinate ?? value.data
export let maxDistanceInMeters: number = undefined
export let mapProperties: Partial<MapProperties> & {
readonly location: UIEventSource<{ lon: number; lat: number }>
} = undefined
/**
* Called when setup is done, can be used to add more layers to the map
*/
export let onCreated: (
value: Store<{
lon: number
lat: number
}>,
map: Store<MlMap>,
mapProperties: MapProperties
) => void = undefined
const dispatch = createEventDispatcher<{ click: { lon: number, lat: number } }>()
export let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mla = new MapLibreAdaptor(map, mapProperties)
mla.lastClickLocation.addCallbackAndRunD(lastClick => {
dispatch("click", lastClick)
})
mapProperties.location.syncWith(value)
if (onCreated) {
onCreated(value, map, mla)
}
let rangeIsShown = false
if (maxDistanceInMeters) {
onDestroy(
mla.location.addCallbackD((newLocation) => {
const l = [newLocation.lon, newLocation.lat]
const c: [number, number] = [initialCoordinate.lon, initialCoordinate.lat]
const d = GeoOperations.distanceBetween(l, c)
console.log("distance is", d, l, c)
if (d <= maxDistanceInMeters) {
return
}
// This is too far away - let's move back
const correctLocation = GeoOperations.along(c, l, maxDistanceInMeters - 10)
window.setTimeout(() => {
mla.location.setData({lon: correctLocation[0], lat: correctLocation[1]})
}, 25)
if (!rangeIsShown) {
new ShowDataLayer(map, {
layer: new LayerConfig(boundsdisplay),
features: new StaticFeatureSource([
turf.circle(c, maxDistanceInMeters, {
units: "meters",
properties: {range: "yes", id: "0"},
}),
]),
})
rangeIsShown = true
}
})
)
}
</script>
<div class="min-h-32 relative h-full cursor-pointer overflow-hidden">
<div class="absolute top-0 left-0 h-full w-full cursor-pointer">
<MaplibreMap {map}/>
</div>
<div
class="pointer-events-none absolute top-0 left-0 flex h-full w-full items-center p-8 opacity-50"
>
<img class="h-full max-h-24" src="./assets/svg/move-arrows.svg"/>
</div>
<DragInvitation hideSignal={mla.location}/>
</div>