| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { QueryParameters } from "../Web/QueryParameters" | 
					
						
							|  |  |  | import { BBox } from "../BBox" | 
					
						
							| 
									
										
										
										
											2022-04-09 19:29:51 +02:00
										 |  |  | import Constants from "../../Models/Constants" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { GeoLocationState } from "../State/GeoLocationState" | 
					
						
							|  |  |  | import { UIEventSource } from "../UIEventSource" | 
					
						
							|  |  |  | import { Feature, LineString, Point } from "geojson" | 
					
						
							|  |  |  | import { FeatureSource, WritableFeatureSource } from "../FeatureSource/FeatureSource" | 
					
						
							|  |  |  | import { LocalStorageSource } from "../Web/LocalStorageSource" | 
					
						
							|  |  |  | import { GeoOperations } from "../GeoOperations" | 
					
						
							|  |  |  | import { OsmTags } from "../../Models/OsmFeature" | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  | import StaticFeatureSource from "../FeatureSource/Sources/StaticFeatureSource" | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import { MapProperties } from "../../Models/MapProperties" | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * 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 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |  */ | 
					
						
							|  |  |  | export default class GeoLocationHandler { | 
					
						
							|  |  |  |     public readonly geolocationState: GeoLocationState | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The location as delivered by the GPS, wrapped as FeatureSource | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     public currentUserLocation: FeatureSource | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * All previously visited points (as 'Point'-objects), with their metadata | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |     public historicalUserLocations: WritableFeatureSource<Feature<Point>> | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * A featureSource containing a single linestring which has the GPS-history of the user. | 
					
						
							|  |  |  |      * However, metadata (such as when every single point was visited) is lost here (but is kept in `historicalUserLocations`. | 
					
						
							|  |  |  |      * Note that this featureSource is _derived_ from 'historicalUserLocations' | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |     public readonly historicalUserLocationsTrack: FeatureSource | 
					
						
							| 
									
										
										
										
											2023-05-07 23:25:28 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     /** | 
					
						
							|  |  |  |      * The last moment that the map has moved | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     public readonly mapHasMoved: UIEventSource<Date | undefined> = new UIEventSource< | 
					
						
							|  |  |  |         Date | undefined | 
					
						
							|  |  |  |     >(undefined) | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |     private readonly selectedElement: UIEventSource<any> | 
					
						
							|  |  |  |     private readonly mapProperties?: MapProperties | 
					
						
							|  |  |  |     private readonly gpsLocationHistoryRetentionTime?: UIEventSource<number> | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor( | 
					
						
							|  |  |  |         geolocationState: GeoLocationState, | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         selectedElement: UIEventSource<any>, | 
					
						
							|  |  |  |         mapProperties?: MapProperties, | 
					
						
							|  |  |  |         gpsLocationHistoryRetentionTime?: UIEventSource<number> | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     ) { | 
					
						
							|  |  |  |         this.geolocationState = geolocationState | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         const mapLocation = mapProperties.location | 
					
						
							|  |  |  |         this.selectedElement = selectedElement | 
					
						
							|  |  |  |         this.mapProperties = mapProperties | 
					
						
							|  |  |  |         this.gpsLocationHistoryRetentionTime = gpsLocationHistoryRetentionTime | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         // Did an interaction move the map?
 | 
					
						
							|  |  |  |         let self = this | 
					
						
							|  |  |  |         let initTime = new Date() | 
					
						
							|  |  |  |         mapLocation.addCallbackD((_) => { | 
					
						
							|  |  |  |             if (new Date().getTime() - initTime.getTime() < 250) { | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-05-07 23:25:28 +02:00
										 |  |  |             self.mapHasMoved.setData(new Date()) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             return true // Unsubscribe
 | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-04-09 19:29:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         const latLonGivenViaUrl = | 
					
						
							|  |  |  |             QueryParameters.wasInitialized("lat") || QueryParameters.wasInitialized("lon") | 
					
						
							|  |  |  |         if (latLonGivenViaUrl) { | 
					
						
							|  |  |  |             // The URL counts as a 'user interaction'
 | 
					
						
							| 
									
										
										
										
											2023-05-07 23:25:28 +02:00
										 |  |  |             this.mapHasMoved.setData(new Date()) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-04-09 19:29:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         this.geolocationState.currentGPSLocation.addCallbackAndRunD((_) => { | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             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() | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             if ( | 
					
						
							|  |  |  |                 timeSinceLastRequest < Constants.zoomToLocationTimeout && | 
					
						
							|  |  |  |                 (this.mapHasMoved.data === undefined || | 
					
						
							|  |  |  |                     this.mapHasMoved.data.getTime() < | 
					
						
							|  |  |  |                         geolocationState.requestMoment.data?.getTime()) | 
					
						
							| 
									
										
										
										
											2023-05-07 23:25:28 +02:00
										 |  |  |             ) { | 
					
						
							|  |  |  |                 // still within request time and the map hasn't moved since requesting to jump to the current location
 | 
					
						
							| 
									
										
										
										
											2023-02-09 03:12:21 +01:00
										 |  |  |                 self.MoveMapToCurrentLocation() | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-19 16:23:13 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |             if (!this.geolocationState.allowMoving.data) { | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |                 // Jup, the map is locked to the bound location: move automatically
 | 
					
						
							|  |  |  |                 self.MoveMapToCurrentLocation() | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2021-07-23 15:56:22 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         geolocationState.allowMoving.syncWith(mapProperties.allowMoving, true) | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         this.CopyGeolocationIntoMapstate() | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         this.historicalUserLocationsTrack = this.initUserLocationTrail() | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-04-13 01:26:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * 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 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         const mapLocation = this.mapProperties.location | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         // We got a new location.
 | 
					
						
							|  |  |  |         // Do we move the map to it?
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         if (this.selectedElement.data !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             // 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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         // We check that the GPS location is not out of bounds
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         const bounds = this.mapProperties.maxbounds.data | 
					
						
							|  |  |  |         if (bounds !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |             // B is an array with our lock-location
 | 
					
						
							|  |  |  |             const inRange = new BBox(bounds).contains([newLocation.longitude, newLocation.latitude]) | 
					
						
							|  |  |  |             if (!inRange) { | 
					
						
							|  |  |  |                 return | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         mapLocation.setData({ | 
					
						
							|  |  |  |             lon: newLocation.longitude, | 
					
						
							|  |  |  |             lat: newLocation.latitude, | 
					
						
							| 
									
										
										
										
											2021-08-19 23:41:48 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         const zoom = this.mapProperties.zoom | 
					
						
							| 
									
										
										
										
											2023-05-17 13:10:22 +02:00
										 |  |  |         zoom.setData(Math.min(Math.max(zoom.data, 14), 18)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-07 23:25:28 +02:00
										 |  |  |         this.mapHasMoved.setData(new Date()) | 
					
						
							| 
									
										
										
										
											2023-02-09 03:12:21 +01:00
										 |  |  |         this.geolocationState.requestMoment.setData(undefined) | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-09 00:05:51 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-22 04:13:52 +01:00
										 |  |  |     private CopyGeolocationIntoMapstate() { | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |         const features: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([]) | 
					
						
							|  |  |  |         this.currentUserLocation = new StaticFeatureSource(features) | 
					
						
							| 
									
										
										
										
											2023-06-07 23:55:02 +02:00
										 |  |  |         let i = 0 | 
					
						
							| 
									
										
										
										
											2022-12-23 15:52:22 +01:00
										 |  |  |         this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { | 
					
						
							|  |  |  |             if (location === undefined) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-22 16:25:24 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             const properties = { | 
					
						
							|  |  |  |                 id: "gps-" + i, | 
					
						
							| 
									
										
										
										
											2023-06-07 23:55:02 +02:00
										 |  |  |                 "user:location": "yes", | 
					
						
							|  |  |  |                 date: new Date().toISOString(), | 
					
						
							| 
									
										
										
										
											2023-11-23 17:07:53 +01:00
										 |  |  |                 // GeolocationObject behaves really weird when indexing, so copying it one by one is the most stable
 | 
					
						
							|  |  |  |                 accuracy: location.accuracy, | 
					
						
							|  |  |  |                 speed: location.speed, | 
					
						
							|  |  |  |                 altitude: location.altitude, | 
					
						
							|  |  |  |                 altitudeAccuracy: location.altitudeAccuracy, | 
					
						
							|  |  |  |                 heading: location.heading, | 
					
						
							| 
									
										
										
										
											2023-06-07 23:55:02 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             i++ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |             const feature = <Feature>{ | 
					
						
							| 
									
										
										
										
											2021-11-03 00:44:53 +01:00
										 |  |  |                 type: "Feature", | 
					
						
							| 
									
										
										
										
											2023-06-07 23:55:02 +02:00
										 |  |  |                 properties, | 
					
						
							| 
									
										
										
										
											2021-11-07 16:34:51 +01:00
										 |  |  |                 geometry: { | 
					
						
							|  |  |  |                     type: "Point", | 
					
						
							| 
									
										
										
										
											2021-11-03 00:44:53 +01:00
										 |  |  |                     coordinates: [location.longitude, location.latitude], | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  |             features.setData([feature]) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private initUserLocationTrail() { | 
					
						
							|  |  |  |         const features = LocalStorageSource.GetParsed<Feature[]>("gps_location_history", []) | 
					
						
							|  |  |  |         const now = new Date().getTime() | 
					
						
							|  |  |  |         features.data = features.data.filter((ff) => { | 
					
						
							|  |  |  |             if (ff.properties === undefined) { | 
					
						
							|  |  |  |                 return false | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             const point_time = new Date(ff.properties["date"]) | 
					
						
							|  |  |  |             return ( | 
					
						
							|  |  |  |                 now - point_time.getTime() < | 
					
						
							|  |  |  |                 1000 * (this.gpsLocationHistoryRetentionTime?.data ?? 24 * 60 * 60 * 1000) | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |         features.ping() | 
					
						
							|  |  |  |         let i = 0 | 
					
						
							|  |  |  |         this.currentUserLocation?.features?.addCallbackAndRunD(([location]: [Feature<Point>]) => { | 
					
						
							|  |  |  |             if (location === undefined) { | 
					
						
							|  |  |  |                 return | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const previousLocation = <Feature<Point>>features.data[features.data.length - 1] | 
					
						
							|  |  |  |             if (previousLocation !== undefined) { | 
					
						
							|  |  |  |                 const previousLocationFreshness = new Date(previousLocation.properties.date) | 
					
						
							|  |  |  |                 const d = GeoOperations.distanceBetween( | 
					
						
							|  |  |  |                     <[number, number]>previousLocation.geometry.coordinates, | 
					
						
							|  |  |  |                     <[number, number]>location.geometry.coordinates | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 let timeDiff = Number.MAX_VALUE // in seconds
 | 
					
						
							|  |  |  |                 const olderLocation = features.data[features.data.length - 2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 if (olderLocation !== undefined) { | 
					
						
							|  |  |  |                     const olderLocationFreshness = new Date(olderLocation.properties.date) | 
					
						
							|  |  |  |                     timeDiff = | 
					
						
							|  |  |  |                         (new Date(previousLocationFreshness).getTime() - | 
					
						
							|  |  |  |                             new Date(olderLocationFreshness).getTime()) / | 
					
						
							|  |  |  |                         1000 | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (d < 20 && timeDiff < 60) { | 
					
						
							|  |  |  |                     // Do not append changes less then 20m - it's probably noise anyway
 | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const feature = JSON.parse(JSON.stringify(location)) | 
					
						
							|  |  |  |             feature.properties.id = "gps/" + features.data.length | 
					
						
							|  |  |  |             i++ | 
					
						
							|  |  |  |             features.data.push(feature) | 
					
						
							|  |  |  |             features.ping() | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         this.historicalUserLocations = <any>new StaticFeatureSource(features) | 
					
						
							| 
									
										
										
										
											2023-03-24 19:21:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const asLine = features.map((allPoints) => { | 
					
						
							|  |  |  |             if (allPoints === undefined || allPoints.length < 2) { | 
					
						
							|  |  |  |                 return [] | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             const feature: Feature<LineString, OsmTags> = { | 
					
						
							|  |  |  |                 type: "Feature", | 
					
						
							|  |  |  |                 properties: { | 
					
						
							|  |  |  |                     id: "location_track", | 
					
						
							|  |  |  |                     "_date:now": new Date().toISOString(), | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 geometry: { | 
					
						
							|  |  |  |                     type: "LineString", | 
					
						
							|  |  |  |                     coordinates: allPoints.map( | 
					
						
							|  |  |  |                         (ff: Feature<Point>) => <[number, number]>ff.geometry.coordinates | 
					
						
							|  |  |  |                     ), | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return [feature] | 
					
						
							| 
									
										
										
										
											2021-07-23 15:56:22 +02:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2023-04-20 18:58:31 +02:00
										 |  |  |         return new StaticFeatureSource(asLine) | 
					
						
							| 
									
										
										
										
											2020-06-28 02:42:22 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-19 16:23:13 +02:00
										 |  |  | } |