Add geolocation button which uses device GPS

This commit is contained in:
Pieter Vander Vennet 2020-06-28 02:42:22 +02:00
parent 57c9fcc5aa
commit a566ab6725
6 changed files with 299 additions and 32 deletions

107
Logic/GeoLocationHandler.ts Normal file
View 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();
}
}
}

View file

@ -10,7 +10,7 @@ export class LayerUpdater {
private _layers: FilteredLayer[]; private _layers: FilteredLayer[];
public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false); public readonly runningQuery: UIEventSource<boolean> = new UIEventSource<boolean>(false);
/** /**
* The previous bounds for which the query has been run * The previous bounds for which the query has been run
*/ */
@ -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};

View file

@ -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 {

View file

@ -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>

View file

@ -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
View file

@ -0,0 +1,133 @@
/**
* Leaflet.AccuratePosition aims to provide an accurate device location when simply calling map.locate() doesnt.
* 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 + '.'
});
}
});