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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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};
|
||||||
|
|
||||||
|
|
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…
Reference in a new issue