3
0

Add geolocation capture feature on record geometry field

This commit is contained in:
Mumbi Francis
2023-03-20 17:17:33 +03:00
committed by Mumbi Francis
parent 0273c5244b
commit fe07bb5073
9 changed files with 272 additions and 26 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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