From 712bf3fc276aacc80f501ac8855b804c3a9ebab7 Mon Sep 17 00:00:00 2001 From: Rishav-N Date: Sun, 26 Apr 2026 23:22:10 +0000 Subject: [PATCH] Added rover path and test new object detection stream --- .../__pycache__/ros_bridge.cpython-310.pyc | Bin 3978 -> 6625 bytes umdloop_gui_web/app/GUI functions/MapView.js | 148 +++++++++++++++++- umdloop_gui_web/ros_bridge.py | 117 +++++++++++++- umdloop_gui_web/server.py | 124 ++++++--------- 4 files changed, 303 insertions(+), 86 deletions(-) diff --git a/umdloop_gui_web/__pycache__/ros_bridge.cpython-310.pyc b/umdloop_gui_web/__pycache__/ros_bridge.cpython-310.pyc index 38fc5145d1ce5cae9c46a5eba6fa46c429eaa65d..fc82c4067c02ee87db9d611ce1c3017879f2082b 100644 GIT binary patch literal 6625 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 WIfWY-F7*wcP88_VhFRMzU-=*8H$wdY diff --git a/umdloop_gui_web/app/GUI functions/MapView.js b/umdloop_gui_web/app/GUI functions/MapView.js index cfd5642..3545726 100644 --- a/umdloop_gui_web/app/GUI functions/MapView.js +++ b/umdloop_gui_web/app/GUI functions/MapView.js @@ -1,10 +1,29 @@ "use client"; -import React, { useState, useRef, useCallback, useEffect } from "react"; -import { Map, Marker } from "react-map-gl/maplibre"; +import React, { useState, useRef, useCallback, useEffect, useMemo } from "react"; +import { Map, Marker, Source, Layer } from "react-map-gl/maplibre"; import "maplibre-gl/dist/maplibre-gl.css"; import { useLocalTiles } from "../config"; +const PLAN_LINE_LAYER = { + id: "global-plan-line", + type: "line", + paint: { + "line-color": "#3b82f6", + "line-width": 3, + "line-dasharray": [4, 2], + }, +}; + +const DRIVEN_PATH_LAYER = { + id: "driven-path-line", + type: "line", + paint: { + "line-color": "#f59e0b", + "line-width": 2, + }, +}; + export default function MapView({ selectedSubsystem, titleOverride }) { const [viewState, setViewState] = useState({ longitude: -76.9378, @@ -15,9 +34,18 @@ export default function MapView({ selectedSubsystem, titleOverride }) { const [roverPosition, setRoverPosition] = useState(null); const [rosStatus, setRosStatus] = useState("no fix"); const [followRover, setFollowRover] = useState(false); + + // Global plan: raw coords held in a ref; planVersion bumps only when plan actually changes + const globalPlanRef = useRef(null); + const [planVersion, setPlanVersion] = useState(0); + + // Driven path history — [[lon, lat], ...] + const [drivenPath, setDrivenPath] = useState([]); + const lastDrivenPoint = useRef(null); + const mapRef = useRef(); - // Poll Flask backend for latest /gps/fix data (sourced from ROS via ros_bridge.py) + // Poll Flask backend for latest /gps/fix and accumulate driven path useEffect(() => { const poll = async () => { try { @@ -27,7 +55,18 @@ export default function MapView({ selectedSubsystem, titleOverride }) { const pos = { latitude: data.latitude, longitude: data.longitude }; setRoverPosition(pos); setRosStatus("fix"); - // Keep map centered on rover when follow mode is active + + // Accumulate driven path; only append if moved more than ~0.5 m + const lon = data.longitude; + const lat = data.latitude; + const last = lastDrivenPoint.current; + const MIN_DELTA = 0.000005; // ~0.5 m in degrees + if (!last || Math.abs(lon - last[0]) > MIN_DELTA || Math.abs(lat - last[1]) > MIN_DELTA) { + const point = [lon, lat]; + lastDrivenPoint.current = point; + setDrivenPath((prev) => [...prev, point]); + } + setFollowRover((prev) => { if (prev) { setViewState((vs) => ({ ...vs, latitude: pos.latitude, longitude: pos.longitude })); @@ -47,6 +86,53 @@ export default function MapView({ selectedSubsystem, titleOverride }) { return () => clearInterval(id); }, []); + // Poll Flask backend for latest /plan (global plan from Nav2). + // Only bump planVersion (triggering re-render) when the plan actually changes. + useEffect(() => { + const fetchPlan = async () => { + try { + const res = await fetch("http://127.0.0.1:5000/navigation/plan"); + const data = await res.json(); + if (data.available && data.coordinates?.length >= 2) { + const coords = data.coordinates; + const prev = globalPlanRef.current; + const changed = + !prev || + prev.length !== coords.length || + prev[0][0] !== coords[0][0] || + prev[0][1] !== coords[0][1]; + if (changed) { + globalPlanRef.current = coords; + setPlanVersion((v) => v + 1); + } + } + } catch { + // silently ignore; plan display is optional + } + }; + + fetchPlan(); + const id = setInterval(fetchPlan, 2000); + return () => clearInterval(id); + }, []); + + // Stable GeoJSON objects — only recreated when underlying data actually changes + const planGeoJSON = useMemo( + () => + globalPlanRef.current?.length >= 2 + ? { type: "Feature", geometry: { type: "LineString", coordinates: globalPlanRef.current } } + : null, + [planVersion] + ); + + const drivenGeoJSON = useMemo( + () => + drivenPath.length >= 2 + ? { type: "Feature", geometry: { type: "LineString", coordinates: drivenPath } } + : null, + [drivenPath] + ); + // Snap to rover and enable follow mode const centerOnRover = () => { if (!roverPosition) return; @@ -80,6 +166,11 @@ export default function MapView({ selectedSubsystem, titleOverride }) { const deleteAllWaypoints = () => setWaypoints([]); + const clearDrivenPath = () => { + setDrivenPath([]); + lastDrivenPoint.current = null; + }; + const MAPTILER_KEY = "DDQqKsPBfdOZOVxgcoy5"; const tileUrl = useLocalTiles() ? "/tiles/{z}/{x}/{y}.jpg" @@ -143,8 +234,41 @@ export default function MapView({ selectedSubsystem, titleOverride }) { {followRover ? "Following Rover" : "Center on Rover"} - {/* Waypoint controls */} + {/* Legend */} +
+ {planGeoJSON && ( + + + Global Plan + + )} + {drivenPath.length >= 2 && ( + + + Driven Path + + )} +
+ + {/* Controls */}
+ {drivenPath.length >= 2 && ( + + )} Waypoints: {waypoints.length} {waypoints.length > 0 && (