Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: navigation WIP #1361

Merged
merged 1 commit into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/frontend/src/assets/images/navigation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
154 changes: 153 additions & 1 deletion src/frontend/src/components/home/ProjectListMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 }[];
Expand Down Expand Up @@ -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 = '<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: 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;
Expand Down
Loading