Skip to content

Commit

Permalink
feat (nav): project details geolocation integration (#1374)
Browse files Browse the repository at this point in the history
* fix projectListMap: remove geolocation from homePage map

* refactor projectListMap: remove old vectorLayer code

* feat projectDetails: geolocationStatus add

* fix geoLocation: add & remove geoLocation layer conditionally

* fix mapControlComponent: change geolocation point to red if status on

* fix projectDetails: set generateMbtiles btn to bottom for mobile screen

* fix geolocation: zoom to current location on geolocation on
  • Loading branch information
NSUWAL123 authored Mar 22, 2024
1 parent ee9e0ca commit aec9fb3
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 313 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import React, { useState } from 'react';
import AssetModules from '@/shared/AssetModules';
import VectorLayer from 'ol/layer/Vector';
import CoreModules from '@/shared/CoreModules.js';
import { ProjectActions } from '@/store/slices/ProjectSlice';
import { useAppSelector } from '@/types/reduxTypes';

const MapControlComponent = ({ map }) => {
const btnList = [
Expand All @@ -26,6 +29,10 @@ const MapControlComponent = ({ map }) => {
},
];

const dispatch = CoreModules.useAppDispatch();
const [toggleCurrentLoc, setToggleCurrentLoc] = useState(false);
const geolocationStatus = useAppSelector((state) => state.project.geolocationStatus);

const handleOnClick = (btnId) => {
if (btnId === 'add') {
const actualZoom = map.getView().getZoom();
Expand All @@ -34,19 +41,8 @@ const MapControlComponent = ({ map }) => {
const actualZoom = map.getView().getZoom();
map.getView().setZoom(actualZoom - 1);
} else if (btnId === 'currentLocation') {
const layers = map.getAllLayers();
let extent;
layers.map((layer) => {
if (layer instanceof VectorLayer) {
const layerName = layer.getProperties().name;
if (layerName === 'geolocation') {
extent = layer.getSource().getExtent();
}
}
});
map.getView().fit(extent, {
padding: [10, 10, 10, 10],
});
setToggleCurrentLoc(!toggleCurrentLoc);
dispatch(ProjectActions.ToggleGeolocationStatus(!geolocationStatus));
} else if (btnId === 'taskBoundries') {
const layers = map.getAllLayers();
let extent;
Expand All @@ -69,7 +65,9 @@ const MapControlComponent = ({ map }) => {
{btnList.map((btn) => (
<div key={btn.id}>
<div
className="fmtm-bg-white fmtm-rounded-full fmtm-p-2 hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300"
className={`fmtm-bg-white fmtm-rounded-full fmtm-p-2 hover:fmtm-bg-gray-100 fmtm-cursor-pointer fmtm-duration-300 ${
geolocationStatus && btn.id === 'currentLocation' ? 'fmtm-text-primaryRed' : ''
}`}
onClick={() => handleOnClick(btn.id)}
title={btn.title}
>
Expand Down
209 changes: 0 additions & 209 deletions src/frontend/src/components/home/ProjectListMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import React, { useState, useEffect } from 'react';
import { useOLMap } from '@/components/MapComponent/OpenLayersComponent';
import { MapContainer as MapComponent } from '@/components/MapComponent/OpenLayersComponent';
import LayerSwitcherControl from '@/components/MapComponent/OpenLayersComponent/LayerSwitcher/index.js';
import OLVectorLayer from 'ol/layer/Vector';
import { ClusterLayer } from '@/components/MapComponent/OpenLayersComponent/Layers';
import CoreModules from '@/shared/CoreModules';
import { geojsonObjectModel } from '@/constants/geojsonObjectModal';
Expand All @@ -12,16 +11,6 @@ import { useNavigate } from 'react-router-dom';
import environment from '@/environment';
import { Style, Text, Icon, Fill } from 'ol/style';
import { projectType } from '@/models/home/homeModel';
import Control from 'ol/control/Control';
import { Feature } from 'ol';
import { circular } from 'ol/geom/Polygon';
import { Point } from 'ol/geom';
import { fromLonLat } from 'ol/proj';
import VectorSource from 'ol/source/Vector';
import logo from '@/assets/images/navigation.svg';
import RedNavigationMarker from '@/assets/images/rednavigationmarker.svg';
import navigationMarker from '@/assets/images/navigation.png';
import pngbluedot from '@/assets/images/png bluedot.png';

type HomeProjectSummaryType = {
features: { geometry: any; properties: any; type: any }[];
Expand All @@ -34,21 +23,6 @@ type HomeProjectSummaryType = {
};
};

// const projectGeojsonLayerStyle = {
// ...defaultStyles,
// fillOpacity: 0,
// lineColor: '#ffffff',
// labelFontSize: 20,
// lineThickness: 7,
// lineOpacity: 40,
// showLabel: true,
// labelField: 'project_id',
// labelOffsetY: 35,
// labelFontWeight: 'bold',
// labelMaxResolution: 10000,
// icon: { scale: [0.09, 0.09], url: MarkerIcon },
// };

const getIndividualStyle = (featureProperty) => {
const style = new Style({
image: new Icon({
Expand Down Expand Up @@ -77,151 +51,7 @@ const ProjectListMap = () => {
zoom: 4,
maxZoom: 20,
});
useEffect(() => {
if (!map) return;
const source = new VectorSource();
const layer = new OLVectorLayer({
source: source,
});
map?.addLayer(layer);

navigator.geolocation.watchPosition(
function (pos) {
const coords = [pos.coords.longitude, pos.coords.latitude];
source.clear(true);
source.addFeatures([
new Feature(circular(coords, pos.coords.accuracy).transform('EPSG:4326', map.getView().getProjection())),
new Feature(new Point(fromLonLat(coords))),
]);
},
function (error) {
alert(`ERROR: ${error.message}`);
},
{
enableHighAccuracy: true,
},
);
const locate = document.createElement('div');
locate.className = 'ol-control ol-unselectable locate';
locate.innerHTML = '<button title="Locate me">◎</button>';
locate.addEventListener('click', function () {
if (!source.isEmpty()) {
map.getView().fit(source.getExtent(), {
maxZoom: 18,
duration: 500,
});
}
});
map.addControl(
new Control({
element: locate,
}),
);
//! [style]
const style = new Style({
fill: new Fill({
color: 'rgba(0, 0, 255, 0.2)',
}),
image: new Icon({
src: pngbluedot,
scale: 0.09,
imgSize: [27, 55],
rotateWithView: true,
}),
});
layer.setStyle(style);

function handleReading(quaternion) {
// https://w3c.github.io/orientation-sensor/#model explains the order of
// the 4 elements in the sensor.quaternion array.
let [qx, qy, qz, qw] = quaternion;

// When the phone is lying flat, we want to treat the direction toward the
// top of the phone as the "forward" direction; when the phone is held
// upright, we want to treat the direction out the back of the phone as the
// "forward" direction. So, let's determine the compass heading of the
// phone based on the vector between these directions, i.e. at a 45-degree
// angle between the positive Y-axis and the negative Z-axis in this figure:
// https://w3c.github.io/orientation-sensor/#absoluteorientationsensor-model

// To find the current "forward" direction of the phone, we want to take this
// vector, (0, 1, -1), and apply the same rotation as the phone's rotation.
const y = 1;
const z = -1;

// From experimentation, it looks like the quaternion from the sensor is
// the inverse rotation, so we need to flip the fourth component.
qw = -qw;

// This section explains how to convert the quaternion to a rotation matrix:
// https://w3c.github.io/orientation-sensor/#convert-quaternion-to-rotation-matrix
// Now let's multiply the forward vector by the rotation matrix.
const rx = y * (2 * qx * qy + 2 * qw * qz) + z * (2 * qx * qz - 2 * qw * qy);
const ry = y * (1 - 2 * qx * qx - 2 * qz * qz) + z * (2 * qy * qz + 2 * qw * qx);
const rz = y * (2 * qy * qz + 2 * qw * qx) + z * (1 - 2 * qx * qx - 2 * qy * qy);

// This gives us a rotated vector indicating the "forward" direction of the
// phone with respect to the earth. We only care about the orientation of
// this vector in the XY plane (the plane tangential to the ground), i.e.
// the heading of the (rx, ry) vector, where (0, 1) is north.

const radians = Math.atan2(ry, rx);
const degrees = (radians * 180) / Math.PI; // counterclockwise from +X axis
let heading = 90 - degrees;
if (heading < 0) heading += 360;
heading = Math.round(heading);

// info.value =
// qx.toFixed(3) +
// "\n" +
// qy.toFixed(3) +
// "\n" +
// qz.toFixed(3) +
// "\n" +
// qw.toFixed(3) +
// "\n\n" +
// rx.toFixed(3) +
// "\n" +
// ry.toFixed(3) +
// "\n" +
// rz.toFixed(3) +
// "\n\nHeading: " +
// heading;
console.log(heading, 'heading');
console.log((Math.PI / 180) * heading, '(Math.PI / 180) * heading');
// To make the arrow point north, rotate it opposite to the phone rotation.
style.getImage().setRotation((Math.PI / 180) * heading);
}

// See the API specification at: https://w3c.github.io/orientation-sensor
// We use referenceFrame: 'screen' because the web page will rotate when
// the phone switches from portrait to landscape.
const sensor = new AbsoluteOrientationSensor({
frequency: 60,
referenceFrame: 'screen',
});
sensor.addEventListener('reading', (event) => {
console.log(event, 'event');
layer.on('postrender', handleReading(sensor.quaternion));
});
// handleReading([0.509, -0.071, -0.19, 0.836]);

Promise.all([
navigator.permissions.query({ name: 'accelerometer' }),
navigator.permissions.query({ name: 'magnetometer' }),
navigator.permissions.query({ name: 'gyroscope' }),
]).then((results) => {
if (results.every((result) => result.state === 'granted')) {
sensor.start();
console.log('Sensor started!');

// stat.value = "Sensor started!";
} else {
console.log('No permissions to use AbsoluteOrientationSensor.');
// stat.value = "No permissions to use AbsoluteOrientationSensor.";
}
});
}, [map]);
const homeProjectSummary: projectType[] = CoreModules.useAppSelector((state) => state.home.homeProjectSummary);
useEffect(() => {
if (homeProjectSummary?.length === 0) return;
Expand Down Expand Up @@ -261,45 +91,6 @@ const ProjectListMap = () => {
}}
>
<LayerSwitcherControl visible={'outdoors'} />
{/* {projectGeojson && projectGeojson?.features?.length > 0 && (
<VectorLayer
geojson={projectGeojson}
style={projectGeojsonLayerStyle}
viewProperties={{
size: map?.getSize(),
padding: [50, 50, 50, 50],
constrainResolution: true,
duration: 2000,
}}
mapOnClick={projectClickOnMap}
zoomToLayer
zIndex={5}
// hoverEffect={(selectedFeature, layer) => {
// if (!selectedFeature)
// return layer.setStyle((feature, resolution) =>
// getStyles({
// style: { ...projectGeojsonLayerStyle },
// feature,
// resolution,
// }),
// );
// else {
// selectedFeature.setStyle((feature, resolution) =>
// getStyles({
// style: { ...projectGeojsonLayerStyle, icon: { scale: [0.15, 0.15], url: MarkerIcon } },
// feature,
// resolution,
// }),
// );
// }
// // selectedFeature.setStyle({
// // ...projectGeojsonLayerStyle,
// // icon: { scale: [0.15, 0.15], url: MarkerIcon },
// // });
// // selectedFeature.setStyle();
// }}
/>
)} */}
{projectGeojson && projectGeojson?.features?.length > 0 && (
<ClusterLayer
map={map}
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 @@ -31,6 +31,7 @@ const initialState: ProjectStateTypes = {
last_active: '',
},
projectDashboardLoading: false,
geolocationStatus: false,
projectCommentsList: [],
projectPostCommentsLoading: false,
projectGetCommentsLoading: false,
Expand Down Expand Up @@ -99,6 +100,9 @@ const ProjectSlice = createSlice({
SetProjectDashboardLoading(state, action) {
state.projectDashboardLoading = action.payload;
},
ToggleGeolocationStatus(state, action) {
state.geolocationStatus = action.payload;
},
SetProjectCommentsList(state, action) {
state.projectCommentsList = action.payload;
},
Expand Down
1 change: 1 addition & 0 deletions src/frontend/src/store/types/IProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export type ProjectStateTypes = {
projectDetailsLoading: boolean;
projectDashboardDetail: projectDashboardDetailTypes;
projectDashboardLoading: boolean;
geolocationStatus: boolean;
projectCommentsList: projectCommentsListTypes[];
projectPostCommentsLoading: boolean;
projectGetCommentsLoading: boolean;
Expand Down
Loading

0 comments on commit aec9fb3

Please sign in to comment.