forked from MapComplete/MapComplete
		
	Add geolocation button which uses device GPS
This commit is contained in:
		
							parent
							
								
									57c9fcc5aa
								
							
						
					
					
						commit
						a566ab6725
					
				
					 6 changed files with 299 additions and 32 deletions
				
			
		
							
								
								
									
										107
									
								
								Logic/GeoLocationHandler.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								Logic/GeoLocationHandler.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,107 @@ | ||||||
|  | import {Basemap} from "./Basemap"; | ||||||
|  | import {UIEventSource} from "../UI/UIEventSource"; | ||||||
|  | import {UIElement} from "../UI/UIElement"; | ||||||
|  | import L from "leaflet"; | ||||||
|  | 
 | ||||||
|  | export class GeoLocationHandler extends UIElement { | ||||||
|  | 
 | ||||||
|  |     currentLocation: UIEventSource<{ | ||||||
|  |         latlng: number, | ||||||
|  |         accuracy: number | ||||||
|  |     }> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined); | ||||||
|  | 
 | ||||||
|  |     private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false); | ||||||
|  | 
 | ||||||
|  |     private _map: Basemap; | ||||||
|  |     private _marker: any; | ||||||
|  | 
 | ||||||
|  |     constructor(map: Basemap) { | ||||||
|  |         super(undefined); | ||||||
|  |         this._map = map; | ||||||
|  |         this.ListenTo(this.currentLocation); | ||||||
|  |         this.ListenTo(this._isActive); | ||||||
|  | 
 | ||||||
|  |         const self = this; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         function onAccuratePositionProgress(e) { | ||||||
|  |             console.log(e.accuracy); | ||||||
|  |             console.log(e.latlng); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         function onAccuratePositionFound(e) { | ||||||
|  |             console.log(e.accuracy); | ||||||
|  |             console.log(e.latlng); | ||||||
|  |             self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         function onAccuratePositionError(e) { | ||||||
|  |             console.log("onerror", e.message); | ||||||
|  |             | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         map.map.on('accuratepositionprogress', onAccuratePositionProgress); | ||||||
|  |         map.map.on('accuratepositionfound', onAccuratePositionFound); | ||||||
|  |         map.map.on('accuratepositionerror', onAccuratePositionError); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         const icon = L.icon( | ||||||
|  |             { | ||||||
|  |                 iconUrl: './assets/crosshair-blue.svg', | ||||||
|  |                 iconSize: [40, 40], // size of the icon
 | ||||||
|  |                 iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
 | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |         this.currentLocation.addCallback((location) => { | ||||||
|  |             const newMarker = L.marker(location.latlng, {icon: icon}); | ||||||
|  |             newMarker.addTo(map.map); | ||||||
|  | 
 | ||||||
|  |             if (self._marker !== undefined) { | ||||||
|  |                 map.map.removeLayer(self._marker); | ||||||
|  |             } | ||||||
|  |             self._marker = newMarker; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         navigator.permissions.query({ name: 'geolocation' }) | ||||||
|  |             .then(function(){self.StartGeolocating()}); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected InnerRender(): string { | ||||||
|  |         if (this.currentLocation.data) { | ||||||
|  |             return "<img src='./assets/crosshair-blue.svg' alt='locate me'>"; | ||||||
|  |         } | ||||||
|  |         if (this._isActive.data) { | ||||||
|  |             return "<img src='./assets/crosshair-blue-center.svg' alt='locate me'>"; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return "<img src='./assets/crosshair.svg' alt='locate me'>"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     private StartGeolocating(){ | ||||||
|  |         const self = this; | ||||||
|  |         if (self.currentLocation.data !== undefined) { | ||||||
|  |             self._map.map.flyTo(self.currentLocation.data.latlng, 18); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         self._isActive.setData(true); | ||||||
|  | 
 | ||||||
|  |         console.log("Searching location using GPS") | ||||||
|  |         self._map.map.findAccuratePosition({ | ||||||
|  |             maxWait: 15000, // defaults to 10000
 | ||||||
|  |             desiredAccuracy: 30 // defaults to 20
 | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     InnerUpdate(htmlElement: HTMLElement) { | ||||||
|  |         super.InnerUpdate(htmlElement); | ||||||
|  | 
 | ||||||
|  |         const self = this; | ||||||
|  |         htmlElement.onclick = function () { | ||||||
|  |             self.StartGeolocating(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -95,13 +95,12 @@ export class LayerUpdater { | ||||||
| 
 | 
 | ||||||
|     private buildBboxFor(): string { |     private buildBboxFor(): string { | ||||||
|         const b = this._map.map.getBounds(); |         const b = this._map.map.getBounds(); | ||||||
|         const latDiff = Math.abs(b.getNorth() - b.getSouth()); |         const diff =0.07; | ||||||
|         const lonDiff = Math.abs(b.getEast() - b.getWest()); |          | ||||||
|         const extra = 0.5; |         const n = b.getNorth() + diff; | ||||||
|         const n = b.getNorth() + latDiff * extra; |         const e = b.getEast() +  diff; | ||||||
|         const e = b.getEast() + lonDiff * extra; |         const s = b.getSouth() - diff; | ||||||
|         const s = b.getSouth() - latDiff * extra; |         const w = b.getWest() -  diff; | ||||||
|         const w = b.getWest() - lonDiff * extra; |  | ||||||
| 
 | 
 | ||||||
|         this.previousBounds = {north: n, east: e, south: s, west: w}; |         this.previousBounds = {north: n, east: e, south: s, west: w}; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										67
									
								
								index.css
									
										
									
									
									
								
							
							
						
						
									
										67
									
								
								index.css
									
										
									
									
									
								
							|  | @ -16,6 +16,26 @@ img { | ||||||
|     border-radius: 1em; |     border-radius: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | #geolocate-button { | ||||||
|  |     position: absolute; | ||||||
|  |     bottom: 27px; | ||||||
|  |     right: 65px; | ||||||
|  |     z-index: 999; /*Just below leaflets zoom*/ | ||||||
|  |     background-color: white; | ||||||
|  |     border-radius: 5px; | ||||||
|  |     border: solid 2px rgba(0,0,0,0.2); | ||||||
|  |     cursor: pointer; | ||||||
|  |     width: 43px; | ||||||
|  |     height:43px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #geolocate-button img{ | ||||||
|  |     width: 31px; | ||||||
|  |     height:31px; | ||||||
|  |     margin: 6px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /**************** GENERIC ****************/ | /**************** GENERIC ****************/ | ||||||
| 
 | 
 | ||||||
| .uielement { | .uielement { | ||||||
|  | @ -145,9 +165,9 @@ img { | ||||||
| #welcomeMessage { | #welcomeMessage { | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
|     max-width: 30em; |     max-width: 30em; | ||||||
|  |     padding: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| #messagesboxmobilewrapper { | #messagesboxmobilewrapper { | ||||||
|     display: none; /*Only shown on small screens*/ |     display: none; /*Only shown on small screens*/ | ||||||
| } | } | ||||||
|  | @ -190,30 +210,33 @@ img { | ||||||
|     #messagesboxmobile-scroll { |     #messagesboxmobile-scroll { | ||||||
|         display: block; |         display: block; | ||||||
|         overflow-y: auto; |         overflow-y: auto; | ||||||
|         height: calc(100vh - 9em); |         width: 100vw; | ||||||
|         padding-top: 1em; |         padding: 0; | ||||||
|         padding-bottom: 2em; |         margin:0; | ||||||
|         margin-bottom: 0; |         height: calc(100% - 5em); /*Height of to-the-map is 2em, padding is 2 * 0.5em*/ | ||||||
|     } |     } | ||||||
|     #messagesboxmobile { |     #messagesboxmobile { | ||||||
|         margin: 1em; |         margin: 1em; | ||||||
|         margin-bottom: 2em; |         padding-bottom: 2em; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| #to-the-map { | #to-the-map { | ||||||
|  |      | ||||||
|  |     height: 4em; | ||||||
|  |     padding: 0.5em; | ||||||
|     margin: 0; |     margin: 0; | ||||||
|     padding: 1em; |      | ||||||
|     padding-right: 2em; |  | ||||||
|     color: white; |  | ||||||
|     background-color: #7ebc6f; |  | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     bottom: 0; |     bottom: 0; | ||||||
|     right: 0; |     right: 0; | ||||||
|     text-align: right; |      | ||||||
|  |     padding-right: 2em; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|     height: 4em; |     text-align: right; | ||||||
|  |     color: white; | ||||||
|  |     background-color: #7ebc6f; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -228,6 +251,7 @@ img { | ||||||
|     padding: 0; |     padding: 0; | ||||||
|     right: 1em; |     right: 1em; | ||||||
|     top: 1em; |     top: 1em; | ||||||
|  |     border-radius: 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #centermessage { | #centermessage { | ||||||
|  | @ -319,8 +343,7 @@ img { | ||||||
| 
 | 
 | ||||||
|     content:url(assets/arrow-left-smooth.svg); |     content:url(assets/arrow-left-smooth.svg); | ||||||
| 
 | 
 | ||||||
|     border-bottom-left-radius: 1em; |     border-radius: 1em; | ||||||
|     border-top-left-radius: 1em; |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -335,8 +358,7 @@ img { | ||||||
|     top: 50%; |     top: 50%; | ||||||
|     right: 0; |     right: 0; | ||||||
|     transform: translate(0, -50%); |     transform: translate(0, -50%); | ||||||
|     border-bottom-right-radius: 1em; |     border-radius: 1em; | ||||||
|     border-top-right-radius: 1em; |  | ||||||
|      |      | ||||||
| 
 | 
 | ||||||
|     z-index: 5060; |     z-index: 5060; | ||||||
|  | @ -345,11 +367,12 @@ img { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .slide > span > img { | .slide > span img { | ||||||
|     width: 500px; |  | ||||||
|     max-width: 100%; |  | ||||||
|     height: auto; |     height: auto; | ||||||
|      |     width: auto; | ||||||
|  |     max-width: 100%; | ||||||
|  |     max-height: 20vh; | ||||||
|  |     border-radius: 1em; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -369,8 +392,8 @@ img { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .wikimedia-link { | .wikimedia-link { | ||||||
|     width: 1.5em; |     /*The actual wikimedia logo*/ | ||||||
|     height: auto; |     width: 1.5em !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .attribution { | .attribution { | ||||||
|  |  | ||||||
|  | @ -33,10 +33,11 @@ | ||||||
| <div id="centermessage"></div> | <div id="centermessage"></div> | ||||||
| <div id="bottomRight" style="display: none">ADD</div> | <div id="bottomRight" style="display: none">ADD</div> | ||||||
| 
 | 
 | ||||||
|  | <div id="geolocate-button"></div> | ||||||
| <div id="leafletDiv"></div> | <div id="leafletDiv"></div> | ||||||
| 
 | 
 | ||||||
| <script src="./index.ts"></script> | <script src="./index.ts"></script> | ||||||
| 
 | <script src="vendor/Leaflet.AccuratePosition.js"></script> | ||||||
| 
 | 
 | ||||||
| <!-- 3 dagen eerste protoype --> | <!-- 3 dagen eerste protoype --> | ||||||
| <!-- 19 juni: eerste feedbackronde, foto's --> | <!-- 19 juni: eerste feedbackronde, foto's --> | ||||||
|  | @ -44,7 +45,7 @@ | ||||||
| <!-- 24 juni: foto's via imgur --> | <!-- 24 juni: foto's via imgur --> | ||||||
| 
 | 
 | ||||||
| <!-- 26 restylen infobox --> | <!-- 26 restylen infobox --> | ||||||
| 
 | <!-- 27 restylen infobox, flow UI verbeteren, mobile, locate-me --> | ||||||
| <script data-goatcounter="https://pietervdvn.goatcounter.com/count" | <script data-goatcounter="https://pietervdvn.goatcounter.com/count" | ||||||
|         async src="//gc.zgo.at/count.js"></script> |         async src="//gc.zgo.at/count.js"></script> | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										4
									
								
								index.ts
									
										
									
									
									
								
							
							
						
						
									
										4
									
								
								index.ts
									
										
									
									
									
								
							|  | @ -17,6 +17,7 @@ import {MessageBoxHandler} from "./UI/MessageBoxHandler"; | ||||||
| import {Overpass} from "./Logic/Overpass"; | import {Overpass} from "./Logic/Overpass"; | ||||||
| import {FixedUiElement} from "./UI/FixedUiElement"; | import {FixedUiElement} from "./UI/FixedUiElement"; | ||||||
| import {FeatureInfoBox} from "./UI/FeatureInfoBox"; | import {FeatureInfoBox} from "./UI/FeatureInfoBox"; | ||||||
|  | import {GeoLocationHandler} from "./Logic/GeoLocationHandler"; | ||||||
| 
 | 
 | ||||||
| let dryRun = false; | let dryRun = false; | ||||||
| 
 | 
 | ||||||
|  | @ -188,6 +189,9 @@ Helpers.LastEffortSave(changes); | ||||||
| osmConnection.registerActivateOsmAUthenticationClass(); | osmConnection.registerActivateOsmAUthenticationClass(); | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | new GeoLocationHandler(bm).AttachTo("geolocate-button"); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| // --------------- Send a ping to start various action --------
 | // --------------- Send a ping to start various action --------
 | ||||||
| 
 | 
 | ||||||
| locationControl.ping(); | locationControl.ping(); | ||||||
|  |  | ||||||
							
								
								
									
										133
									
								
								vendor/Leaflet.AccuratePosition.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								vendor/Leaflet.AccuratePosition.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | /** | ||||||
|  |  * Leaflet.AccuratePosition aims to provide an accurate device location when simply calling map.locate() doesn’t. | ||||||
|  |  * https://github.com/m165437/Leaflet.AccuratePosition
 | ||||||
|  |  * | ||||||
|  |  * Greg Wilson's getAccurateCurrentPosition() forked to be a Leaflet plugin | ||||||
|  |  * https://github.com/gwilson/getAccurateCurrentPosition
 | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2013 Greg Wilson, 2014 Michael Schmidt-Voigt | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | L.Map.include({ | ||||||
|  |     _defaultAccuratePositionOptions: { | ||||||
|  |         maxWait: 10000, | ||||||
|  |         desiredAccuracy: 20 | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     findAccuratePosition: function (options) { | ||||||
|  | 
 | ||||||
|  |         if (!navigator.geolocation) { | ||||||
|  |             this._handleAccuratePositionError({ | ||||||
|  |                 code: 0, | ||||||
|  |                 message: 'Geolocation not supported.' | ||||||
|  |             }); | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this._accuratePositionEventCount = 0; | ||||||
|  |         this._accuratePositionOptions = L.extend(this._defaultAccuratePositionOptions, options); | ||||||
|  |         this._accuratePositionOptions.enableHighAccuracy = true; | ||||||
|  |         this._accuratePositionOptions.maximumAge = 0; | ||||||
|  | 
 | ||||||
|  |         if (!this._accuratePositionOptions.timeout) | ||||||
|  |             this._accuratePositionOptions.timeout = this._accuratePositionOptions.maxWait; | ||||||
|  | 
 | ||||||
|  |         var onResponse = L.bind(this._checkAccuratePosition, this), | ||||||
|  |             onError = L.bind(this._handleAccuratePositionError, this), | ||||||
|  |             onTimeout = L.bind(this._handleAccuratePositionTimeout, this); | ||||||
|  | 
 | ||||||
|  |         this._accuratePositionWatchId = navigator.geolocation.watchPosition( | ||||||
|  |             onResponse, | ||||||
|  |             onError, | ||||||
|  |             this._accuratePositionOptions); | ||||||
|  | 
 | ||||||
|  |         this._accuratePositionTimerId = setTimeout( | ||||||
|  |             onTimeout, | ||||||
|  |             this._accuratePositionOptions.maxWait); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _handleAccuratePositionTimeout: function() { | ||||||
|  |         navigator.geolocation.clearWatch(this._accuratePositionWatchId); | ||||||
|  | 
 | ||||||
|  |         if (typeof this._lastCheckedAccuratePosition !== 'undefined') { | ||||||
|  |             this._handleAccuratePositionResponse(this._lastCheckedAccuratePosition); | ||||||
|  |         } else { | ||||||
|  |             this._handleAccuratePositionError({ | ||||||
|  |                 code: 3, | ||||||
|  |                 message: 'Timeout expired' | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _cleanUpAccuratePositioning: function () { | ||||||
|  |         clearTimeout(this._accuratePositionTimerId); | ||||||
|  |         navigator.geolocation.clearWatch(this._accuratePositionWatchId); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _checkAccuratePosition: function (pos) { | ||||||
|  |         var accuracyReached = pos.coords.accuracy <= this._accuratePositionOptions.desiredAccuracy; | ||||||
|  | 
 | ||||||
|  |         this._lastCheckedAccuratePosition = pos; | ||||||
|  |         this._accuratePositionEventCount = this._accuratePositionEventCount + 1; | ||||||
|  | 
 | ||||||
|  |         if (accuracyReached && (this._accuratePositionEventCount > 1)) { | ||||||
|  |             this._cleanUpAccuratePositioning(); | ||||||
|  |             this._handleAccuratePositionResponse(pos); | ||||||
|  |         } else { | ||||||
|  |             this._handleAccuratePositionProgress(pos); | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _prepareAccuratePositionData: function (pos) { | ||||||
|  |         var lat = pos.coords.latitude, | ||||||
|  |             lng = pos.coords.longitude, | ||||||
|  |             latlng = new L.LatLng(lat, lng), | ||||||
|  | 
 | ||||||
|  |             latAccuracy = 180 * pos.coords.accuracy / 40075017, | ||||||
|  |             lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * lat), | ||||||
|  | 
 | ||||||
|  |             bounds = L.latLngBounds( | ||||||
|  |                 [lat - latAccuracy, lng - lngAccuracy], | ||||||
|  |                 [lat + latAccuracy, lng + lngAccuracy]); | ||||||
|  | 
 | ||||||
|  |         var data = { | ||||||
|  |             latlng: latlng, | ||||||
|  |             bounds: bounds, | ||||||
|  |             timestamp: pos.timestamp | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         for (var i in pos.coords) { | ||||||
|  |             if (typeof pos.coords[i] === 'number') { | ||||||
|  |                 data[i] = pos.coords[i]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return data; | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _handleAccuratePositionProgress: function (pos) { | ||||||
|  |         var data = this._prepareAccuratePositionData(pos); | ||||||
|  |         this.fire('accuratepositionprogress', data); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _handleAccuratePositionResponse: function (pos) { | ||||||
|  |         var data = this._prepareAccuratePositionData(pos); | ||||||
|  |         this.fire('accuratepositionfound', data); | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     _handleAccuratePositionError: function (error) { | ||||||
|  |         var c = error.code, | ||||||
|  |             message = error.message || | ||||||
|  |                 (c === 1 ? 'permission denied' : | ||||||
|  |                     (c === 2 ? 'position unavailable' : 'timeout')); | ||||||
|  | 
 | ||||||
|  |         this._cleanUpAccuratePositioning(); | ||||||
|  | 
 | ||||||
|  |         this.fire('accuratepositionerror', { | ||||||
|  |             code: c, | ||||||
|  |             message: 'Geolocation error: ' + message + '.' | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | }); | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue