Skip to content

Commit

Permalink
feat: capacitor geolocation and orientation (#1016)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
NSUWAL123 authored Nov 29, 2023
1 parent 4106a01 commit 570bc5d
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 51 deletions.
2 changes: 2 additions & 0 deletions src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added src/frontend/src/assets/images/locationArc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
};

Expand Down
56 changes: 6 additions & 50 deletions src/frontend/src/components/ProjectDetails/MapControlComponent.tsx
Original file line number Diff line number Diff line change
@@ -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 = [
Expand All @@ -29,20 +23,9 @@ const MapControlComponent = ({ map }) => {
icon: <AssetModules.CropFreeIcon />,
},
];
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();
Expand All @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/store/slices/ProjectSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const ProjectSlice = createSlice({
downloadDataExtractLoading: false,
taskModalStatus: false,
mobileFooterSelection: 'explore',
geolocationStatus: false,
},
reducers: {
SetProjectTaskBoundries(state, action) {
Expand Down Expand Up @@ -74,6 +75,9 @@ const ProjectSlice = createSlice({
SetMobileFooterSelection(state, action) {
state.mobileFooterSelection = action.payload;
},
ToggleGeolocationStatus(state, action) {
state.geolocationStatus = action.payload;
},
},
});

Expand Down
102 changes: 102 additions & 0 deletions src/frontend/src/views/NewProjectDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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) => {
Expand Down Expand Up @@ -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 (
<div>
{/* Customized Modal For Generate Tiles */}
Expand Down Expand Up @@ -320,6 +406,22 @@ const Home = () => {
zIndex={5}
/>
)}
{geolocationStatus && currentCoordinate?.latitude && currentCoordinate?.longitude && (
<VectorLayer
map={map}
geojson={positionGeojson}
setStyle={locationArcStyle}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
constrainResolution: true,
duration: 2000,
}}
zIndex={5}
rotation={deviceRotation}
/>
)}
<div className="fmtm-top-28 fmtm-left-5">{window.DeviceMotionEvent}</div>
<div className="fmtm-hidden sm:fmtm-block fmtm-absolute fmtm-bottom-5 fmtm-left-5 fmtm-z-50 fmtm-rounded-lg">
<Accordion
body={<MapLegends defaultTheme={defaultTheme} />}
Expand Down

0 comments on commit 570bc5d

Please sign in to comment.