diff --git a/src/components/Map/EditingToolbar.vue b/src/components/Map/EditingToolbar.vue index 97c86db1a01faad1f264161aaa6288167a6b7d62..33c86323c46c203d1fb6e3550130a6479a1e0d99 100644 --- a/src/components/Map/EditingToolbar.vue +++ b/src/components/Map/EditingToolbar.vue @@ -117,8 +117,9 @@ export default { .editionToolbar{ position: absolute; - // each button have .5em space between, zoom buttons are 60px high and full screen button is 34px high - top: calc(2em + 60px + 34px + 28px); + // each button have (more or less depends on borders) .5em space between + // zoom buttons are 60px high, geolocation and full screen button is 34px high with borders + top: calc(2em + 60px + 34px + 64px); right: 6px; border: 2px solid rgba(0,0,0,.2); border-radius: 4px; diff --git a/src/components/Map/Geocoder.vue b/src/components/Map/Geocoder.vue index d4644cb993d52fcc035444d3103c01755aec27d8..028a6a7598288ccff9789868afffb60e819dd73b 100644 --- a/src/components/Map/Geocoder.vue +++ b/src/components/Map/Geocoder.vue @@ -143,8 +143,9 @@ export default { .geocoder-container { position: absolute; right: 0.5em; - // each button have .5em space between, zoom buttons are 60px high and full screen button is 34px high - top: calc(1.5em + 60px + 34px); + // each button have (more or less depends on borders) .5em space between + // zoom buttons are 60px high, geolocation and full screen button is 34px high with borders + top: calc(1.6em + 60px + 34px + 34px); pointer-events: auto; z-index: 1000; border: 2px solid rgba(0,0,0,.2); diff --git a/src/components/Map/Geolocation.vue b/src/components/Map/Geolocation.vue new file mode 100644 index 0000000000000000000000000000000000000000..cc83f8c98773273d69827886517ef40b739ad711 --- /dev/null +++ b/src/components/Map/Geolocation.vue @@ -0,0 +1,66 @@ +<template> + <div class="geolocation-container"> + <button + :class="['button-geolocation', { tracking }]" + @click.prevent="toggleTracking" + > + <i class="crosshairs icon" /> + </button> + </div> +</template> + +<script> + +import mapService from '@/services/map-service'; + +export default { + name: 'Geolocation', + + data() { + return { + tracking: false, + }; + }, + + methods: { + toggleTracking() { + this.tracking = !this.tracking; + mapService.toggleGeolocation(this.tracking); + }, + } +}; +</script> + +<style> +div.geolocation-container { + position: absolute; + right: 6px; + z-index: 1000; + border: 2px solid rgba(0,0,0,.2); + background-clip: padding-box; + padding: 0; + border-radius: 4px; +} +button.button-geolocation { + border: none; + padding: 0; + margin: 0; + text-align: center; + background-color: #fff; + color: rgb(39, 39, 39); + width: 30px; + height: 30px; + font: 700 18px Lucida Console,Monaco,monospace; + border-radius: 4px; + line-height: 1.15; + cursor: pointer; +} +button.button-geolocation.tracking { + background-color: rgba(255, 145, 0, 0.904); + color: #fff; +} +button.button-geolocation i { + margin: 0; + vertical-align: top; /* strangely top is the only value that center at middle */ +} +</style> diff --git a/src/services/map-service.js b/src/services/map-service.js index 5aefb5eada744855a97794e1990b04a83609d493..01d6cd8b01a9f6c1a6f8a5e22a57458e701bcf28 100644 --- a/src/services/map-service.js +++ b/src/services/map-service.js @@ -2,7 +2,7 @@ import TileWMS from 'ol/source/TileWMS'; import { View, Map } from 'ol'; import { ScaleLine, Zoom, Attribution, FullScreen } from 'ol/control'; import TileLayer from 'ol/layer/Tile'; -import { transform, transformExtent } from 'ol/proj.js'; +import { transform, transformExtent, fromLonLat } from 'ol/proj.js'; import { defaults } from 'ol/interaction'; import XYZ from 'ol/source/XYZ'; import VectorTileLayer from 'ol/layer/VectorTile'; @@ -16,8 +16,10 @@ import { import { asArray } from 'ol/color'; import VectorSource from 'ol/source/Vector'; import VectorLayer from 'ol/layer/Vector'; -import { fromLonLat } from 'ol/proj.js'; import OverlayPositioning from 'ol/OverlayPositioning'; +import Geolocation from 'ol/Geolocation.js'; +import Feature from 'ol/Feature.js'; +import Point from 'ol/geom/Point.js'; import axios from '@/axios-client.js'; import router from '@/router'; @@ -26,6 +28,19 @@ import { statusChoices } from '@/utils'; let dictLayersToMap = {}; +const geolocationStyle = new Style({ + image: new Circle({ + radius: 6, + fill: new Fill({ + color: '#3399CC', + }), + stroke: new Stroke({ + color: '#fff', + width: 2, + }), + }), +}); + const mapService = { layers: [], @@ -39,6 +54,9 @@ const mapService = { queryParams: {}, + geolocation: undefined, + + geolocationSource: null, getMap() { return this.map; @@ -58,6 +76,7 @@ const mapService = { zoom, zoomControl = true, fullScreenControl = false, + geolocationControl = false, interactions = { doubleClickZoom: false, mouseWheelZoom: false, dragPan: true }, controls = [ new Attribution({ collapsible: false }), @@ -92,6 +111,10 @@ const mapService = { if (zoomControl) { this.map.addControl(new Zoom({ zoomInTipLabel: 'Zoomer', zoomOutTipLabel: 'Dézoomer' })); } + if (geolocationControl) { + this.initGeolocation(); + } + this.map.once('rendercomplete', () => { this.map.updateSize(); }); @@ -120,6 +143,68 @@ const mapService = { return this.map; }, + initGeolocation() { + this.geolocation = new Geolocation({ + // enableHighAccuracy must be set to true to have the heading value. + trackingOptions: { + enableHighAccuracy: true, + }, + projection: this.map.getView().getProjection(), + }); + + // handle this.geolocation error. + this.geolocation.on('error', (error) => { + console.error(error.message); + }); + + /* const accuracyFeature = new Feature(); + this.geolocation.on('change:accuracyGeometry', () => { + accuracyFeature.setGeometry(this.geolocation.getAccuracyGeometry()); + }); */ + + const positionFeature = new Feature(); + positionFeature.setStyle( geolocationStyle ); + + this.geolocation.on('change:position', () => { + const coordinates = this.geolocation.getPosition(); + positionFeature.setGeometry(coordinates ? new Point(coordinates) : null); + if (coordinates) { + this.map.getView().animate( + { zoom: 16 }, + { center: coordinates }, + { duration: 1000 } + ); + } + }); + + this.geolocationSource = new VectorSource({ + features: [positionFeature], + //features: [accuracyFeature, positionFeature], + }); + new VectorLayer({ + map: this.map, + source: this.geolocationSource, + }); + }, + + displayGeolocationPoint(isVisible) { + let features = this.geolocationSource.getFeatures(); + if (!features) return; + const hiddenStyle = new Style(); // hide the feature + for (let i = 0; i < features.length; i++) { + features[i].setStyle(isVisible ? geolocationStyle : hiddenStyle); + } + }, + + + toggleGeolocation(isTracking) { + if (this.geolocation) { + this.geolocation.setTracking(isTracking); + if (this.geolocationSource) { + this.displayGeolocationPoint(isTracking); + } + } + }, addRouterToPopup({ featureId, featureTypeSlug, index }) { diff --git a/src/store/modules/map.store.js b/src/store/modules/map.store.js index f20bf643ccc8804c7f2dd6b794d5860b875991a4..8421ef3663ce34023d5df7498f797a598fc15681 100644 --- a/src/store/modules/map.store.js +++ b/src/store/modules/map.store.js @@ -111,7 +111,8 @@ const map = { maxZoom: options.maxZoom || rootState.projects.project.map_max_zoom_level, controls: options.controls, zoomControl: options.zoomControl, - interactions : { doubleClickZoom :false, mouseWheelZoom:true, dragPan:true } + interactions : { doubleClickZoom :false, mouseWheelZoom:true, dragPan:true }, + geolocationControl: true, }); const map = { ...mapService.getMap() }; commit('SET_MAP', map); diff --git a/src/views/Feature/FeatureDetail.vue b/src/views/Feature/FeatureDetail.vue index a233e1b1a1a11dd45c70cf04b127de01efd05505..991bc5e11c69cbfc82cf830f1848ee455bcd71d8 100644 --- a/src/views/Feature/FeatureDetail.vue +++ b/src/views/Feature/FeatureDetail.vue @@ -42,6 +42,7 @@ v-if="basemaps && map" ref="sidebar" /> + <Geolocation /> </div> <div id="popup" @@ -182,6 +183,7 @@ import FeatureTable from '@/components/Feature/Detail/FeatureTable'; import FeatureAttachements from '@/components/Feature/Detail/FeatureAttachements'; import FeatureComments from '@/components/Feature/Detail/FeatureComments'; import SidebarLayers from '@/components/Map/SidebarLayers'; +import Geolocation from '@/components/Map/Geolocation'; import { buffer } from 'ol/extent'; @@ -194,6 +196,7 @@ export default { FeatureAttachements, FeatureComments, SidebarLayers, + Geolocation, }, beforeRouteUpdate (to, from, next) { @@ -482,7 +485,8 @@ export default { mouseWheelZoom: true, dragPan: true }, - fullScreenControl: true + fullScreenControl: true, + geolocationControl: true, }); // Update link to feature list with map zoom and center @@ -600,6 +604,12 @@ export default { min-height: 250px; border: 1px solid grey; } +div.geolocation-container { + /* each button have (more or less depends on borders) .5em space between */ + /* zoom buttons are 60px high, geolocation and full screen button is 34px high with borders */ + top: calc(1.3em + 60px + 34px); +} + .prewrap { white-space: pre-wrap; } diff --git a/src/views/Feature/FeatureEdit.vue b/src/views/Feature/FeatureEdit.vue index b156f07c8c517d5e4be302348d403d5d274c56fd..92b6004a2ae1bdd0f694c28041505002b4b70a39 100644 --- a/src/views/Feature/FeatureEdit.vue +++ b/src/views/Feature/FeatureEdit.vue @@ -15,8 +15,6 @@ <form id="form-feature-edit" - action="" - method="post" enctype="multipart/form-data" class="ui form" > @@ -252,6 +250,7 @@ ref="map" > <SidebarLayers v-if="basemaps && map" /> + <Geolocation /> <Geocoder /> <EditingToolbar v-if="isEditable" @@ -375,6 +374,7 @@ import Dropdown from '@/components/Dropdown.vue'; import SidebarLayers from '@/components/Map/SidebarLayers'; import EditingToolbar from '@/components/Map/EditingToolbar'; import Geocoder from '@/components/Map/Geocoder'; +import Geolocation from '@/components/Map/Geolocation'; import featureAPI from '@/services/feature-api'; import mapService from '@/services/map-service'; @@ -384,7 +384,6 @@ import axios from '@/axios-client.js'; import { GeoJSON } from 'ol/format'; - export default { name: 'FeatureEdit', @@ -394,6 +393,7 @@ export default { Dropdown, SidebarLayers, Geocoder, + Geolocation, EditingToolbar, FeatureExtraForm, }, @@ -970,7 +970,8 @@ export default { mapDefaultViewZoom, maxZoom: this.project.map_max_zoom_level, interactions : { doubleClickZoom :false, mouseWheelZoom:true, dragPan:true }, - fullScreenControl: true + fullScreenControl: true, + geolocationControl: true, }); const currentFeatureId = this.$route.params.slug_signal; const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}v2/features/?feature_type__slug=${this.$route.params.slug_type_signal}&project__slug=${this.$route.params.slug}&output=geojson`; @@ -1035,6 +1036,13 @@ export default { width: 100%; border: 1px solid grey; } + + +div.geolocation-container { + /* each button have .5em space between, zoom buttons are 60px high and full screen button is 34px high */ + top: calc(1.3em + 60px + 34px); +} + #get-geom-from-image-file { margin-bottom: 5px; } diff --git a/src/views/Project/FeaturesListAndMap.vue b/src/views/Project/FeaturesListAndMap.vue index 7d57794ee88151a3aa4258f6aa22793074a7918f..8bf9e85e87657ace651ff0952826c55b9cd4d0d0 100644 --- a/src/views/Project/FeaturesListAndMap.vue +++ b/src/views/Project/FeaturesListAndMap.vue @@ -26,6 +26,7 @@ v-if="basemaps && map" ref="sidebar" /> + <Geolocation /> <Geocoder /> </div> <div @@ -106,8 +107,9 @@ import Geocoder from '@/components/Map/Geocoder'; import featureAPI from '@/services/feature-api'; import FeaturesListAndMapFilters from '@/components/Project/FeaturesListAndMap/FeaturesListAndMapFilters'; -import SidebarLayers from '@/components/Map/SidebarLayers'; import FeatureListTable from '@/components/Project/FeaturesListAndMap/FeatureListTable'; +import SidebarLayers from '@/components/Map/SidebarLayers'; +import Geolocation from '@/components/Map/Geolocation'; const initialPagination = { currentPage: 1, @@ -123,6 +125,7 @@ export default { FeaturesListAndMapFilters, SidebarLayers, Geocoder, + Geolocation, FeatureListTable, }, @@ -332,7 +335,8 @@ export default { mapDefaultViewZoom, maxZoom: this.project.map_max_zoom_level, interactions : { doubleClickZoom :false, mouseWheelZoom:true, dragPan:true }, - fullScreenControl: true + fullScreenControl: true, + geolocationControl: true, }); document.addEventListener('change-layers-order', (event) => { @@ -545,6 +549,11 @@ export default { z-index: 1; } } +div.geolocation-container { + // each button have (more or less depends on borders) .5em space between + // zoom buttons are 60px high, geolocation and full screen button is 34px high with borders + top: calc(1.3em + 60px + 34px); +} @media screen and (max-width: 767px) { #project-features { diff --git a/src/views/Project/ProjectDetail.vue b/src/views/Project/ProjectDetail.vue index 91aed08946787587bbfd699e5935b4fa6da0a643..ccbb87732b5e42f2e4799def93f2c1f67106a205 100644 --- a/src/views/Project/ProjectDetail.vue +++ b/src/views/Project/ProjectDetail.vue @@ -39,6 +39,7 @@ v-if="basemaps && map && !projectInfoLoading" ref="sidebar" /> + <Geolocation /> <div id="popup" class="ol-popup" @@ -127,6 +128,7 @@ import ProjectLastComments from '@/components/Project/Detail/ProjectLastComments import ProjectParameters from '@/components/Project/Detail/ProjectParameters'; import ProjectModal from '@/components/Project/Detail/ProjectModal'; import SidebarLayers from '@/components/Map/SidebarLayers'; +import Geolocation from '@/components/Map/Geolocation'; export default { name: 'ProjectDetail', @@ -139,6 +141,7 @@ export default { ProjectParameters, ProjectModal, SidebarLayers, + Geolocation, }, filters: { @@ -483,4 +486,9 @@ export default { margin-top: 0.5em; } } + +div.geolocation-container { + /* each button have .5em space between, zoom buttons are 60px high and full screen button is 34px high */ + top: calc(1.1em + 60px); +} </style>