From 6a05fc3f3ddee526d0b3f2cdaedf5e8eb0c8d52d Mon Sep 17 00:00:00 2001 From: Deepak Pradhan Date: Sun, 17 Mar 2024 09:16:26 +0545 Subject: [PATCH] feat: navigation WIP --- src/frontend/src/assets/images/navigation.svg | 9 + .../src/components/home/ProjectListMap.tsx | 154 +++++++++++++++++- 2 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/frontend/src/assets/images/navigation.svg diff --git a/src/frontend/src/assets/images/navigation.svg b/src/frontend/src/assets/images/navigation.svg new file mode 100644 index 0000000000..e11370e122 --- /dev/null +++ b/src/frontend/src/assets/images/navigation.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/frontend/src/components/home/ProjectListMap.tsx b/src/frontend/src/components/home/ProjectListMap.tsx index 1cd903d538..e2af5b0da3 100644 --- a/src/frontend/src/components/home/ProjectListMap.tsx +++ b/src/frontend/src/components/home/ProjectListMap.tsx @@ -2,7 +2,7 @@ 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 { VectorLayer } from '@/components/MapComponent/OpenLayersComponent/Layers'; +import OLVectorLayer from 'ol/layer/Vector'; import { ClusterLayer } from '@/components/MapComponent/OpenLayersComponent/Layers'; import CoreModules from '@/shared/CoreModules'; import { geojsonObjectModel } from '@/constants/geojsonObjectModal'; @@ -12,6 +12,13 @@ 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'; type HomeProjectSummaryType = { features: { geometry: any; properties: any; type: any }[]; @@ -67,6 +74,151 @@ const ProjectListMap = () => { zoom: 4, maxZoom: 17, }); + 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: false, + }, + ); + const locate = document.createElement('div'); + locate.className = 'ol-control ol-unselectable locate'; + locate.innerHTML = ''; + 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: logo, + scale: 0.02, + 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: 10, + referenceFrame: 'screen', + }); + sensor.addEventListener('reading', () => { + 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;