forked from MapComplete/MapComplete
		
	Zoom to geolocation automatically if within 60 seconds, fix reading the previous map location from local storage if not initialized, fix #724"
This commit is contained in:
		
							parent
							
								
									0cb306e542
								
							
						
					
					
						commit
						49f26687e3
					
				
					 3 changed files with 72 additions and 38 deletions
				
			
		|  | @ -6,6 +6,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"; | ||||||
| import {QueryParameters} from "../Web/QueryParameters"; | import {QueryParameters} from "../Web/QueryParameters"; | ||||||
| import FeatureSource from "../FeatureSource/FeatureSource"; | import FeatureSource from "../FeatureSource/FeatureSource"; | ||||||
| import {BBox} from "../BBox"; | import {BBox} from "../BBox"; | ||||||
|  | import Constants from "../../Models/Constants"; | ||||||
| 
 | 
 | ||||||
| export interface GeoLocationPointProperties { | export interface GeoLocationPointProperties { | ||||||
|     id: "gps", |     id: "gps", | ||||||
|  | @ -25,13 +26,11 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Wether or not the geolocation is active, aka the user requested the current location |      * Wether or not the geolocation is active, aka the user requested the current location | ||||||
|      * @private |  | ||||||
|      */ |      */ | ||||||
|     private readonly _isActive: UIEventSource<boolean>; |     private readonly _isActive: UIEventSource<boolean>; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user |      * Wether or not the geolocation is locked, aka the user requested the current location and wants the crosshair to follow the user | ||||||
|      * @private |  | ||||||
|      */ |      */ | ||||||
|     private readonly _isLocked: UIEventSource<boolean>; |     private readonly _isLocked: UIEventSource<boolean>; | ||||||
| 
 | 
 | ||||||
|  | @ -54,9 +53,8 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs |      * The date when the user requested the geolocation. If we have a location, it'll autozoom to it the first 30 secs | ||||||
|      * @private |  | ||||||
|      */ |      */ | ||||||
|     private _lastUserRequest: Date; |     private _lastUserRequest: UIEventSource<Date>; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. |      * A small flag on localstorage. If the user previously granted the geolocation, it will be set. | ||||||
|  | @ -80,6 +78,8 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|     ) { |     ) { | ||||||
|         const currentGPSLocation = new UIEventSource<Coordinates>(undefined, "GPS-coordinate") |         const currentGPSLocation = new UIEventSource<Coordinates>(undefined, "GPS-coordinate") | ||||||
|         const leafletMap = state.leafletMap |         const leafletMap = state.leafletMap | ||||||
|  |         const initedAt = new Date() | ||||||
|  |         let autozoomDone = false; | ||||||
|         const hasLocation = currentGPSLocation.map( |         const hasLocation = currentGPSLocation.map( | ||||||
|             (location) => location !== undefined |             (location) => location !== undefined | ||||||
|         ); |         ); | ||||||
|  | @ -97,13 +97,30 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|             const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 |             const timeDiff = (new Date().getTime() - lastClick.getTime()) / 1000 | ||||||
|             return timeDiff <= 3 |             return timeDiff <= 3 | ||||||
|         }) |         }) | ||||||
|  | 
 | ||||||
|  |         const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon") | ||||||
|  |         const willFocus = lastClick.map(lastUserRequest => { | ||||||
|  |             const timeDiffInited = (new Date().getTime() - initedAt.getTime()) / 1000 | ||||||
|  |             console.log("TimeDiff with initedAtt is ", timeDiffInited) | ||||||
|  |             if (!latLonGiven && !autozoomDone && timeDiffInited < Constants.zoomToLocationTimeout) { | ||||||
|  |                 return true | ||||||
|  |             } | ||||||
|  |             if (lastUserRequest === undefined) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |             const timeDiff = (new Date().getTime() - lastUserRequest.getTime()) / 1000 | ||||||
|  |             console.log("TimeDiff with lastClick is ", timeDiff) | ||||||
|  |             return timeDiff <= Constants.zoomToLocationTimeout | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|         lastClick.addCallbackAndRunD(_ => { |         lastClick.addCallbackAndRunD(_ => { | ||||||
|             window.setTimeout(() => { |             window.setTimeout(() => { | ||||||
|                 if (lastClickWithinThreeSecs.data) { |                 if (lastClickWithinThreeSecs.data || willFocus.data) { | ||||||
|                     lastClick.ping() |                     lastClick.ping() | ||||||
|                 } |                 } | ||||||
|             }, 500) |             }, 500) | ||||||
|         }) |         }) | ||||||
|  | 
 | ||||||
|         super( |         super( | ||||||
|             hasLocation.map( |             hasLocation.map( | ||||||
|                 (hasLocationData) => { |                 (hasLocationData) => { | ||||||
|  | @ -116,7 +133,8 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|                     } |                     } | ||||||
|                     if (!hasLocationData) { |                     if (!hasLocationData) { | ||||||
|                         // Position not yet found but we are active: we spin to indicate activity
 |                         // Position not yet found but we are active: we spin to indicate activity
 | ||||||
|                         const icon = Svg.location_empty_svg() |                         // If will focus is active too, we indicate this differently
 | ||||||
|  |                         const icon = willFocus.data ? Svg.location_svg() : Svg.location_empty_svg() | ||||||
|                         icon.SetStyle("animation: spin 4s linear infinite;") |                         icon.SetStyle("animation: spin 4s linear infinite;") | ||||||
|                         return icon; |                         return icon; | ||||||
|                     } |                     } | ||||||
|  | @ -130,7 +148,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|                     // We have a location, so we show a dot in the center
 |                     // We have a location, so we show a dot in the center
 | ||||||
|                     return Svg.location_svg(); |                     return Svg.location_svg(); | ||||||
|                 }, |                 }, | ||||||
|                 [isActive, isLocked, permission, lastClickWithinThreeSecs] |                 [isActive, isLocked, permission, lastClickWithinThreeSecs, willFocus] | ||||||
|             ) |             ) | ||||||
|         ); |         ); | ||||||
|         this.SetClass("mapcontrol") |         this.SetClass("mapcontrol") | ||||||
|  | @ -142,6 +160,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|         this._leafletMap = leafletMap; |         this._leafletMap = leafletMap; | ||||||
|         this._layoutToUse = state.layoutToUse; |         this._layoutToUse = state.layoutToUse; | ||||||
|         this._hasLocation = hasLocation; |         this._hasLocation = hasLocation; | ||||||
|  |         this._lastUserRequest = lastClick | ||||||
|         const self = this; |         const self = this; | ||||||
| 
 | 
 | ||||||
|         const currentPointer = this._isActive.map( |         const currentPointer = this._isActive.map( | ||||||
|  | @ -183,8 +202,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|             self.init(true, true); |             self.init(true, true); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         const latLonGiven = QueryParameters.wasInitialized("lat") && QueryParameters.wasInitialized("lon") |       | ||||||
| 
 |  | ||||||
|         const doAutoZoomToLocation = !latLonGiven && state.featureSwitchGeolocation.data && state.selectedElement.data !== undefined |         const doAutoZoomToLocation = !latLonGiven && state.featureSwitchGeolocation.data && state.selectedElement.data !== undefined | ||||||
|         this.init(false, doAutoZoomToLocation); |         this.init(false, doAutoZoomToLocation); | ||||||
| 
 | 
 | ||||||
|  | @ -221,8 +239,12 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|             self.currentLocation?.features?.setData([{feature, freshness: new Date()}]) |             self.currentLocation?.features?.setData([{feature, freshness: new Date()}]) | ||||||
| 
 | 
 | ||||||
|             const timeSinceRequest = |             const timeSinceRequest = | ||||||
|                 (new Date().getTime() - (self._lastUserRequest?.getTime() ?? 0)) / 1000; |                 (new Date().getTime() - (self._lastUserRequest.data?.getTime() ?? 0)) / 1000; | ||||||
|             if (timeSinceRequest < 30) { | 
 | ||||||
|  |             if (willFocus.data) { | ||||||
|  |                 console.log("Zooming to user location: willFocus is set") | ||||||
|  |                 willFocus.setData(false) | ||||||
|  |                 autozoomDone = true; | ||||||
|                 self.MoveToCurrentLocation(16); |                 self.MoveToCurrentLocation(16); | ||||||
|             } else if (self._isLocked.data) { |             } else if (self._isLocked.data) { | ||||||
|                 self.MoveToCurrentLocation(); |                 self.MoveToCurrentLocation(); | ||||||
|  | @ -239,8 +261,8 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|             self.MoveToCurrentLocation(16); |             self.MoveToCurrentLocation(16); | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         if(typeof navigator === "undefined"){ |         if (typeof navigator === "undefined") { | ||||||
|             return |             return | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -271,7 +293,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Moves to the currently loaded location. |      * Moves to the currently loaded location. | ||||||
|      *  |      * | ||||||
|      * // Should move to any location
 |      * // Should move to any location
 | ||||||
|      * let resultingLocation = undefined |      * let resultingLocation = undefined | ||||||
|      * let resultingzoom = 1 |      * let resultingzoom = 1 | ||||||
|  | @ -321,7 +343,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|      */ |      */ | ||||||
|     private MoveToCurrentLocation(targetZoom?: number) { |     private MoveToCurrentLocation(targetZoom?: number) { | ||||||
|         const location = this._currentGPSLocation.data; |         const location = this._currentGPSLocation.data; | ||||||
|         this._lastUserRequest = undefined; |         this._lastUserRequest.setData(undefined); | ||||||
| 
 | 
 | ||||||
|         if ( |         if ( | ||||||
|             this._currentGPSLocation.data.latitude === 0 && |             this._currentGPSLocation.data.latitude === 0 && | ||||||
|  | @ -356,7 +378,7 @@ export default class GeoLocationHandler extends VariableUiElement { | ||||||
|     private StartGeolocating(zoomToGPS = true) { |     private StartGeolocating(zoomToGPS = true) { | ||||||
|         const self = this; |         const self = this; | ||||||
| 
 | 
 | ||||||
|         this._lastUserRequest = zoomToGPS ? new Date() : new Date(0); |         this._lastUserRequest.setData(zoomToGPS ? new Date() : new Date(0)) | ||||||
|         if (self._permission.data === "denied") { |         if (self._permission.data === "denied") { | ||||||
|             self._previousLocationGrant.setData(""); |             self._previousLocationGrant.setData(""); | ||||||
|             self._isActive.setData(false) |             self._isActive.setData(false) | ||||||
|  |  | ||||||
|  | @ -43,29 +43,34 @@ export default class ElementsState extends FeatureSwitchState { | ||||||
| 
 | 
 | ||||||
|     constructor(layoutToUse: LayoutConfig) { |     constructor(layoutToUse: LayoutConfig) { | ||||||
|         super(layoutToUse); |         super(layoutToUse); | ||||||
|  |          | ||||||
|  |          | ||||||
|  |             function localStorageSynced(key: string, deflt: number, docs: string ): UIEventSource<number>{ | ||||||
|  |                 const localStorage = LocalStorageSource.Get(key) | ||||||
|  |                 const previousValue = localStorage.data | ||||||
|  |                 const src = UIEventSource.asFloat( | ||||||
|  |                     QueryParameters.GetQueryParameter( | ||||||
|  |                         key, | ||||||
|  |                         "" + deflt, | ||||||
|  |                         docs | ||||||
|  |                     ).syncWith(localStorage) | ||||||
|  |                 ); | ||||||
|  |                  | ||||||
|  |                 if(src.data === deflt){ | ||||||
|  |                     const prev = Number(previousValue) | ||||||
|  |                     if(!isNaN(prev)){ | ||||||
|  |                         src.setData(prev) | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 return src; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // -- Location control initialization
 |             // -- Location control initialization
 | ||||||
|             const zoom = UIEventSource.asFloat( |             const zoom = localStorageSynced("z",(layoutToUse?.startZoom ?? 1),"The initial/current zoom level") | ||||||
|                 QueryParameters.GetQueryParameter( |             const lat = localStorageSynced("lat",(layoutToUse?.startLat ?? 0),"The initial/current latitude") | ||||||
|                     "z", |             const lon = localStorageSynced("lon",(layoutToUse?.startLon ?? 0),"The initial/current longitude of the app") | ||||||
|                     "" + (layoutToUse?.startZoom ?? 1), | 
 | ||||||
|                     "The initial/current zoom level" |  | ||||||
|                 ).syncWith(LocalStorageSource.Get("zoom")) |  | ||||||
|             ); |  | ||||||
|             const lat = UIEventSource.asFloat( |  | ||||||
|                 QueryParameters.GetQueryParameter( |  | ||||||
|                     "lat", |  | ||||||
|                     "" + (layoutToUse?.startLat ?? 0), |  | ||||||
|                     "The initial/current latitude" |  | ||||||
|                 ).syncWith(LocalStorageSource.Get("lat")) |  | ||||||
|             ); |  | ||||||
|             const lon = UIEventSource.asFloat( |  | ||||||
|                 QueryParameters.GetQueryParameter( |  | ||||||
|                     "lon", |  | ||||||
|                     "" + (layoutToUse?.startLon ?? 0), |  | ||||||
|                     "The initial/current longitude of the app" |  | ||||||
|                 ).syncWith(LocalStorageSource.Get("lon")) |  | ||||||
|             ); |  | ||||||
| 
 | 
 | ||||||
|             this.locationControl.setData({ |             this.locationControl.setData({ | ||||||
|                 zoom: Utils.asFloat(zoom.data), |                 zoom: Utils.asFloat(zoom.data), | ||||||
|  | @ -73,7 +78,7 @@ export default class ElementsState extends FeatureSwitchState { | ||||||
|                 lon: Utils.asFloat(lon.data), |                 lon: Utils.asFloat(lon.data), | ||||||
|             }) |             }) | ||||||
|             this.locationControl.addCallback((latlonz) => { |             this.locationControl.addCallback((latlonz) => { | ||||||
|                 // Sync th location controls
 |                 // Sync the location controls
 | ||||||
|                 zoom.setData(latlonz.zoom); |                 zoom.setData(latlonz.zoom); | ||||||
|                 lat.setData(latlonz.lat); |                 lat.setData(latlonz.lat); | ||||||
|                 lon.setData(latlonz.lon); |                 lon.setData(latlonz.lon); | ||||||
|  |  | ||||||
|  | @ -62,6 +62,13 @@ export default class Constants { | ||||||
|      */ |      */ | ||||||
|     static distanceToChangeObjectBins = [25, 50, 100, 500, 1000, 5000, Number.MAX_VALUE] |     static distanceToChangeObjectBins = [25, 50, 100, 500, 1000, 5000, Number.MAX_VALUE] | ||||||
|     static themeOrder = ["personal", "cyclofix", "waste" , "etymology",  "food","cafes_and_pubs", "playgrounds", "hailhydrant", "toilets", "aed", "bookcases"]; |     static themeOrder = ["personal", "cyclofix", "waste" , "etymology",  "food","cafes_and_pubs", "playgrounds", "hailhydrant", "toilets", "aed", "bookcases"]; | ||||||
|  |     /** | ||||||
|  |      * Upon initialization, the GPS will search the location. | ||||||
|  |      * If the location is found within the given timout, it'll automatically fly to it. | ||||||
|  |      *  | ||||||
|  |      * In seconds | ||||||
|  |      */ | ||||||
|  |     static zoomToLocationTimeout = 60; | ||||||
| 
 | 
 | ||||||
|     private static isRetina(): boolean { |     private static isRetina(): boolean { | ||||||
|         if (Utils.runningFromConsole) { |         if (Utils.runningFromConsole) { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue