Few UI improvements
* Better info message when fetching geolocation * No longer displaying a marker with heading if heading is unknown. Use a circle marker instead. * Display the accuracy area
This commit is contained in:
parent
0d54fa29a9
commit
989db3b787
@ -2,7 +2,12 @@
|
|||||||
<div class="fill-height fill-width">
|
<div class="fill-height fill-width">
|
||||||
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }">
|
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }">
|
||||||
<v-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
<v-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
||||||
<v-lts :lat-lng="latlng" :options="markerOptions"></v-lts>
|
|
||||||
|
<v-lts v-if="heading" :lat-lng="latlng" :options="markerOptions"></v-lts>
|
||||||
|
<v-lcirclemarker v-else :lat-lng="latlng" :color="markerOptions.color" :fillColor="markerOptions.fillColor" :fillOpacity="1.0" :weight="markerOptions.weight" :radius="markerRadius"></v-lcirclemarker>
|
||||||
|
|
||||||
|
<v-lcircle v-if="shouldDisplayAccuracy" :lat-lng="latlng" :radius="radiusFromAccuracy"></v-lcircle>
|
||||||
|
|
||||||
<v-lmarker v-for="marker in markers" :key="marker.id" :lat-lng="marker.latLng"></v-lmarker>
|
<v-lmarker v-for="marker in markers" :key="marker.id" :lat-lng="marker.latLng"></v-lmarker>
|
||||||
</v-lmap>
|
</v-lmap>
|
||||||
</div>
|
</div>
|
||||||
@ -14,6 +19,8 @@ import iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png';
|
|||||||
import iconUrl from 'leaflet/dist/images/marker-icon.png';
|
import iconUrl from 'leaflet/dist/images/marker-icon.png';
|
||||||
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
|
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
|
||||||
|
|
||||||
|
import { EARTH_RADIUS } from '@/constants';
|
||||||
|
|
||||||
// Fix for a bug in Leaflet default icon
|
// Fix for a bug in Leaflet default icon
|
||||||
// see https://github.com/PaulLeCam/react-leaflet/issues/255#issuecomment-261904061
|
// see https://github.com/PaulLeCam/react-leaflet/issues/255#issuecomment-261904061
|
||||||
delete L.Icon.Default.prototype._getIconUrl;
|
delete L.Icon.Default.prototype._getIconUrl;
|
||||||
@ -26,24 +33,46 @@ L.Icon.Default.mergeOptions({
|
|||||||
export const DEFAULT_ZOOM = 17;
|
export const DEFAULT_ZOOM = 17;
|
||||||
export const MIN_ZOOM = 15;
|
export const MIN_ZOOM = 15;
|
||||||
export const MAX_ZOOM = 18;
|
export const MAX_ZOOM = 18;
|
||||||
export const TILE_SERVER = process.env.TILE_SERVER || 'https://a.tile.thunderforest.com/cycle/{z}/{x}/{y}.png';
|
export const TILE_SERVER = process.env.TILE_SERVER || 'https://a.tile.thunderforest.com/cycle/{z}/{x}/{y}.png';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
accuracy: {
|
||||||
|
type: Number,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
heading: Number,
|
heading: Number,
|
||||||
lat: Number,
|
lat: Number,
|
||||||
lng: Number,
|
lng: Number,
|
||||||
markers: Array,
|
markers: Array,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
radiusFromAccuracy() {
|
||||||
|
if (this.accuracy) {
|
||||||
|
return this.accuracy / (
|
||||||
|
(EARTH_RADIUS * 2 * Math.PI * Math.cos(this.lat)) /
|
||||||
|
(2 ** (this.zoom + 8))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
shouldDisplayAccuracy() {
|
||||||
|
return (
|
||||||
|
this.accuracy &&
|
||||||
|
this.accuracy < 100 &&
|
||||||
|
this.radiusFromAccuracy > this.markerRadius
|
||||||
|
);
|
||||||
|
},
|
||||||
latlng() {
|
latlng() {
|
||||||
return [this.lat, this.lng];
|
return [this.lat, this.lng];
|
||||||
},
|
},
|
||||||
markerOptions() {
|
markerOptions() {
|
||||||
return {
|
return {
|
||||||
fillColor: '#00ff00',
|
fillColor: '#00ff00',
|
||||||
|
color: '#000000',
|
||||||
heading: this.heading,
|
heading: this.heading,
|
||||||
|
weight: 1,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -51,6 +80,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
|
attribution: 'Map data © <a href="http://openstreetmap.org">OpenStreetMap</a> contributors',
|
||||||
zoom: DEFAULT_ZOOM,
|
zoom: DEFAULT_ZOOM,
|
||||||
|
markerRadius: 10.0,
|
||||||
minZoom: MIN_ZOOM,
|
minZoom: MIN_ZOOM,
|
||||||
maxZoom: MAX_ZOOM,
|
maxZoom: MAX_ZOOM,
|
||||||
tileServer: TILE_SERVER,
|
tileServer: TILE_SERVER,
|
||||||
|
6
src/constants.js
Normal file
6
src/constants.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export const MOCK_LOCATION = false;
|
||||||
|
export const MOCK_LOCATION_UPDATE_INTERVAL = 30 * 1000;
|
||||||
|
|
||||||
|
export const UPDATE_REPORTS_DISTANCE_THRESHOLD = 500;
|
||||||
|
|
||||||
|
export const EARTH_RADIUS = 6378137;
|
@ -1,7 +1,7 @@
|
|||||||
// Keys should be sorted alphabetically
|
// Keys should be sorted alphabetically
|
||||||
export default {
|
export default {
|
||||||
geolocation: {
|
geolocation: {
|
||||||
enable: 'Please accept the geolocation permission request to use the app.',
|
fetching: 'Fetching current position…',
|
||||||
unavailable: 'Sorry, geolocation is not available in your browser.',
|
unavailable: 'Sorry, geolocation is not available in your browser.',
|
||||||
},
|
},
|
||||||
menu: {
|
menu: {
|
||||||
|
@ -19,6 +19,8 @@ Vue.use(Vuetify);
|
|||||||
Vue.component('v-lmap', Vue2Leaflet.LMap);
|
Vue.component('v-lmap', Vue2Leaflet.LMap);
|
||||||
Vue.component('v-ltilelayer', Vue2Leaflet.LTileLayer);
|
Vue.component('v-ltilelayer', Vue2Leaflet.LTileLayer);
|
||||||
Vue.component('v-lmarker', Vue2Leaflet.LMarker);
|
Vue.component('v-lmarker', Vue2Leaflet.LMarker);
|
||||||
|
Vue.component('v-lcirclemarker', Vue2Leaflet.LCircleMarker);
|
||||||
|
Vue.component('v-lcircle', Vue2Leaflet.LCircle);
|
||||||
Vue.component('v-lpolyline', Vue2Leaflet.LPolyline);
|
Vue.component('v-lpolyline', Vue2Leaflet.LPolyline);
|
||||||
Vue.component('v-lts', Vue2LeafletTracksymbol);
|
Vue.component('v-lts', Vue2LeafletTracksymbol);
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { EARTH_RADIUS } from '@/constants';
|
||||||
|
|
||||||
export function distance(latLng1, latLng2) {
|
export function distance(latLng1, latLng2) {
|
||||||
const lat1 = (latLng1[0] * Math.PI) / 180;
|
const lat1 = (latLng1[0] * Math.PI) / 180;
|
||||||
const lng1 = (latLng1[1] * Math.PI) / 180;
|
const lng1 = (latLng1[1] * Math.PI) / 180;
|
||||||
@ -10,7 +12,6 @@ export function distance(latLng1, latLng2) {
|
|||||||
(Math.cos(lat1) * Math.cos(lat2) * (Math.sin((lng2 - lng1) / 2.0) ** 2))
|
(Math.cos(lat1) * Math.cos(lat2) * (Math.sin((lng2 - lng1) / 2.0) ** 2))
|
||||||
);
|
);
|
||||||
const c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
const c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
const EARTH_RADIUS = 6371000;
|
|
||||||
|
|
||||||
return EARTH_RADIUS * c;
|
return EARTH_RADIUS * c;
|
||||||
}
|
}
|
||||||
@ -22,9 +23,10 @@ export function mockLocation() {
|
|||||||
const LNG_MAX = 2.392742;
|
const LNG_MAX = 2.392742;
|
||||||
return {
|
return {
|
||||||
coords: {
|
coords: {
|
||||||
|
accuracy: 10, // In meters
|
||||||
latitude: (Math.random() * (LAT_MAX - LAT_MIN)) + LAT_MIN,
|
latitude: (Math.random() * (LAT_MAX - LAT_MIN)) + LAT_MIN,
|
||||||
longitude: (Math.random() * (LNG_MAX - LNG_MIN)) + LNG_MIN,
|
longitude: (Math.random() * (LNG_MAX - LNG_MIN)) + LNG_MIN,
|
||||||
heading: 20 * (Math.PI / 180),
|
heading: null, // 20 * (Math.PI / 180),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<v-container fluid fill-height class="no-padding">
|
<v-container fluid fill-height class="no-padding">
|
||||||
<v-layout row wrap fill-height>
|
<v-layout row wrap fill-height>
|
||||||
<v-flex xs12 fill-height v-if="lat && lng">
|
<v-flex xs12 fill-height v-if="lat && lng">
|
||||||
<Map :lat="lat" :lng="lng" :heading="heading" :markers="reportsMarkers"></Map>
|
<Map :lat="lat" :lng="lng" :heading="heading" :accuracy="accuracy" :markers="reportsMarkers"></Map>
|
||||||
<v-btn
|
<v-btn
|
||||||
fixed
|
fixed
|
||||||
dark
|
dark
|
||||||
@ -18,10 +18,15 @@
|
|||||||
<ReportDialog v-model="dialog" :lat="lat" :lng="lng"></ReportDialog>
|
<ReportDialog v-model="dialog" :lat="lat" :lng="lng"></ReportDialog>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 fill-height v-else class="pa-3">
|
<v-flex xs12 fill-height v-else class="pa-3">
|
||||||
<p>{{ error }}</p>
|
<template v-if="error">
|
||||||
<p class="text-xs-center">
|
<p class="text-xs-center">{{ error }}</p>
|
||||||
<v-btn color="blue" dark @click="initializePositionWatching">Retry</v-btn>
|
<p class="text-xs-center">
|
||||||
</p>
|
<v-btn color="blue" dark @click="initializePositionWatching">Retry</v-btn>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<p class="text-xs-center">{{ $t('geolocation.fetching') }}</p>
|
||||||
|
</template>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
@ -32,12 +37,9 @@ import NoSleep from 'nosleep.js';
|
|||||||
|
|
||||||
import Map from '@/components/Map.vue';
|
import Map from '@/components/Map.vue';
|
||||||
import ReportDialog from '@/components/ReportDialog/index.vue';
|
import ReportDialog from '@/components/ReportDialog/index.vue';
|
||||||
|
import * as constants from '@/constants';
|
||||||
import { distance, mockLocation } from '@/tools';
|
import { distance, mockLocation } from '@/tools';
|
||||||
|
|
||||||
const MOCK_LOCATION = false;
|
|
||||||
const MOCK_LOCATION_UPDATE_INTERVAL = 30 * 1000;
|
|
||||||
const UPDATE_REPORTS_DISTANCE_THRESHOLD = 500;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Map,
|
Map,
|
||||||
@ -62,8 +64,9 @@ export default {
|
|||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
accuracy: null,
|
||||||
dialog: false,
|
dialog: false,
|
||||||
error: this.$t('geolocation.enable'),
|
error: null,
|
||||||
heading: null,
|
heading: null,
|
||||||
lat: null,
|
lat: null,
|
||||||
lng: null,
|
lng: null,
|
||||||
@ -75,11 +78,11 @@ export default {
|
|||||||
initializePositionWatching() {
|
initializePositionWatching() {
|
||||||
this.disablePositionWatching(); // Ensure at most one at the same time
|
this.disablePositionWatching(); // Ensure at most one at the same time
|
||||||
|
|
||||||
if (MOCK_LOCATION) {
|
if (constants.MOCK_LOCATION) {
|
||||||
this.setPosition(mockLocation());
|
this.setPosition(mockLocation());
|
||||||
this.watchID = setInterval(
|
this.watchID = setInterval(
|
||||||
() => this.setPosition(mockLocation()),
|
() => this.setPosition(mockLocation()),
|
||||||
MOCK_LOCATION_UPDATE_INTERVAL,
|
constants.MOCK_LOCATION_UPDATE_INTERVAL,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (!('geolocation' in navigator)) {
|
if (!('geolocation' in navigator)) {
|
||||||
@ -99,7 +102,7 @@ export default {
|
|||||||
},
|
},
|
||||||
disablePositionWatching() {
|
disablePositionWatching() {
|
||||||
if (this.watchID !== null) {
|
if (this.watchID !== null) {
|
||||||
if (MOCK_LOCATION) {
|
if (constants.MOCK_LOCATION) {
|
||||||
clearInterval(this.watchID);
|
clearInterval(this.watchID);
|
||||||
} else {
|
} else {
|
||||||
navigator.geolocation.clearWatch(this.watchID);
|
navigator.geolocation.clearWatch(this.watchID);
|
||||||
@ -115,17 +118,14 @@ export default {
|
|||||||
[this.lat, this.lng],
|
[this.lat, this.lng],
|
||||||
[position.coords.latitude, position.coords.longitude],
|
[position.coords.latitude, position.coords.longitude],
|
||||||
);
|
);
|
||||||
if (distanceFromPreviousPoint > UPDATE_REPORTS_DISTANCE_THRESHOLD) {
|
if (distanceFromPreviousPoint > constants.UPDATE_REPORTS_DISTANCE_THRESHOLD) {
|
||||||
this.$store.dispatch('fetchReports');
|
this.$store.dispatch('fetchReports');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.lat = position.coords.latitude;
|
this.lat = position.coords.latitude;
|
||||||
this.lng = position.coords.longitude;
|
this.lng = position.coords.longitude;
|
||||||
if (position.coords.heading) {
|
this.heading = position.coords.heading ? position.coords.heading : null;
|
||||||
this.heading = position.coords.heading;
|
this.accuracy = position.coords.accuracy ? position.coords.accuracy : null;
|
||||||
} else {
|
|
||||||
this.heading = null;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setNoSleep() {
|
setNoSleep() {
|
||||||
this.noSleep = new NoSleep();
|
this.noSleep = new NoSleep();
|
||||||
|
Loading…
Reference in New Issue
Block a user