From 3448cbbc2d2c8d4c5cf10cb96007ec4180183286 Mon Sep 17 00:00:00 2001 From: Ahnaf Kamal Date: Thu, 8 Oct 2026 15:40:10 -0400 Subject: [PATCH 1/3] show heading accuracy --- src/components/MapCompass.tsx | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/MapCompass.tsx b/src/components/MapCompass.tsx index e60ccf0..0b9e290 100644 --- a/src/components/MapCompass.tsx +++ b/src/components/MapCompass.tsx @@ -24,6 +24,7 @@ const MapCompass: React.FC = () => { const { ros, connectionStatus: rosStatus } = useROS(); const [heading, setHeading] = useState(null); const [valid, setValid] = useState(false); + const [headingAccuracy, setHeadingAccuracy] = useState(null); useEffect(() => { if (!ros) return; @@ -36,10 +37,15 @@ const MapCompass: React.FC = () => { }); const handleHeading = (msg: any) => { - if (msg.orientation_covariance[8] > 50) { + const yawVariance = msg.orientation_covariance?.[8]; + + setHeadingAccuracy((2 * Math.sqrt(yawVariance) * 180) / Math.PI); + + if (yawVariance > 50) { setValid(false); return; } + const heading = headingFromQuaternion(msg.orientation); setValid(true); setHeading(heading); @@ -88,6 +94,18 @@ const MapCompass: React.FC = () => { ); })} +
+ Accuracy: {headingAccuracy !== null ? `±${headingAccuracy.toFixed(2)}°` : 'N/A'} (2σ) +
); } From 48b0a54991e5d01ade8847dfe431aa96ea2c7dd6 Mon Sep 17 00:00:00 2001 From: Ahnaf Kamal Date: Tue, 13 Oct 2026 00:46:56 -0400 Subject: [PATCH 2/3] Created useGpsHeading hook, calling that in breadcrumbtrail and mapcompass. Breadcrumb displays the heading accuracy. Mapcompass displays the accuracy with a red arch around the compass rim --- src/components/BreadCrumbTrail.tsx | 6 ++ src/components/MapCompass.tsx | 114 +++++++++-------------------- src/hooks/useGpsHeading.ts | 59 +++++++++++++++ 3 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 src/hooks/useGpsHeading.ts diff --git a/src/components/BreadCrumbTrail.tsx b/src/components/BreadCrumbTrail.tsx index 508df84..d4e6674 100644 --- a/src/components/BreadCrumbTrail.tsx +++ b/src/components/BreadCrumbTrail.tsx @@ -4,6 +4,7 @@ import React, { useState, useEffect, useRef } from 'react'; import { Circle, Polyline, useMap } from 'react-leaflet'; import { useROS } from '@/ros/ROSContext'; import { useWaypoints } from '@/contexts/WaypointContext'; +import { useGpsHeading } from '@/hooks/useGpsHeading'; import ROSLIB from 'roslib'; interface Breadcrumb { @@ -36,6 +37,7 @@ export const haversineDistance = ( const BreadcrumbTrail: React.FC = ({ downloadPNG }) => { const map = useMap(); const { ros, connectionStatus } = useROS(); + const { headingAccuracy } = useGpsHeading(); const { addWaypoint } = useWaypoints(); const [breadcrumbs, setBreadcrumbs] = useState([]); const [paused, setPaused] = useState(false); @@ -176,6 +178,10 @@ const BreadcrumbTrail: React.FC = ({ downloadPNG }) => { ) : (
No fix data received yet.
)} +
+ Heading Accuracy:{' '} + {headingAccuracy !== null ? `±${headingAccuracy.toFixed(2)}°` : 'N/A'} (2σ) +
Total Fixes: {breadcrumbs.length}
diff --git a/src/components/MapCompass.tsx b/src/components/MapCompass.tsx index 0b9e290..28e4c6a 100644 --- a/src/components/MapCompass.tsx +++ b/src/components/MapCompass.tsx @@ -1,65 +1,35 @@ -import React, {useEffect, useState} from "react"; -import ROSLIB from 'roslib'; -import { useROS } from '@/ros/ROSContext'; - -type Quaternion = { - x: number; - y: number; - z: number; - w: number; -}; - -//extract yaw, convert to degrees, normalize -const headingFromQuaternion = (q: Quaternion): number => { - const { x, y, z, w } = q; - const yaw = Math.atan2( - 2.0 * (w * z + x * y), - 1.0 - 2.0 * (y * y + z * z) - ); - const heading = (yaw * 180) / Math.PI; - return (heading + 360) % 360; -}; +import React from "react"; +import { useGpsHeading } from '@/hooks/useGpsHeading'; const MapCompass: React.FC = () => { - const { ros, connectionStatus: rosStatus } = useROS(); - const [heading, setHeading] = useState(null); - const [valid, setValid] = useState(false); - const [headingAccuracy, setHeadingAccuracy] = useState(null); - - useEffect(() => { - if (!ros) return; - - const headingTopic = new ROSLIB.Topic({ - ros, - name: '/gps/heading', - messageType: 'sensor_msgs/Imu', - throttle_rate: 100, - }); - - const handleHeading = (msg: any) => { - const yawVariance = msg.orientation_covariance?.[8]; + const { heading, valid, headingAccuracy } = useGpsHeading(); + const RIM_RADIUS = 95; - setHeadingAccuracy((2 * Math.sqrt(yawVariance) * 180) / Math.PI); + // Arc along the rim indicating the accuracy of the heading + const arcPath = (halfAngleDeg: number, r: number): string => { + const a = (halfAngleDeg * Math.PI) / 180; + const x1 = 100 - r * Math.sin(a); + const x2 = 100 + r * Math.sin(a); + const y = 100 - r * Math.cos(a); + const largeArc = halfAngleDeg > 90 ? 1 : 0; + return `M ${x1} ${y} A ${r} ${r} 0 ${largeArc} 1 ${x2} ${y}`; + }; - if (yawVariance > 50) { - setValid(false); - return; - } - - const heading = headingFromQuaternion(msg.orientation); - setValid(true); - setHeading(heading); - }; - - headingTopic.subscribe(handleHeading); - return () => headingTopic.unsubscribe(handleHeading); - }, [ros]); - - return( -
+ return ( +
{/* */} - + + {heading !== null && headingAccuracy !== null && headingAccuracy >= 180 && ( + + )} {/* */} { const x2 = 100 + Math.cos(angle) * 85; const y2 = 100 + Math.sin(angle) * 85; return ( - + ); })} - -
- Accuracy: {headingAccuracy !== null ? `±${headingAccuracy.toFixed(2)}°` : 'N/A'} (2σ) -
+
); } diff --git a/src/hooks/useGpsHeading.ts b/src/hooks/useGpsHeading.ts new file mode 100644 index 0000000..7ebb94c --- /dev/null +++ b/src/hooks/useGpsHeading.ts @@ -0,0 +1,59 @@ +import { useEffect, useState } from 'react'; +import ROSLIB from 'roslib'; +import { useROS } from '@/ros/ROSContext'; + +type Quaternion = { + x: number; + y: number; + z: number; + w: number; +}; + +//extract yaw, convert to degrees, normalize +const headingFromQuaternion = (q: Quaternion): number => { + const { x, y, z, w } = q; + const yaw = Math.atan2( + 2.0 * (w * z + x * y), + 1.0 - 2.0 * (y * y + z * z) + ); + const heading = (yaw * 180) / Math.PI; + return (heading + 360) % 360; +}; + +export function useGpsHeading(){ + const { ros } = useROS(); + const [heading, setHeading] = useState(null); + const [valid, setValid] = useState(false); + const [headingAccuracy, setHeadingAccuracy] = useState(null); + + useEffect(() => { + if (!ros) return; + + const headingTopic = new ROSLIB.Topic({ + ros, + name: '/gps/heading', + messageType: 'sensor_msgs/Imu', + throttle_rate: 100, + }); + + const handleHeading = (msg: any) => { + const yawVariance = msg.orientation_covariance?.[8]; + + setHeadingAccuracy((2 * Math.sqrt(yawVariance) * 180) / Math.PI); + + if (yawVariance > 50) { + setValid(false); + return; + } + + const heading = headingFromQuaternion(msg.orientation); + setValid(true); + setHeading(heading); + }; + + headingTopic.subscribe(handleHeading); + return () => headingTopic.unsubscribe(handleHeading); + }, [ros]); + + return { heading, valid, headingAccuracy }; +} From d2a93b1e64ab71f07d8814a18da817fc38728e54 Mon Sep 17 00:00:00 2001 From: Ahnaf Kamal Date: Tue, 13 Oct 2026 22:08:46 -0400 Subject: [PATCH 3/3] comments --- src/components/MapCompass.tsx | 2 +- src/hooks/useGpsHeading.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/MapCompass.tsx b/src/components/MapCompass.tsx index 28e4c6a..20cce14 100644 --- a/src/components/MapCompass.tsx +++ b/src/components/MapCompass.tsx @@ -20,7 +20,7 @@ const MapCompass: React.FC = () => { {/* */} - {heading !== null && headingAccuracy !== null && headingAccuracy >= 180 && ( + {heading !== null && headingAccuracy !== null && headingAccuracy <= 180 && ( 50) { + if (yawVariance > 0.5) { setValid(false); return; }