mapcomplete/Logic/Actors/GeoLocationHandler.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

164 lines
6.1 KiB
TypeScript
Raw Normal View History

import { QueryParameters } from "../Web/QueryParameters"
import { BBox } from "../BBox"
import Constants from "../../Models/Constants"
import { GeoLocationPointProperties, GeoLocationState } from "../State/GeoLocationState"
import { UIEventSource } from "../UIEventSource"
import Loc from "../../Models/Loc"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import SimpleFeatureSource from "../FeatureSource/Sources/SimpleFeatureSource"
/**
* The geolocation-handler takes a map-location and a geolocation state.
* It'll move the map as appropriate given the state of the geolocation-API
2022-12-23 15:52:22 +01:00
* It will also copy the geolocation into the appropriate FeatureSource to display on the map
*/
export default class GeoLocationHandler {
public readonly geolocationState: GeoLocationState
private readonly _state: {
currentUserLocation: SimpleFeatureSource
layoutToUse: LayoutConfig
locationControl: UIEventSource<Loc>
selectedElement: UIEventSource<any>
leafletMap?: UIEventSource<any>
}
public readonly mapHasMoved: UIEventSource<boolean> = new UIEventSource<boolean>(false)
constructor(
geolocationState: GeoLocationState,
state: {
locationControl: UIEventSource<Loc>
currentUserLocation: SimpleFeatureSource
layoutToUse: LayoutConfig
selectedElement: UIEventSource<any>
leafletMap?: UIEventSource<any>
}
) {
this.geolocationState = geolocationState
this._state = state
const mapLocation = state.locationControl
// Did an interaction move the map?
let self = this
let initTime = new Date()
mapLocation.addCallbackD((_) => {
if (new Date().getTime() - initTime.getTime() < 250) {
return
}
self.mapHasMoved.setData(true)
return true // Unsubscribe
})
const latLonGivenViaUrl =
QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon")
if (latLonGivenViaUrl) {
// The URL counts as a 'user interaction'
this.mapHasMoved.setData(true)
}
this.geolocationState.currentGPSLocation.addCallbackAndRunD((newLocation) => {
const timeSinceLastRequest =
(new Date().getTime() - geolocationState.requestMoment.data?.getTime() ?? 0) / 1000
if (!this.mapHasMoved.data) {
// The map hasn't moved yet; we received our first coordinates, so let's move there!
2023-02-09 03:12:21 +01:00
self.MoveMapToCurrentLocation()
}
if (timeSinceLastRequest < Constants.zoomToLocationTimeout) {
self.MoveMapToCurrentLocation()
}
2021-07-19 16:23:13 +02:00
if (this.geolocationState.isLocked.data) {
// Jup, the map is locked to the bound location: move automatically
self.MoveMapToCurrentLocation()
return
}
2021-07-23 15:56:22 +02:00
})
geolocationState.isLocked.map(
(isLocked) => {
if (isLocked) {
state.leafletMap?.data?.dragging?.disable()
} else {
state.leafletMap?.data?.dragging?.enable()
}
},
[state.leafletMap]
)
this.CopyGeolocationIntoMapstate()
}
/**
* Move the map to the GPS-location, except:
* - If there is a selected element
* - The location is out of the locked bound
* - The GPS-location iss NULL-island
* @constructor
*/
public MoveMapToCurrentLocation() {
const newLocation = this.geolocationState.currentGPSLocation.data
const mapLocation = this._state.locationControl
const state = this._state
// We got a new location.
// Do we move the map to it?
if (state.selectedElement.data !== undefined) {
// Nope, there is something selected, so we don't move to the current GPS-location
return
}
if (newLocation.latitude === 0 && newLocation.longitude === 0) {
console.debug("Not moving to GPS-location: it is null island")
return
}
2021-07-19 16:23:13 +02:00
// We check that the GPS location is not out of bounds
const bounds = state.layoutToUse.lockLocation
if (bounds && bounds !== true) {
// B is an array with our lock-location
const inRange = new BBox(bounds).contains([newLocation.longitude, newLocation.latitude])
if (!inRange) {
return
}
}
mapLocation.setData({
2023-02-09 03:12:21 +01:00
zoom: Math.max(mapLocation.data.zoom, 16),
lon: newLocation.longitude,
lat: newLocation.latitude,
})
this.mapHasMoved.setData(true)
2023-02-09 03:12:21 +01:00
this.geolocationState.requestMoment.setData(undefined)
}
private CopyGeolocationIntoMapstate() {
const state = this._state
// For some weird reason, the 'Object.keys' method doesn't work for the 'location: GeolocationCoordinates'-object and will thus not copy all the properties when using {...location}
// As such, they are copied here
const keysToCopy = ["speed", "accuracy", "altitude", "altitudeAccuracy", "heading"]
2022-12-23 15:52:22 +01:00
this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => {
if (location === undefined) {
return
}
const feature = {
type: "Feature",
properties: <GeoLocationPointProperties>{
2021-11-08 02:36:01 +01:00
id: "gps",
2021-11-07 16:34:51 +01:00
"user:location": "yes",
2021-11-08 14:18:45 +01:00
date: new Date().toISOString(),
...location,
},
2021-11-07 16:34:51 +01:00
geometry: {
type: "Point",
coordinates: [location.longitude, location.latitude],
},
}
for (const key of keysToCopy) {
if (location[key] !== null) {
feature.properties[key] = location[key]
}
}
2021-11-07 16:34:51 +01:00
state.currentUserLocation?.features?.setData([{ feature, freshness: new Date() }])
2021-07-23 15:56:22 +02:00
})
}
2021-07-19 16:23:13 +02:00
}