Add geolocation capture feature on record geometry field
This commit is contained in:
committed by
Mumbi Francis
parent
0273c5244b
commit
fe07bb5073
@@ -1,21 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<b-form-checkbox v-model="f.options.prefillWithCurrentLocation">
|
||||
{{ $t('prefillWithCurrentLocation') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-checkbox v-model="f.options.hideCurrentLocationButton">
|
||||
{{ $t('hideCurrentLocationButton') }}
|
||||
</b-form-checkbox>
|
||||
|
||||
<b-form-group
|
||||
:label="$t('kind.geometry.initialZoomAndPosition')"
|
||||
:label="$t('initialZoomAndPosition')"
|
||||
class="mb-0"
|
||||
>
|
||||
<l-map
|
||||
ref="map"
|
||||
:zoom="zoom"
|
||||
:center="center"
|
||||
class="w-100"
|
||||
style="height: 60vh;"
|
||||
@update:zoom="f.options.zoom = $event"
|
||||
@update:center="f.options.center = $event"
|
||||
@locationfound="onLocationFound"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
:attribution="attribution"
|
||||
/>
|
||||
<l-control class="leaflet-bar">
|
||||
<a
|
||||
:title="$t('tooltip.goToCurrentLocation')"
|
||||
role="button"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
@click="goToCurrentLocation"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</a>
|
||||
</l-control>
|
||||
</l-map>
|
||||
</b-form-group>
|
||||
</div>
|
||||
@@ -23,10 +46,16 @@
|
||||
|
||||
<script>
|
||||
import base from './base'
|
||||
import { LControl } from 'vue2-leaflet'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
namespaces: 'field',
|
||||
keyPrefix: 'kind.geometry',
|
||||
},
|
||||
|
||||
components: {
|
||||
LControl,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
@@ -46,5 +75,16 @@ export default {
|
||||
return this.f.options.zoom || 3
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
goToCurrentLocation () {
|
||||
this.$refs.map.mapObject.locate()
|
||||
},
|
||||
|
||||
onLocationFound ({ latitude, longitude }) {
|
||||
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
|
||||
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -33,13 +33,14 @@
|
||||
<div class="d-flex w-100">
|
||||
<b-button
|
||||
v-if="field.isMulti"
|
||||
variant="primary"
|
||||
rounded
|
||||
:title="$t('tooltip.openMap')"
|
||||
variant="light"
|
||||
class="w-100"
|
||||
@click="openMap"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'map-marked-alt']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</b-button>
|
||||
</div>
|
||||
@@ -55,15 +56,31 @@
|
||||
<b-form-input
|
||||
v-model="localValue[ctx.index].coordinates[0]"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
number
|
||||
:placeholder="$t('latitude')"
|
||||
/>
|
||||
<b-form-input
|
||||
v-model="localValue[ctx.index].coordinates[1]"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
number
|
||||
:placeholder="$t('longitude')"
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
v-if="!field.options.hideCurrentLocationButton"
|
||||
:title="$t('tooltip.useCurrentLocation')"
|
||||
variant="light"
|
||||
class="d-flex align-items-center"
|
||||
@click="useCurrentLocation(ctx.index)"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
</multi>
|
||||
|
||||
@@ -72,23 +89,40 @@
|
||||
<b-form-input
|
||||
v-model="localValue.coordinates[0]"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
number
|
||||
:placeholder="$t('latitude')"
|
||||
/>
|
||||
<b-form-input
|
||||
v-model="localValue.coordinates[1]"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
number
|
||||
:placeholder="$t('longitude')"
|
||||
/>
|
||||
<b-input-group-append>
|
||||
<b-button
|
||||
:title="$t('tooltip.openMap')"
|
||||
variant="light"
|
||||
rounded
|
||||
class="d-flex align-items-center"
|
||||
@click="openMap"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'map-marked-alt']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</b-button>
|
||||
|
||||
<b-button
|
||||
v-if="!field.options.hideCurrentLocationButton"
|
||||
:title="$t('tooltip.useCurrentLocation')"
|
||||
variant="light"
|
||||
class="d-flex align-items-center"
|
||||
@click="useCurrentLocation()"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</b-button>
|
||||
</b-input-group-append>
|
||||
@@ -100,16 +134,11 @@
|
||||
<b-modal
|
||||
v-model="map.show"
|
||||
size="lg"
|
||||
title="Map"
|
||||
body-class="p-0"
|
||||
hide-header
|
||||
>
|
||||
<template #modal-footer>
|
||||
<h6
|
||||
class="w-100"
|
||||
>
|
||||
{{ $t('clickToPlaceMarker') }}
|
||||
</h6>
|
||||
{{ $t('clickToPlaceMarker') }}
|
||||
</template>
|
||||
|
||||
<l-map
|
||||
@@ -118,6 +147,7 @@
|
||||
:center="map.center"
|
||||
style="height: 75vh; width: 100%; cursor: pointer;"
|
||||
@click="placeMarker"
|
||||
@locationfound="onLocationFound"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
@@ -129,6 +159,19 @@
|
||||
:lat-lng="marker"
|
||||
@click="removeMarker(i)"
|
||||
/>
|
||||
<l-control class="leaflet-bar">
|
||||
<a
|
||||
:title="$t('tooltip.goToCurrentLocation')"
|
||||
role="button"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
@click="goToCurrentLocation"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</a>
|
||||
</l-control>
|
||||
</l-map>
|
||||
</b-modal>
|
||||
</b-form-group>
|
||||
@@ -136,6 +179,7 @@
|
||||
<script>
|
||||
import base from './base'
|
||||
import { latLng } from 'leaflet'
|
||||
import { LControl } from 'vue2-leaflet'
|
||||
|
||||
export default {
|
||||
i18nOptions: {
|
||||
@@ -143,6 +187,10 @@ export default {
|
||||
keyPrefix: 'kind.geometry',
|
||||
},
|
||||
|
||||
components: {
|
||||
LControl,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
@@ -188,6 +236,10 @@ export default {
|
||||
} else {
|
||||
this.localValue = JSON.parse(this.value || '{"coordinates":[]}')
|
||||
}
|
||||
|
||||
if (this.field.options.prefillWithCurrentLocation) {
|
||||
this.useCurrentLocation()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -210,15 +262,23 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
placeMarker (e) {
|
||||
let { lat = 0, lng = 0 } = e.latlng || {}
|
||||
lat = Math.round(lat * 1e7) / 1e7
|
||||
lng = Math.round(lng * 1e7) / 1e7
|
||||
placeMarker (e, index) {
|
||||
const { lat = 0, lng = 0 } = e.latlng || {}
|
||||
const coords = {
|
||||
coordinates: [
|
||||
Math.round(lat * 1e7) / 1e7,
|
||||
Math.round(lng * 1e7) / 1e7,
|
||||
],
|
||||
}
|
||||
|
||||
if (this.field.isMulti) {
|
||||
this.localValue.push({ coordinates: [lat, lng] })
|
||||
if (index >= 0) {
|
||||
this.localValue.splice(index, 1, coords)
|
||||
} else {
|
||||
this.localValue.push(coords)
|
||||
}
|
||||
} else {
|
||||
this.localValue = { coordinates: [lat, lng] }
|
||||
this.localValue = coords
|
||||
}
|
||||
},
|
||||
|
||||
@@ -229,6 +289,48 @@ export default {
|
||||
this.localValue = { coordinates: [] }
|
||||
}
|
||||
},
|
||||
|
||||
useCurrentLocation (index) {
|
||||
try {
|
||||
if (!navigator.geolocation) {
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.notSupported'))()
|
||||
}
|
||||
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
({ coords }) => {
|
||||
const latlng = { lat: coords.latitude, lng: coords.longitude }
|
||||
this.placeMarker({ latlng }, index)
|
||||
},
|
||||
error => {
|
||||
switch (error.code) {
|
||||
case error.PERMISSION_DENIED:
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.permissionDenied'))()
|
||||
break
|
||||
case error.POSITION_UNAVAILABLE:
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.positionUnavailable'))()
|
||||
break
|
||||
case error.TIMEOUT:
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.timeout'))()
|
||||
break
|
||||
default:
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.unknownError'))()
|
||||
break
|
||||
}
|
||||
},
|
||||
)
|
||||
} catch (error) {
|
||||
this.toastErrorHandler(this.$t('notification:field-geometry.geolocationErrors.errorOccurred'))()
|
||||
}
|
||||
},
|
||||
|
||||
goToCurrentLocation () {
|
||||
this.$refs.map.mapObject.locate()
|
||||
},
|
||||
|
||||
onLocationFound ({ latitude, longitude }) {
|
||||
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
|
||||
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
:zoom="map.zoom"
|
||||
:center="map.center"
|
||||
style="height: 75vh; width: 100%;"
|
||||
@locationfound="onLocationFound"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
@@ -41,6 +42,19 @@
|
||||
:lat-lng="marker"
|
||||
:opacity="i == localValueIndex ? 1.0 : 0.6"
|
||||
/>
|
||||
<l-control class="leaflet-bar">
|
||||
<a
|
||||
:title="$t('tooltip.goToCurrentLocation')"
|
||||
role="button"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
@click="goToCurrentLocation"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</a>
|
||||
</l-control>
|
||||
</l-map>
|
||||
</b-modal>
|
||||
|
||||
@@ -50,15 +64,20 @@
|
||||
<script>
|
||||
import base from './base'
|
||||
import { latLng } from 'leaflet'
|
||||
import { LControl } from 'vue2-leaflet'
|
||||
|
||||
export default {
|
||||
extends: base,
|
||||
|
||||
i18nOptions: {
|
||||
namespaces: 'field',
|
||||
keyPrefix: 'kind.geometry',
|
||||
},
|
||||
|
||||
components: {
|
||||
LControl,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
return {
|
||||
map: {
|
||||
@@ -103,6 +122,15 @@ export default {
|
||||
return latLng(lat, lng)
|
||||
}
|
||||
},
|
||||
|
||||
goToCurrentLocation () {
|
||||
this.$refs.map.mapObject.locate()
|
||||
},
|
||||
|
||||
onLocationFound ({ latitude, longitude }) {
|
||||
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
|
||||
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
:bounds="map.bounds"
|
||||
:max-bounds="map.bounds"
|
||||
class="w-100 h-100"
|
||||
@locationfound="onLocationFound"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
@@ -43,6 +44,19 @@
|
||||
:lat-lng="marker.value"
|
||||
:icon="getIcon(marker)"
|
||||
/>
|
||||
<l-control class="leaflet-bar">
|
||||
<a
|
||||
:title="$t('tooltip.goToCurrentLocation')"
|
||||
role="button"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
@click="goToCurrentLocation"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</a>
|
||||
</l-control>
|
||||
</l-map>
|
||||
</div>
|
||||
</template>
|
||||
@@ -51,16 +65,17 @@
|
||||
|
||||
<script>
|
||||
import { divIcon, latLng, latLngBounds } from 'leaflet'
|
||||
import {
|
||||
LPolygon,
|
||||
} from 'vue2-leaflet'
|
||||
import { LPolygon, LControl } from 'vue2-leaflet'
|
||||
import { compose, NoID } from '@cortezaproject/corteza-js'
|
||||
import { mapGetters, mapActions } from 'vuex'
|
||||
import { evaluatePrefilter } from 'corteza-webapp-compose/src/lib/record-filter'
|
||||
import base from './base'
|
||||
|
||||
export default {
|
||||
components: { LPolygon },
|
||||
components: {
|
||||
LPolygon,
|
||||
LControl,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
@@ -228,6 +243,15 @@ export default {
|
||||
enableMap () {
|
||||
if (this.editable) this.$refs.map.mapObject._handlers.forEach(handler => handler.enable())
|
||||
},
|
||||
|
||||
goToCurrentLocation () {
|
||||
this.$refs.map.mapObject.locate()
|
||||
},
|
||||
|
||||
onLocationFound ({ latitude, longitude }) {
|
||||
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
|
||||
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -14,11 +14,25 @@
|
||||
@update:zoom="zoomUpdated"
|
||||
@update:center="updateCenter"
|
||||
@update:bounds="boundsUpdated"
|
||||
@locationfound="onLocationFound"
|
||||
>
|
||||
<l-tile-layer
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
:attribution="map.attribution"
|
||||
/>
|
||||
<l-control class="leaflet-bar">
|
||||
<a
|
||||
:title="$t('tooltip.goToCurrentLocation')"
|
||||
role="button"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
@click="goToCurrentLocation"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'location-arrow']"
|
||||
class="text-primary"
|
||||
/>
|
||||
</a>
|
||||
</l-control>
|
||||
</l-map>
|
||||
<b-form-text id="password-help-block">
|
||||
{{ $t('geometry.mapHelpText') }}
|
||||
@@ -130,16 +144,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { latLng } from 'leaflet'
|
||||
import base from '../base'
|
||||
import { latLng } from 'leaflet'
|
||||
import { LControl } from 'vue2-leaflet'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
|
||||
i18nOptions: {
|
||||
namespaces: 'block',
|
||||
},
|
||||
|
||||
components: {
|
||||
LControl,
|
||||
},
|
||||
|
||||
extends: base,
|
||||
|
||||
data () {
|
||||
@@ -166,6 +183,7 @@ export default {
|
||||
return latLng(lat, lng)
|
||||
}
|
||||
},
|
||||
|
||||
updateCenter (coordinates) {
|
||||
let { lat = 0, lng = 0 } = coordinates || {}
|
||||
|
||||
@@ -174,14 +192,17 @@ export default {
|
||||
|
||||
this.options.center = [lat, lng]
|
||||
},
|
||||
|
||||
boundsUpdated (coordinates) {
|
||||
this.bounds = coordinates
|
||||
|
||||
this.updateBounds(this.options.lockBounds)
|
||||
},
|
||||
|
||||
zoomUpdated (zoom) {
|
||||
this.options.zoomStarting = zoom
|
||||
},
|
||||
|
||||
updateBounds (value) {
|
||||
if (value) {
|
||||
const bounds = this.bounds || this.$refs.map.mapObject.getBounds()
|
||||
@@ -192,6 +213,15 @@ export default {
|
||||
this.options.bounds = null
|
||||
}
|
||||
},
|
||||
|
||||
goToCurrentLocation () {
|
||||
this.$refs.map.mapObject.locate()
|
||||
},
|
||||
|
||||
onLocationFound ({ latitude, longitude }) {
|
||||
const zoom = this.$refs.map.mapObject._zoom >= 13 ? this.$refs.map.mapObject._zoom : 13
|
||||
this.$refs.map.mapObject.flyTo([latitude, longitude], zoom)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
faSync,
|
||||
faExclamationTriangle,
|
||||
faEllipsisV,
|
||||
faLocationArrow,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import {
|
||||
@@ -172,4 +173,5 @@ library.add(
|
||||
faExclamationTriangle,
|
||||
faSync,
|
||||
faEllipsisV,
|
||||
faLocationArrow,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,8 @@ interface GeometryOptions extends Options {
|
||||
center: number[];
|
||||
zoom: number;
|
||||
multiDelimiter: string;
|
||||
prefillWithCurrentLocation: boolean;
|
||||
hideCurrentLocationButton: boolean;
|
||||
}
|
||||
|
||||
const defaults = (): Readonly<GeometryOptions> => Object.freeze({
|
||||
@@ -14,6 +16,8 @@ const defaults = (): Readonly<GeometryOptions> => Object.freeze({
|
||||
center: [30, 30],
|
||||
zoom: 3,
|
||||
multiDelimiter: '\n',
|
||||
prefillWithCurrentLocation: false,
|
||||
hideCurrentLocationButton: false,
|
||||
})
|
||||
|
||||
export class ModuleFieldGeometry extends ModuleField {
|
||||
@@ -32,6 +36,8 @@ export class ModuleFieldGeometry extends ModuleField {
|
||||
|
||||
Apply(this.options, o, String, 'multiDelimiter')
|
||||
Apply(this.options, o, Number, 'zoom')
|
||||
Apply(this.options, o, Boolean, 'prefillWithCurrentLocation')
|
||||
Apply(this.options, o, Boolean, 'hideCurrentLocationButton')
|
||||
|
||||
if (o.center) {
|
||||
this.options.center = o.center
|
||||
|
||||
@@ -143,8 +143,14 @@ kind:
|
||||
label: Geometry
|
||||
latitude: Latitude
|
||||
longitude: Longitude
|
||||
clickToPlaceMarker: Click to place a marker or click on a marker to remove it
|
||||
clickToPlaceMarker: Click to place/remove a marker
|
||||
initialZoomAndPosition: Set initial map zoom and position
|
||||
prefillWithCurrentLocation: Prefill with current location
|
||||
hideCurrentLocationButton: Hide current location button
|
||||
tooltip:
|
||||
goToCurrentLocation: Go to current location
|
||||
useCurrentLocation: Use current location
|
||||
openMap: Open map
|
||||
noPermission: No permission to read field value
|
||||
no-items-found: No items found
|
||||
options:
|
||||
|
||||
@@ -28,6 +28,14 @@ field:
|
||||
field-datetime:
|
||||
valueNotFuture: Past value on future only field
|
||||
valueNotPast: Future value on past only field
|
||||
field-geometry:
|
||||
geolocationErrors:
|
||||
permissionDenied: User denied the request for geolocation
|
||||
positionUnavailable: Location information is unavailable
|
||||
timeout: The request to get user location timed out
|
||||
unknownError: An unknown error occurred
|
||||
notSupported: Geolocation is not supported by this browser
|
||||
errorOccurred: An error occurred while getting user location
|
||||
general:
|
||||
composeAccessNotAllowed: Not allowed to access Compose
|
||||
error: Error
|
||||
|
||||
Reference in New Issue
Block a user