From 570bc5df13b04606a1d7d23527b0d8c8fdd5a49d Mon Sep 17 00:00:00 2001 From: Nishit Suwal <81785002+NSUWAL123@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:15:10 +0545 Subject: [PATCH] feat: capacitor geolocation and orientation (#1016) * feat (projectDetails): geolocation - capacitor geolocation & motion integrated for device position & orientation * feat (vectorLayer): geolocationImage - rotate image when device orientation changes * fix (projectDetails): mapControlComponent - relplaced navigator geolocation with capacitor geolocation --- src/frontend/package.json | 2 + .../src/assets/images/locationArc.png | Bin 0 -> 1567 bytes .../OpenLayersComponent/Layers/VectorLayer.js | 19 +++- .../ProjectDetails/MapControlComponent.tsx | 56 ++-------- src/frontend/src/store/slices/ProjectSlice.ts | 4 + src/frontend/src/views/NewProjectDetails.jsx | 102 ++++++++++++++++++ 6 files changed, 132 insertions(+), 51 deletions(-) create mode 100644 src/frontend/src/assets/images/locationArc.png diff --git a/src/frontend/package.json b/src/frontend/package.json index cc25dde4e7..d4dac1c81e 100755 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -42,6 +42,8 @@ "vitest": "^0.34.6" }, "dependencies": { + "@capacitor/geolocation": "^5.0.6", + "@capacitor/motion": "^5.0.6", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/base": "5.0.0-beta.18", diff --git a/src/frontend/src/assets/images/locationArc.png b/src/frontend/src/assets/images/locationArc.png new file mode 100644 index 0000000000000000000000000000000000000000..c8dd9b1f5f98d2675adbd91ea67c78b374ef89c9 GIT binary patch literal 1567 zcmV+)2H^RLP)-A;F*gcd@n z>uy;rM0d%9EbIoKwisyXOH1*=1QPP#ZkjG`aMz}SO&>%eE$%XrPy+dH$F+rYCXS<{ zj`M%+J$;zpHRHb~J@AEl=l;&;o_l`3-}#+UB7$v7DFqbD(6a9VM9b2$??nn=%D&c6 z8&srKTyR|oHnnmE*A`&e@ZwF-BEX^L)GE>A3=Ot4FaxBuvRWvmED@2yPcGJwT|g<| z9vB#S@#xW`M{8?qn<^_Sy#SF&WF{C4-k+YHo<4p0^i3cRB!QHESxA`L79s(?nH z&(|6V1jghk76=5!d_G?*(5QXOG^+^x*>2!DpmA_;@KiFH42y`Qa;BWS6_MAk%?TIp z3K#Fn>(}Pw+^vYDa;AugB$LVT;NajX4S!Cv97=z>3)s`y+3CG>>C%Y9;VAoGC_(Fg zW+9YhTdOJMzrSnbmo+Z3*=(%4ySrm(XlMpl1rkb!zZ<9sg+hP#cswni%&yS!j~TFJ zd#G)OV&sore%n~i`B`-K0e;z@pxJmQ#m?D=AI4(u!N40ITlkn zJRVQW`1p8-&Oq_{YVdl!tpI=dG6>6=r$cR9&g6Xn%_ygt?IDU~uUFrOi} zo6QtbriD^U)z#HG0i}Q%pfZt2TrVvxtyE`k!{Z&6*iPmtMgIB%$z(F(a=Cs3L=1E> zSz20p0N^|5{?P&3KAMpOOhaE}g2CV|fD67lC^2?~Y_Sp}?*nK?2FTgqR99Em$FW%K z+vdGa-g)i7Gjrd0?EuYtoy20XZ@ap>J|J*ZX%HrF%=5 zn2qf`na(0Uc;ly>IZ}m)>+P6ZpiWN_ep7kmRlvPz#m0!Mmx!vpa{-V0N`jFf0t_Fz5 zCd?5RFEi$B!TXQvYTE96NSwH-O*o zU(;E(fXu|igs;53Ja^>Ck$(aSo!=bV(Lug+RFIe3co}#J_?6a6 zz{@>7J#Wn~gufCIS&puL0aR;-!wzNxczAgDO*@A<)6Dw){-O5v_D1g~iA>3yYC&?0frJC={9i4ryPP zW+{7%*$qez$oBX5pEWJZe7KTEox9Dc>yNmbaMs^VIO|Vce}p=Bn};iDOv^I+`}@z5 zKl<&{S?p+S*(<{hRNT9F@7?E{nmUa0cNC8{mD2q#tUv!u9^Ah_(%jtqF8SkVeMJCb z=+31u;tY`N?d`po$z+pPU#;J=^{&2JFPTg>+1uNDk$g{4*iYnJEnAx=(E~J1O--E_ z5#gf;!ryM>_0a5BBP_DXMn?cjC*sm zBJ!l$gbmGMv5uk)_(^1G@!yH!-G>2OxwVIt75jbuF6F7< zO)0Va<@F9yN0|AcXwgw^F%c1^lvVJP$;QUpPS>{39l-@=Z_(}kgZy9v@;!8MSx1hD RVr&2a002ovPDHLkV1nAb5TyVB literal 0 HcmV?d00001 diff --git a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js index def955f536..134debfa47 100644 --- a/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js +++ b/src/frontend/src/components/MapComponent/OpenLayersComponent/Layers/VectorLayer.js @@ -49,9 +49,9 @@ const VectorLayer = ({ onDraw, getTaskStatusStyle, layerProperties, + rotation, }) => { const [vectorLayer, setVectorLayer] = useState(null); - useEffect(() => () => map && vectorLayer && map.removeLayer(vectorLayer), [map, vectorLayer]); // Modify Feature @@ -282,6 +282,23 @@ const VectorLayer = ({ map.un('pointermove', pointerMovefn); }; }, [vectorLayer]); + + // ROTATE ICON IMAGE ACCORDING TO ORIENTATION + useEffect(() => { + if (!map) return; + if (typeof rotation === 'number') { + const mapRotation = map.getView().getRotation(); + setStyle?.getImage().setRotation(rotation); + } + }, [rotation, map, geojson]); + + // ROTATE MAP ACCORDING TO ORIENTATION + // useEffect(() => { + // if (!map) return; + // if (rotation) { + // map.getView().setRotation(rotation); + // } + // }, [rotation, map]); return null; }; diff --git a/src/frontend/src/components/ProjectDetails/MapControlComponent.tsx b/src/frontend/src/components/ProjectDetails/MapControlComponent.tsx index 930510b602..a0ab6ea5fd 100644 --- a/src/frontend/src/components/ProjectDetails/MapControlComponent.tsx +++ b/src/frontend/src/components/ProjectDetails/MapControlComponent.tsx @@ -1,14 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import AssetModules from '../../shared/AssetModules'; -import { transform } from 'ol/proj'; -import * as ol from 'ol'; -import { Point } from 'ol/geom'; -import Vector from 'ol/layer/Vector'; -import VectorSource from 'ol/source/Vector'; -import { Style } from 'ol/style'; -import { Icon } from 'ol/style'; -import LocationImage from '../../assets/images/location.png'; import VectorLayer from 'ol/layer/Vector'; +import CoreModules from '../../shared/CoreModules.js'; +import { ProjectActions } from '../../store/slices/ProjectSlice'; const MapControlComponent = ({ map }) => { const btnList = [ @@ -29,20 +23,9 @@ const MapControlComponent = ({ map }) => { icon: , }, ]; + const dispatch = CoreModules.useAppDispatch(); const [toggleCurrentLoc, setToggleCurrentLoc] = useState(false); - const [currentLocLayer, setCurrentLocLayer] = useState(null); - useEffect(() => { - if (!map) return; - if (!currentLocLayer) return; - map.addLayer(currentLocLayer); - map.getView().fit(currentLocLayer.getSource().getExtent(), { - maxZoom: 18, - duration: 500, - }); - return () => { - map.removeLayer(currentLocLayer); - }; - }, [map, currentLocLayer]); + const geolocationStatus = CoreModules.useAppSelector((state) => state.project.geolocationStatus); const handleOnClick = (btnId) => { if (btnId === 'add') { const actualZoom = map.getView().getZoom(); @@ -52,34 +35,7 @@ const MapControlComponent = ({ map }) => { map.getView().setZoom(actualZoom - 1); } else if (btnId === 'currentLocation') { setToggleCurrentLoc(!toggleCurrentLoc); - const sourceProjection = 'EPSG:4326'; // The current projection of the coordinates - const targetProjection = 'EPSG:3857'; // The desired projection - var markerStyle = new Style({ - image: new Icon({ - src: LocationImage, // Path to your marker icon image - anchor: [0.5, 1], // Anchor point of the marker icon (center bottom) - scale: 2, // Scale factor for the marker icon - }), - }); - if ('geolocation' in navigator) { - if (!toggleCurrentLoc) { - navigator.geolocation.getCurrentPosition((position) => { - const lat = position.coords.latitude; - const lng = position.coords.longitude; - const convertedCoordinates = transform([lng, lat], sourceProjection, targetProjection); - const positionFeature = new ol.Feature(new Point(convertedCoordinates)); - const positionLayer = new Vector({ - source: new VectorSource({ - features: [positionFeature], - }), - }); - positionFeature.setStyle(markerStyle); - setCurrentLocLayer(positionLayer); - }); - } else { - setCurrentLocLayer(null); - } - } + dispatch(ProjectActions.ToggleGeolocationStatus(!geolocationStatus)); } else if (btnId === 'taskBoundries') { const layers = map.getAllLayers(); let extent; diff --git a/src/frontend/src/store/slices/ProjectSlice.ts b/src/frontend/src/store/slices/ProjectSlice.ts index f853fc5296..bcc7be7985 100755 --- a/src/frontend/src/store/slices/ProjectSlice.ts +++ b/src/frontend/src/store/slices/ProjectSlice.ts @@ -20,6 +20,7 @@ const ProjectSlice = createSlice({ downloadDataExtractLoading: false, taskModalStatus: false, mobileFooterSelection: 'explore', + geolocationStatus: false, }, reducers: { SetProjectTaskBoundries(state, action) { @@ -74,6 +75,9 @@ const ProjectSlice = createSlice({ SetMobileFooterSelection(state, action) { state.mobileFooterSelection = action.payload; }, + ToggleGeolocationStatus(state, action) { + state.geolocationStatus = action.payload; + }, }, }); diff --git a/src/frontend/src/views/NewProjectDetails.jsx b/src/frontend/src/views/NewProjectDetails.jsx index 44583a94a0..373ce3a38f 100644 --- a/src/frontend/src/views/NewProjectDetails.jsx +++ b/src/frontend/src/views/NewProjectDetails.jsx @@ -34,6 +34,11 @@ import getTaskStatusStyle from '../utilfunctions/getTaskStatusStyle'; import { defaultStyles } from '../components/MapComponent/OpenLayersComponent/helpers/styleUtils'; import MapLegends from '../components/MapLegends'; import Accordion from '../components/common/Accordion'; +import { Geolocation } from '@capacitor/geolocation'; +import { Icon, Style } from 'ol/style'; +import { Motion } from '@capacitor/motion'; +import locationArc from '../assets/images/locationArc.png'; +import { CommonActions } from '../store/slices/CommonSlice'; const Home = () => { const dispatch = CoreModules.useAppDispatch(); @@ -47,6 +52,9 @@ const Home = () => { const [toggleGenerateModal, setToggleGenerateModal] = useState(false); const [taskBuildingGeojson, setTaskBuildingGeojson] = useState(null); const [initialFeaturesLayer, setInitialFeaturesLayer] = useState(null); + const [currentCoordinate, setCurrentCoordinate] = useState({ latitude: null, longitude: null }); + const [positionGeojson, setPositionGeojson] = useState(null); + const [deviceRotation, setDeviceRotation] = useState(0); const encodedId = params.id; const decodedId = environment.decode(encodedId); @@ -57,6 +65,7 @@ const Home = () => { const projectBuildingGeojson = CoreModules.useAppSelector((state) => state.project.projectBuildingGeojson); const mobileFooterSelection = CoreModules.useAppSelector((state) => state.project.mobileFooterSelection); const mapTheme = CoreModules.useAppSelector((state) => state.theme.hotTheme); + const geolocationStatus = CoreModules.useAppSelector((state) => state.project.geolocationStatus); //snackbar handle close funtion const handleClose = (event, reason) => { @@ -177,6 +186,83 @@ const Home = () => { } }, [mobileFooterSelection]); + const handlePositionChange = (position) => { + setCurrentCoordinate({ + latitude: position.coords.latitude, + longitude: position.coords.longitude, + }); + + const geojson = { + type: 'Point', + coordinates: [position.coords.longitude, position.coords.latitude], + }; + setPositionGeojson(geojson); + }; + + useEffect(async () => { + if (geolocationStatus) { + const checkPermission = await Geolocation.checkPermissions(); + if (checkPermission.location === 'denied') { + await Geolocation.requestPermissions(['location']); + } + } + }, [geolocationStatus]); + + useEffect(() => { + if (geolocationStatus) { + const getCurrentPosition = async () => { + try { + const position = await Geolocation.getCurrentPosition(); + handlePositionChange(position); + // Watch for position changes + const watchId = Geolocation.watchPosition({ enableHighAccuracy: true }, handlePositionChange); + // Clean up the watchPosition when the component unmounts + return () => { + Geolocation.clearWatch({ id: watchId }); + }; + } catch (error) { + dispatch( + CommonActions.SetSnackBar({ + open: true, + message: `Error getting current position. Please ensure location permissions has been granted.`, + variant: 'error', + duration: 2000, + }), + ); + dispatch(ProjectActions.ToggleGeolocationStatus(false)); + + console.error('Error getting current position:', error); + } + }; + + getCurrentPosition(); + } + }, [geolocationStatus]); + + const locationArcStyle = new Style({ + image: new Icon({ + src: locationArc, + }), + }); + + const startOrientation = async () => { + const handler = await Motion.addListener('orientation', (event) => { + var alphaRad = event?.alpha * (Math.PI / 180); + var betaRad = event?.beta * (Math.PI / 180); + var gammaRad = event?.gamma * (Math.PI / 180); + + setDeviceRotation(alphaRad + betaRad + gammaRad); + }); + }; + + useEffect(() => { + // Cleanup when the component unmounts + if (geolocationStatus) { + startOrientation(); + } + return () => {}; + }, [geolocationStatus]); + return (
{/* Customized Modal For Generate Tiles */} @@ -320,6 +406,22 @@ const Home = () => { zIndex={5} /> )} + {geolocationStatus && currentCoordinate?.latitude && currentCoordinate?.longitude && ( + + )} +
{window.DeviceMotionEvent}
}