<!-- Copyright (c) 2022 NIBIO <http://www.nibio.no/>. This file is part of VIPSObservationApp. VIPSObservationApp is free software: you can redistribute it and/or modify it under the terms of the NIBIO Open Source License as published by NIBIO, either version 1 of the License, or (at your option) any later version. VIPSObservationApp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the NIBIO Open Source License for more details. You should have received a copy of the NIBIO Open Source License along with VIPSObservationApp. If not, see <http://www.nibio.no/licenses/>. Author: Bhabesh Bhabani Mukhopadhyay Author: Tor-Einar Skog <tor-einar.skog@nibio.no> Dated : 19-Aug-2021 --> <template> <div> <router-link id='btnBack' :to="{name:'PlacesList', params: {}}" class="vips-btn"> {{ $t("map.link.back.label") }} </router-link> <div v-show="userCanEditPOI" id='map-mylocation' style="margin: 5px;"> <a v-on:click="myposition" style="cursor:pointer; "><font-awesome-icon style="font-size: x-large; padding: 5px; color: white; background-color:#3d8052;" icon="location-crosshairs" /></a> </div> <div id='map-poi'></div> <div id='divPoiData' class="container" > <div class="form-group"><input class="form-control" :placeholder="$t('mapPOI.poiName.label')" id='poiName' ref='poiName' v-model="poi.name" :disabled="!userCanEditPOI"/></div> <div class='form-group'> <select class="form-control" v-model="poi.pointOfInterestTypeId" :disabled="!userCanEditPOI"> <option v-for="poiType in poiTypes" v-bind:key="poiType.point_of_interest_type_id" :value='poiType.point_of_interest_type_id' :disabled="!poiType.is_user_selectable">{{$t(poiType.default_name)}}</option> </select> </div> <div class="form-group" v-show="userCanEditPOI"> <div class="float-right"> <a class="vips-btn" v-on:click="validate">{{$t("save.label")}}</a> <a v-show="POIExists" class="vips-btn danger" v-on:click="callForRemovePOI">{{$t("delete.label")}}</a> </div> </div> </div> <div id="poiMarker" style="display:none"> <img src="@/assets/map_icon.png"> </div> <sync ref="sync"/> </div> </template> <script> import CommonUtil from '@/components/CommonUtil'; import Sync from '@/components/Sync' import 'ol/ol.css'; import Map from 'ol/Map'; import OSM from 'ol/source/OSM'; import TileLayer from 'ol/layer/Tile'; import View from 'ol/View'; import WMTS, {optionsFromCapabilities} from 'ol/source/WMTS'; import WMTSCapabilities from 'ol/format/WMTSCapabilities'; import {Vector as VectorSource} from 'ol/source'; import {Vector as VectorLayer} from 'ol/layer'; import Collection from 'ol/Collection'; import {fromLonLat} from 'ol/proj'; import Circle from 'ol/geom/Circle'; import {Circle as CircleStyle, Fill, Stroke, Style} from 'ol/style'; import Feature from 'ol/Feature'; import GeoJSON from 'ol/format/GeoJSON'; import {toStringXY} from 'ol/coordinate'; import {transform} from 'ol/proj'; import Point from 'ol/geom/Point'; import {Modify} from 'ol/interaction'; import Draw from 'ol/interaction/Draw'; import Overlay from 'ol/Overlay'; import Geolocation from 'ol/Geolocation'; import i18n from '@/App' export default{ name : 'MapPOI', components : {Sync}, props : ['pointOfInterestId'], data() { return { poi : {}, mapZoom : 0, poiTypes : [], msgErr : '', map : '', } }, computed: { userCanEditPOI: function(){ return this.isPOIOwnedByUser(); }, POIExists: function() { return this.poi.pointOfInterestId; } }, methods : { isPOIOwnedByUser(){ return !this.poi.pointOfInterestId || (this.poi.userId && this.poi.userId == this.$root.sharedState.userId); }, callForRemovePOI() { if(confirm(this.$i18n.t("mapPOI.modal.label.deleteprompt") + " " + this.poi.name + "?")) { this.deletePOI(); } }, deletePOI() { if(this.poi.pointOfInterestId) { if(this.poi.pointOfInterestId < 0) { /** Just remove it locally */ this.removeLocalPOI(this.poi.pointOfInterestId); this.$router.replace({path:'/places'}); } else { /** Mark the record - for sending to server */ this.poi.deleted=true; this.saveToStore(); } } }, removeLocalPOI(id) { let lstPOI = JSON.parse(localStorage.getItem(CommonUtil.CONST_STORAGE_POI_LIST)); let indexPosition = null; $.each(lstPOI, function(index, poi){ if(poi.pointOfInterestId===id) { indexPosition = index; return false; } }); if(indexPosition) { lstPOI.splice(indexPosition,1); localStorage.setItem(CommonUtil.CONST_STORAGE_POI_LIST,JSON.stringify(lstPOI)); } }, validate() { if((!this.poi.name) || (this.trimString(this.poi.name) === '')) { alert(this.$i18n.t("mapPOI.modal.alert.missingPOIName")); return; } if(!this.poi.geoJSON) { alert(this.$i18n.t("mapPOI.modal.alert.missingGeoInfo")); return; } // TODO Simplify this logic if (!this.poi.pointOfInterestTypeId) { if(this.poi.pointOfInterestTypeId === 0) { // Pass } else { alert(this.$i18n.t("mapPOI.modal.alert.missingPOIType")); return false; } } if(confirm(this.$i18n.t("mapPOI.modal.label.saveprompt") + " " + this.poi.name + "?")) { this.saveToStore(); } }, getPointOfInterest(id){ let lstPOI = JSON.parse(localStorage.getItem(CommonUtil.CONST_STORAGE_POI_LIST)); let poi = lstPOI.find(({pointOfInterestId}) => pointOfInterestId === id); this.poi = poi; }, saveToStore(){ let This = this; let lstPOI = JSON.parse(localStorage.getItem(CommonUtil.CONST_STORAGE_POI_LIST)); if(this.poi.pointOfInterestId) { $.each(lstPOI, function(index, poi){ if(poi.pointOfInterestId === This.poi.pointOfInterestId) { poi.latitude=This.poi.latitude; poi.longitude=This.poi.longitude; poi.name=This.poi.name; poi.pointOfInterestTypeId=This.poi.pointOfInterestTypeId; poi.geoJSON=This.poi.geoJSON poi.uploaded=false; if(This.poi.deleted) { poi.deleted = This.poi.deleted; } } }) } else { this.poi.pointOfInterestId = this.getNewPoiId(lstPOI); this.poi.userId = this.$root.sharedState.userId; this.poi.uploaded=false; if(lstPOI) { lstPOI.push(this.poi); } else { lstPOI = []; lstPOI.push(this.poi); } } localStorage.setItem(CommonUtil.CONST_STORAGE_POI_LIST,JSON.stringify(lstPOI)); this.$refs.sync.syncTwoWay(); this.$router.push('/places'); }, /** new POI pointOfInterestId */ getNewPoiId(lstPOI) { let newId = 0; let poiIds=[]; if(lstPOI) { $.each(lstPOI, function(index, poi){ if(poi.pointOfInterestId < 0) { poiIds.push(Math.abs(poi.pointOfInterestId)); } }); if(poiIds.length === 0) { newId = CommonUtil.CONST_POI_COUNT_START_ID; } else { let largestValue = Math.max.apply(null, poiIds); newId = -Math.abs(largestValue + 1); } } else { newId = CommonUtil.CONST_POI_COUNT_START_ID; } return newId; }, myImage() { var fill = new Fill({ color: 'white' }); return new CircleStyle({ radius: 15, fill: fill, stroke: new Stroke({color: '#3d8052', width: 15}), }); }, myVectorGeoSource(geoInfo){ if(geoInfo) { return new VectorSource({ features : new GeoJSON({dataProjection:"EPSG:4326", featureProjection:"EPSG:3857"}).readFeatures(geoInfo), }) } }, myVectorGeoLayer(vectorSource,image){ return new VectorLayer({ source : vectorSource, style : new Style({ image: image, }), }) }, myView(longitude,latitude,mapZoom){ return new View ({ center:fromLonLat([longitude,latitude]), zoom : mapZoom }) }, myOverLayCoord(longitude,latitude) { let coordinate = [longitude,latitude]; return this.myOverLay(coordinate); }, myOverLay(coordinates){ return new Overlay({ position : fromLonLat(coordinates) , positioning: 'bottom-center', element: document.getElementById('poiMarker'), stopEvent: false }); }, myInteractions(mapInteractions) { return (mapInteractions) ? [] : ''; }, /** My current location */ myposition() { let options = { enableHighAccuracy: true }; navigator.geolocation.getCurrentPosition(this.geolocationSuccess, this.geolocationError, options); //navigator.geolocation.getCurrentPosition(this.geolocationSuccess, this.geolocationError, this.geolocationOptions); }, geolocationOptions(options){ console.log('geolocation options : '+options); }, geolocationError(error){ console.log('geolocation error : '+geolocationError); }, geolocationSuccess(pos) { let This = this; This.poi.latitude = pos.coords.latitude; This.poi.longitude = pos.coords.longitude; let coord = [pos.coords.longitude,pos.coords.latitude]; // This is the marker icon for the location of the POI let vectorLayer = new VectorLayer({ source : new VectorSource ({ features : [ new Feature({ geometry : new Point(fromLonLat(coord)) }) ], }), style : new Style({ image : This.myImage() }) }); let geoJSON = new GeoJSON(); let resultGeoJSON = geoJSON.writeFeatures(vectorLayer.getSource().getFeatures(),{ dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); this.poi.geoJSON = resultGeoJSON; if(This.map) { let mapLayers = This.map.getLayers(); mapLayers.forEach(function(layer){ let source = layer.get('source'); source.clear(); }) This.map.addLayer(vectorLayer); This.map.getView().setCenter(fromLonLat(coord)); This.map.getView().setZoom(CommonUtil.CONST_GPS_OBSERVATION_ZOOM); } }, mapInit() { let This = this; let urlMap = CommonUtil.CONST_GPS_URL_NORWAY_MAP; let latitude = this.poi.latitude; let longitude = this.poi.longitude; let mapZoom = this.mapZoom; let geoInfo = ''; if(this.poi && this.poi.geoJSON) { geoInfo = JSON.parse(this.poi.geoJSON); } else { let image = this.myImage(); latitude = CommonUtil.CONST_GPS_DEFAULT_LATITUDE_02_NORWAY; longitude = CommonUtil.CONST_GPS_DEFAULT_LONGITUDE_02_NORWAY; mapZoom = CommonUtil.CONST_GPS_DEFAULT_ZOOM; let coord = [CommonUtil.CONST_GPS_DEFAULT_LONGITUDE_02_NORWAY,CommonUtil.CONST_GPS_DEFAULT_LATITUDE_02_NORWAY]; let transFormCord = transform(coord, 'EPSG:3857','EPSG:4326'); let iconFeature = new Feature({ geometry: new Point(fromLonLat(transFormCord)) }); let vectorSource = new VectorSource({}); vectorSource.addFeature(iconFeature); let vectorLayer = new VectorLayer({ source: vectorSource, style: new Style({ image: image, }), }); let geoGSON = new GeoJSON(); let resultGeoGSON = JSON.parse(geoGSON.writeFeatures(vectorLayer.getSource().getFeatures())); //this.poi.geoJSON = JSON.stringify(resultGeoGSON); geoInfo = resultGeoGSON; } // Set default POI type to field if(! this.poi.pointOfInterestTypeId) { this.poi.pointOfInterestTypeId = CommonUtil.CONST_POI_TYPE_DEFAULT; } fetch(urlMap) .then(function (response) { return response.text(); }) .then(function (text) { let parser = new WMTSCapabilities(); var result = parser.read(text); var options = optionsFromCapabilities(result, { layer: 'topo4', matrixSet: 'EPSG:3857', }); This.map = new Map({ layers: [ new TileLayer({ opacity: 1, source: new WMTS(options), }) , new VectorLayer({ source : new VectorSource({ features : new GeoJSON({ dataProjection:"EPSG:4326", featureProjection:"EPSG:3857" }).readFeatures(geoInfo), }), style : new Style({ image: This.myImage(), }), }), ], controls : [], target : 'map-poi', view : new View({ center : fromLonLat([longitude,latitude]), zoom : mapZoom, }), }); /** Remove map pointer for first time newly poi */ if(!This.poi.longitude) { let mapLayers = This.map.getLayers(); mapLayers.forEach(function(layer){ let source = layer.get('source'); source.clear(); }) } This.map.on(['singleclick'],function(event){ if(!This.userCanEditPOI) { return; } let transFormCord = transform(event.coordinate, 'EPSG:3857','EPSG:4326'); This.poi.longitude = transFormCord[0]; This.poi.latitude = transFormCord[1]; This.map.getView().setCenter(fromLonLat(transFormCord)); let mapLayers = This.map.getLayers(); mapLayers.forEach(function(layer){ let source = layer.get('source'); source.clear(); }) let vectorLayer = new VectorLayer({ source : new VectorSource ({ features : [ new Feature({ geometry : new Point(fromLonLat(transFormCord)) }) ], }), style : new Style({ image : This.myImage() }) }); This.map.addLayer(vectorLayer); let geoGSON = new GeoJSON(); let resultGeoGSON = geoGSON.writeFeatures(vectorLayer.getSource().getFeatures(), { dataProjection : 'EPSG:4326', featureProjection : 'EPSG:3857' }) This.poi.geoJSON = resultGeoGSON; }) }); }, trimString(param) { return param.replace(/\s*/g,"") } }, filters: { trim: function(string) { return string.trim() } }, mounted() { var appDiv = document.getElementById("app"); var navDiv = document.getElementById("vipsobsappmenu"); var mapDiv = document.getElementById("map-poi"); appDiv.style.marginTop = "0"; appDiv.style.paddingRight = "0"; appDiv.style.paddingLeft = "0"; mapDiv.style.height = (screen.height - navDiv.offsetHeight) + "px"; this.mapZoom = CommonUtil.CONST_GPS_OBSERVATION_ZOOM; this.poiTypes = CommonUtil.CONST_POI_TYPES; //console.info(this.poiTypes); if(this.$route.params.pointOfInterestId) { this.getPointOfInterest(this.$route.params.pointOfInterestId); } this.mapInit(); }, beforeDestroy() { // This resets the container layout when leaving the router page var appDiv = document.getElementById("app"); appDiv.style.marginTop="15px"; appDiv.style.paddingRight="15px"; appDiv.style.paddingLeft="15px"; }, } </script> <style> html, body, #map-poi { margin: 0; width: 100%; } #btnBack{ position: fixed; z-index: 1000; } #map-mylocation { position: fixed; right: 0; z-index: 2000; } #divPoiData { position: fixed; z-index: 1100; bottom: 0; } </style>