Autonomy Software C++ 24.5.1
Welcome to the Autonomy Software repository of the Mars Rover Design Team (MRDT) at Missouri University of Science and Technology (Missouri S&T)! API reference contains the source code and other resources for the development of the autonomy software for our Mars rover. The Autonomy Software project aims to compete in the University Rover Challenge (URC) by demonstrating advanced autonomous capabilities and robust navigation algorithms.
Loading...
Searching...
No Matches
pathplanners::GeoPlanner Class Reference

This class implements a geospatial path planner that uses AStar's algorithm with a bias towards travel scores to find the optimal path between two points. geospatial data is fetched from the LidarHandler, and the path is planned using the Eigen library for matrix operations. More...

#include <GeoPlanner.h>

Collaboration diagram for pathplanners::GeoPlanner:

Classes

struct  PlannerState
 This struct represents the state of a node in the path planning algorithm. More...
 
struct  PlannerStateCompare
 This struct is used to compare two PlannerState objects based on their cost. More...
 
struct  TileKey
 This struct represents a tile key in the implicit graph representation. We divide the plan into a regular grid of tiles of size dTileSize*dTileSize meters. A tile covering the real-world X coordinate in [i*dTileSize, (i+1)*dTileSize) and Y coordinate in [j*dTileSize, (j+1)*dTileSize) is represented by the key (i, j). More...
 
struct  TileKeyEqual
 This struct is used to compare two TileKey objects for equality. After the hashbuckets narrow down candidates, we need to still confirm that the X and Y coordinates are equal to ensure they are the same tile. More...
 
struct  TileKeyHash
 This struct is used to hash TileKey objects for use in unordered maps. It combines the X and Y coordinates to create a unique hash for each tile key. We hash stKey.nX and stKey.nY separately and combine them using XOR and bit shifting. More...
 

Public Member Functions

 GeoPlanner (double dTileSize=50.0)
 Construct a new Geo Planner:: Geo Planner object.
 
 ~GeoPlanner ()
 Destroy the Geo Planner:: Geo Planner object.
 
std::vector< geoops::WaypointPlanPath (LiDARHandler *pLiDARHandler, const geoops::UTMCoordinate &stStart, const geoops::UTMCoordinate &stEnd, double dSearchRadius=2.0, double dMaxSearchTimeSeconds=240.0, bool bPlotPath=false)
 Plan a path from the start to the end UTM coordinates using A* algorithm.
 
void ClearGeoCache ()
 Clear all cached tiles and KD-Tree data.
 
void SetTileSize (double dTileSize)
 Set the size of each tile in meters.
 
void SetMinTravScore (double dMinTravScore)
 Set the minimum traversal score for path planning.
 
void SetBetaBias (double dBetaBias)
 Set the beta bias for travel scores in path planning.
 
double GetTileSize () const
 Get the size of each tile in meters.
 
double GetMinTravScore () const
 Get the minimum traversal score for path planning.
 
double GetBetaBias () const
 Get the beta bias for travel scores in path planning.
 

Private Member Functions

bool InitializeSearch (const geoops::UTMCoordinate &stStart, const geoops::UTMCoordinate &stEnd)
 Initialize the search by caching the start and end tiles and setting up initial states.
 
void SearchAStar ()
 Perform the A* search algorithm to find the optimal path.
 
std::vector< geoops::WaypointReconstructPath () const
 Plan a path from the start to the end UTM coordinates using A* algorithm.
 
void CheckAndLoadTile (const PlannerState &stCurrentState)
 Check if the tile containing the current state is loaded, and if not, load it.
 
PlannerState FindClosestLiDARPoint (const geoops::UTMCoordinate &stCoordinate)
 Find the closest LiDAR point to the given UTM coordinate.
 
void PlotPathAndTerrain (const std::vector< geoops::Waypoint > &vPath) const
 Plot the given path and the terrain points that the path goes through.
 
double EuclideanDistance (double dEasting1, double dNorthing1, double dAltitude1, double dEasting2, double dNorthing2, double dAltitude2) const
 Calculate the distance between two UTM coordinates.
 

Private Attributes

const std::function< void(const rovecomm::RoveCommPacket< float > &, const sockaddr_in &)> MinTravScore
 Callback function used to set the minimum travel score for path planning.
 
const std::function< void(const rovecomm::RoveCommPacket< float > &, const sockaddr_in &)> BetaBias
 Callback function used to set the beta bias for travel scores in path planning.
 
int m_nStartID
 
int m_nEndID
 
double m_dBeta
 
double m_dMinTravScore
 
double m_dTileSize
 
double m_dSearchRadius
 
double m_dMaxSearchTimeSeconds
 
LiDARHandlerm_pLiDARHandler
 
std::unique_ptr< logging::graphing::PathTracerm_pPathTracer
 
std::unique_ptr< KDTree2D > m_pKDTree
 
std::mutex m_muPathGenMutex
 
std::priority_queue< PlannerState, std::vector< PlannerState >, PlannerStateComparem_pqOpenSetNextBest
 
std::unordered_map< int, int > m_umPredecessors
 
std::unordered_set< int > m_usClosedSet
 
std::unordered_map< int, PlannerStatem_umAllStates
 
std::unordered_map< TileKey, std::vector< LiDARHandler::PointRow >, TileKeyHash, TileKeyEqualm_umTileMapCache
 
std::unordered_set< TileKey, TileKeyHash, TileKeyEqualm_usKDTreeInsertedTiles
 

Detailed Description

This class implements a geospatial path planner that uses AStar's algorithm with a bias towards travel scores to find the optimal path between two points. geospatial data is fetched from the LidarHandler, and the path is planned using the Eigen library for matrix operations.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-14

Constructor & Destructor Documentation

◆ GeoPlanner()

pathplanners::GeoPlanner::GeoPlanner ( double  dTileSize = 50.0)

Construct a new Geo Planner:: Geo Planner object.

Parameters
dTileSize- The size of each tile in meters. Default is 5.0 meters.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-24
35 {
36 // Initialize member variables.
37 m_dTileSize = dTileSize;
38 m_pLiDARHandler = nullptr;
39 m_nStartID = -1;
40 m_nEndID = -1;
41 m_dBeta = 1.0;
42 m_dMinTravScore = 0.0;
43 m_dSearchRadius = 3.0;
44 m_dMaxSearchTimeSeconds = 120.0;
45 m_pPathTracer = std::make_unique<logging::graphing::PathTracer>("GeoPlanner Path", false);
46 m_pKDTree = std::make_unique<KDTree2D>(PointKDAccessor());
47
48 // Create path plotter layers.
49 m_pPathTracer->CreateDotLayer("TerrainPoints", "gray", false);
50 m_pPathTracer->CreatePathLayer("RoverPath", "red");
51
52 // Make sure RoveComm UDP Node is initialized.
53 if (network::g_pRoveCommUDPNode != nullptr)
54 {
55 // Set RoveComm Node callbacks.
56 network::g_pRoveCommUDPNode->AddUDPCallback<float>(MinTravScore, manifest::Autonomy::COMMANDS.find("SETMINTRAVSCORE")->second.DATA_ID);
57 network::g_pRoveCommUDPNode->AddUDPCallback<float>(BetaBias, manifest::Autonomy::COMMANDS.find("SETBETABIAS")->second.DATA_ID);
58 }
59
60 // Log initialization message.
61 LOG_INFO(logging::g_qSharedLogger, "GeoPlanner initialized with tile size: {} meters", std::to_string(dTileSize));
62 }
const std::function< void(const rovecomm::RoveCommPacket< float > &, const sockaddr_in &)> MinTravScore
Callback function used to set the minimum travel score for path planning.
Definition GeoPlanner.h:251
const std::function< void(const rovecomm::RoveCommPacket< float > &, const sockaddr_in &)> BetaBias
Callback function used to set the beta bias for travel scores in path planning.
Definition GeoPlanner.h:279

◆ ~GeoPlanner()

pathplanners::GeoPlanner::~GeoPlanner ( )

Destroy the Geo Planner:: Geo Planner object.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-27
72 {
73 // Destructor
74 }

Member Function Documentation

◆ PlanPath()

std::vector< geoops::Waypoint > pathplanners::GeoPlanner::PlanPath ( LiDARHandler pLiDARHandler,
const geoops::UTMCoordinate stStart,
const geoops::UTMCoordinate stEnd,
double  dSearchRadius = 2.0,
double  dMaxSearchTimeSeconds = 240.0,
bool  bPlotPath = false 
)

Plan a path from the start to the end UTM coordinates using A* algorithm.

Parameters
pLiDARHandler- Pointer to the LiDARHandler instance for fetching geospatial data.
stStart- The starting UTM coordinate.
stEnd- The ending UTM coordinate.
dBeta- A bias factor for traversal score weighting. Higher values favor safer paths with better trav_scores.
dSearchRadius- The radius in meters to search for neighboring points during path planning.
dMaxSearchTimeSeconds- The maximum time in seconds to spend searching for a path.
bPlotPath- Whether to plot the planned path and terrain in 3D.
Returns
std::vector<geoops::Waypoint> - The planned path waypoints.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-28
97 {
98 // Acquire a mutex lock so we don't try to plan multiple paths at the same time.
99 std::lock_guard<std::mutex> lkPathLock(m_muPathGenMutex);
100
101 // Initialize member variables.
102 m_pLiDARHandler = pLiDARHandler;
103 m_dSearchRadius = dSearchRadius;
104 m_dMaxSearchTimeSeconds = dMaxSearchTimeSeconds;
105
106 // Submit logger message.
107 LOG_NOTICE(logging::g_qSharedLogger,
108 "Starting GeoPlanner path planning from ({:.2f}, {:.2f}) to ({:.2f}, {:.2f}) with beta: {}, search radius: {} meters, min traversal score: {}.",
109 stStart.dEasting,
110 stStart.dNorthing,
111 stEnd.dEasting,
112 stEnd.dNorthing,
113 m_dBeta,
114 m_dSearchRadius,
115 m_dMinTravScore);
116
117 // Validate beta to avoid accidental disabling.
118 if (m_dBeta <= 0.0)
119 {
120 m_dBeta = 0.001;
121 LOG_WARNING(logging::g_qSharedLogger, "GeoPlanner: supplied dBeta {} invalid; using fallback 0.001.", m_dBeta);
122 }
123
124 // Store the start time.
125 std::chrono::time_point<std::chrono::high_resolution_clock> tmStartTime = std::chrono::high_resolution_clock::now();
126
127 // Initialize search for new start and end points.
128 this->InitializeSearch(stStart, stEnd);
129 // Track time taken to initialize search.
130 std::chrono::time_point<std::chrono::high_resolution_clock> tmAfterInit = std::chrono::high_resolution_clock::now();
131 std::chrono::duration<double> dInitDuration = tmAfterInit - tmStartTime;
132 LOG_INFO(logging::g_qSharedLogger, "GeoPlanner search initialization took {:.6f} seconds.", dInitDuration.count());
133
134 // Run A* search algorithm.
135 this->SearchAStar();
136 // Track time taken to perform search.
137 std::chrono::time_point<std::chrono::high_resolution_clock> tmAfterSearch = std::chrono::high_resolution_clock::now();
138 std::chrono::duration<double> dSearchDuration = tmAfterSearch - tmAfterInit;
139 LOG_INFO(logging::g_qSharedLogger, "GeoPlanner A* search took {:.6f} seconds.", dSearchDuration.count());
140
141 // Reconstruct the path from the predecessor map.
142 std::vector<geoops::Waypoint> vPath = this->ReconstructPath();
143 // Track total time taken for path planning.
144 std::chrono::time_point<std::chrono::high_resolution_clock> tmEndTime = std::chrono::high_resolution_clock::now();
145 std::chrono::duration<double> dTotalDuration = tmEndTime - tmAfterSearch;
146 LOG_INFO(logging::g_qSharedLogger, "GeoPlanner path reconstruction took {:.6f} seconds.", dTotalDuration.count());
147
148 // Log the total time taken for path planning.
149 std::chrono::duration<double> dOverallDuration = tmEndTime - tmStartTime;
150 LOG_NOTICE(logging::g_qSharedLogger, "GeoPlanner total path planning took {:.6f} seconds. Path is {} waypoints long.", dOverallDuration.count(), vPath.size());
151
152 // Plot the path and terrain if requested.
153 if (bPlotPath && !vPath.empty())
154 {
155 this->PlotPathAndTerrain(vPath);
156 }
157 else if (bPlotPath && vPath.empty())
158 {
159 LOG_WARNING(logging::g_qSharedLogger, "No path to plot.");
160 }
161
162 return vPath;
163 }
std::vector< geoops::Waypoint > ReconstructPath() const
Plan a path from the start to the end UTM coordinates using A* algorithm.
Definition GeoPlanner.cpp:469
void PlotPathAndTerrain(const std::vector< geoops::Waypoint > &vPath) const
Plot the given path and the terrain points that the path goes through.
Definition GeoPlanner.cpp:656
bool InitializeSearch(const geoops::UTMCoordinate &stStart, const geoops::UTMCoordinate &stEnd)
Initialize the search by caching the start and end tiles and setting up initial states.
Definition GeoPlanner.cpp:270
void SearchAStar()
Perform the A* search algorithm to find the optimal path.
Definition GeoPlanner.cpp:320
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ClearGeoCache()

void pathplanners::GeoPlanner::ClearGeoCache ( )

Clear all cached tiles and KD-Tree data.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-27
173 {
174 // Acquire a mutex lock so we don't try to clear cache while planning a path.
175 std::lock_guard<std::mutex> lkResourceLock(m_muPathGenMutex);
176
177 // Clear all cached tiles and KD-Tree data.
178 m_umTileMapCache.clear();
179 }

◆ SetTileSize()

void pathplanners::GeoPlanner::SetTileSize ( double  dTileSize)

Set the size of each tile in meters.

Parameters
dTileSize- The new tile size in meters.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
190 {
191 m_dTileSize = dTileSize;
192 }

◆ SetMinTravScore()

void pathplanners::GeoPlanner::SetMinTravScore ( double  dMinTravScore)

Set the minimum traversal score for path planning.

Parameters
dMinTravScore- The new minimum traversal score.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
203 {
204 m_dMinTravScore = dMinTravScore;
205 }

◆ SetBetaBias()

void pathplanners::GeoPlanner::SetBetaBias ( double  dBetaBias)

Set the beta bias for travel scores in path planning.

Parameters
dBetaBias- The new beta bias value.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
216 {
217 m_dBeta = dBetaBias;
218 }

◆ GetTileSize()

double pathplanners::GeoPlanner::GetTileSize ( ) const

Get the size of each tile in meters.

Returns
double - The current tile size in meters.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
229 {
230 return m_dTileSize;
231 }

◆ GetMinTravScore()

double pathplanners::GeoPlanner::GetMinTravScore ( ) const

Get the minimum traversal score for path planning.

Returns
double - The current minimum traversal score.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
242 {
243 return m_dMinTravScore;
244 }

◆ GetBetaBias()

double pathplanners::GeoPlanner::GetBetaBias ( ) const

Get the beta bias for travel scores in path planning.

Returns
double - The current beta bias value.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-28
255 {
256 return m_dBeta;
257 }

◆ InitializeSearch()

bool pathplanners::GeoPlanner::InitializeSearch ( const geoops::UTMCoordinate stStart,
const geoops::UTMCoordinate stEnd 
)
private

Initialize the search by caching the start and end tiles and setting up initial states.

Parameters
stStart- The starting UTM coordinate.
stEnd- The ending UTM coordinate.
Returns
true - Initialization successful.
false - Initialization failed due to invalid start or end points.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-27
271 {
272 // Clear previous search data.
273 m_umAllStates.clear();
274 m_umPredecessors.clear();
275 m_usClosedSet.clear();
276 while (!m_pqOpenSetNextBest.empty())
277 {
278 m_pqOpenSetNextBest.pop();
279 }
280 // Reset start and end IDs.
281 m_nStartID = -1;
282 m_nEndID = -1;
283 // Clear KD-Tree.
284 m_usKDTreeInsertedTiles.clear();
285 m_pKDTree->clear();
286
287 // Cache the start and end tiles and update ID values.
288 PlannerState stStartState = FindClosestLiDARPoint(stStart);
289 PlannerState stEndState = FindClosestLiDARPoint(stEnd);
290 m_nStartID = stStartState.nID;
291 m_nEndID = stEndState.nID;
292
293 // Check if valid start and end points were found.
294 if (m_nStartID == -1 || m_nEndID == -1)
295 {
296 LOG_ERROR(logging::g_qSharedLogger, "Invalid start or end point for path planning. Start ID: {}, End ID: {}.", m_nStartID, m_nEndID);
297 return false;
298 }
299
300 // Log the chosen start and end UTM positions.
301 LOG_INFO(logging::g_qSharedLogger,
302 "GeoPlanner initialized search with Start ID: {} at ({:.2f}, {:.2f}), End ID: {} at ({:.2f}, {:.2f}).",
303 m_nStartID,
304 stStartState.dEasting,
305 stStartState.dNorthing,
306 m_nEndID,
307 stEndState.dEasting,
308 stEndState.dNorthing);
309
310 return true;
311 }
PlannerState FindClosestLiDARPoint(const geoops::UTMCoordinate &stCoordinate)
Find the closest LiDAR point to the given UTM coordinate.
Definition GeoPlanner.cpp:617
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SearchAStar()

void pathplanners::GeoPlanner::SearchAStar ( )
private

Perform the A* search algorithm to find the optimal path.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-28
321 {
322 // Store the start time.
323 std::chrono::high_resolution_clock::time_point tmStartTime = std::chrono::high_resolution_clock::now();
324
325 // Initialize the start state.
326 PlannerState stStartState = m_umAllStates[m_nStartID];
327 stStartState.dGCost = 0.0;
328 // Heuristic cost (Euclidean distance to goal).
329 stStartState.dHCost = this->EuclideanDistance(stStartState.dEasting,
330 stStartState.dNorthing,
331 stStartState.dAltitude,
332 m_umAllStates[m_nEndID].dEasting,
333 m_umAllStates[m_nEndID].dNorthing,
334 m_umAllStates[m_nEndID].dAltitude);
335 // Cumulative traversal score starts as the start point's score.
336 m_umAllStates[m_nStartID] = stStartState;
337
338 // Push start into the open set priority queue.
339 m_pqOpenSetNextBest.push(m_umAllStates[m_nStartID]);
340
341 // Main A* search loop.
342 while (!m_pqOpenSetNextBest.empty())
343 {
344 // Get the node in the open set with the lowest f = g + h cost.
345 PlannerState stCurrentState = m_pqOpenSetNextBest.top();
346 // Remove the current node from the open set.
347 m_pqOpenSetNextBest.pop();
348
349 // Check stale: compare popped.g to canonical g in m_umAllStates.
350 std::unordered_map<int, pathplanners::GeoPlanner::PlannerState>::iterator itState = m_umAllStates.find(stCurrentState.nID);
351 if (itState == m_umAllStates.end())
352 {
353 continue; // This is unexpected, but skip if not found.
354 }
355 // If the popped state has a higher G cost than the recorded state, it's stale.
356 if (stCurrentState.dGCost > itState->second.dGCost + 1e-9)
357 {
358 // Stale entry: we previously found a better path and pushed that copy.
359 continue;
360 }
361
362 // If we've already evaluated it (closed set), skip.
363 if (m_usClosedSet.find(stCurrentState.nID) != m_usClosedSet.end())
364 {
365 continue;
366 }
367
368 // If we reached the goal.
369 if (stCurrentState.nID == m_nEndID)
370 {
371 LOG_INFO(logging::g_qSharedLogger, "Goal reached in A* search.");
372 return;
373 }
374
375 // Mark the current node as evaluated by adding it to the closed set.
376 m_usClosedSet.insert(stCurrentState.nID);
377
378 // Ensure the tile containing the current state is loaded.
379 this->CheckAndLoadTile(stCurrentState);
380
381 // Query the KDTree to get the neighbors in the given search radius.
382 std::vector<LiDARHandler::PointRow> vNeighbors;
383 KDQueryPoint stQueryPoint{stCurrentState.dEasting, stCurrentState.dNorthing};
384 m_pKDTree->find_within_range(stQueryPoint, m_dSearchRadius, std::back_inserter(vNeighbors));
385
386 // Loop through the neighboring points within the radius.
387 for (LiDARHandler::PointRow& stPoint : vNeighbors)
388 {
389 // Skip if this neighbor is already in the closed set.
390 if (m_usClosedSet.find(stPoint.nID) != m_usClosedSet.end())
391 {
392 continue; // Already evaluated.
393 }
394
395 /*
396 Calculate the tentative G cost for this neighbor.
397 */
398 // Calculate Euclidean distance to neighbor.
399 double dDistance = this->EuclideanDistance(stCurrentState.dEasting,
400 stCurrentState.dNorthing,
401 stCurrentState.dAltitude,
402 stPoint.dEasting,
403 stPoint.dNorthing,
404 stPoint.dAltitude);
405 // Clamp traversal score [0,1] just to be safe.
406 double dScore = std::clamp(stPoint.dTraversalScore, 0.0, 1.0);
407 // Calculate cost multiplier based on traversal score and beta.
408 double dMultiplier = 1.0 + m_dBeta * (1.0 - dScore); // <1 not needed; this is >=1
409 // Calculate tentative G cost.
410 double dTentativeGCost = stCurrentState.dGCost + dDistance * dMultiplier;
411
412 // If this neighbor is not in the open set or we found a better path to it.
413 std::unordered_map<int, PlannerState>::const_iterator itNeighborState = m_umAllStates.find(stPoint.nID);
414 // If this path to neighbor is better, update its state.
415 if (itNeighborState == m_umAllStates.end() || dTentativeGCost + 1e-12 < itNeighborState->second.dGCost)
416 {
417 // Update the neighbor's state.
418 PlannerState stNeighborState;
419 stNeighborState.nID = stPoint.nID;
420 stNeighborState.dEasting = stPoint.dEasting;
421 stNeighborState.dNorthing = stPoint.dNorthing;
422 stNeighborState.dAltitude = stPoint.dAltitude;
423 stNeighborState.nZone = std::stoi(stPoint.szZone.substr(0, 2)); // Assuming zone is stored as string.
424 stNeighborState.bInNorthernHemisphere = (stPoint.dNorthing >= 0); // Simple check based on northing.
425 stNeighborState.dGCost = dTentativeGCost;
426 // Heuristic cost (Euclidean distance to goal).
427 stNeighborState.dHCost = this->EuclideanDistance(stNeighborState.dEasting,
428 stNeighborState.dNorthing,
429 stNeighborState.dAltitude,
430 m_umAllStates[m_nEndID].dEasting,
431 m_umAllStates[m_nEndID].dNorthing,
432 m_umAllStates[m_nEndID].dAltitude);
433
434 // Update the state map.
435 m_umAllStates[stPoint.nID] = stNeighborState;
436 // Update the predecessor map.
437 m_umPredecessors[stPoint.nID] = stCurrentState.nID;
438
439 // Add the neighbor to the open set. Duplicates are handled by checking the cost when popping.
440 m_pqOpenSetNextBest.push(stNeighborState);
441 }
442 }
443
444 // Log progress every 100 iterations.
445 if (m_usClosedSet.size() % 1000 == 0)
446 {
447 LOG_INFO(logging::g_qSharedLogger, "A* search progress: {} nodes evaluated.", m_usClosedSet.size());
448 }
449
450 // Check if we've exceeded the maximum search time.
451 std::chrono::high_resolution_clock::time_point tmCurrentTime = std::chrono::high_resolution_clock::now();
452 std::chrono::duration<double> dElapsedTime = tmCurrentTime - tmStartTime;
453 if (dElapsedTime.count() >= m_dMaxSearchTimeSeconds)
454 {
455 LOG_WARNING(logging::g_qSharedLogger, "A* search terminated after exceeding max search time of {} seconds.", m_dMaxSearchTimeSeconds);
456 return;
457 }
458 }
459 }
void CheckAndLoadTile(const PlannerState &stCurrentState)
Check if the tile containing the current state is loaded, and if not, load it.
Definition GeoPlanner.cpp:527
double EuclideanDistance(double dEasting1, double dNorthing1, double dAltitude1, double dEasting2, double dNorthing2, double dAltitude2) const
Calculate the distance between two UTM coordinates.
Definition GeoPlanner.cpp:762
Definition LiDARHandler.h:38
Here is the call graph for this function:
Here is the caller graph for this function:

◆ ReconstructPath()

std::vector< geoops::Waypoint > pathplanners::GeoPlanner::ReconstructPath ( ) const
private

Plan a path from the start to the end UTM coordinates using A* algorithm.

Returns
std::vector<geoops::Waypoint> - The planned path as a vector of waypoints.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-27
470 {
471 // Create instance variables.
472 std::vector<geoops::Waypoint> vPath;
473 int nCurrentID = m_nEndID;
474
475 // If the goal wasn't reached, return an empty path.
476 if (m_umPredecessors.find(m_nEndID) == m_umPredecessors.end())
477 {
478 LOG_WARNING(logging::g_qSharedLogger, "Path was not found.");
479 return {};
480 }
481
482 // Backtrack from the end node to the start node using the cameFrom map.
483 while (true)
484 {
485 // Add the current node ID to the path.
486 std::unordered_map<int, PlannerState>::const_iterator itPlannerState = m_umAllStates.find(nCurrentID);
487 if (itPlannerState != m_umAllStates.end())
488 {
489 // Found the PlannerState for this point ID.
490 const PlannerState& stState = itPlannerState->second;
491 // Convert PlannerState to Waypoint and add to path.
492 vPath.emplace_back(geoops::UTMCoordinate(stState.dEasting, stState.dNorthing, stState.nZone, stState.bInNorthernHemisphere, stState.dAltitude),
493 geoops::WaypointType::eNavigationWaypoint,
494 0.5,
495 stState.nID);
496 }
497 else
498 {
499 // Submit logger message.
500 LOG_WARNING(logging::g_qSharedLogger, "PlannerState for point ID {} not found during path reconstruction.", nCurrentID);
501 }
502
503 // If we've reached the start node, break the loop.
504 if (nCurrentID == m_nStartID)
505 {
506 break;
507 }
508
509 // Look at the predecessor map to get the parent node ID.
510 nCurrentID = m_umPredecessors.at(nCurrentID);
511 }
512
513 // Reverse the path to get it from start to end.
514 std::reverse(vPath.begin(), vPath.end());
515
516 return vPath;
517 }
This struct stores/contains information about a UTM coordinate.
Definition GeospatialOperations.hpp:210
Here is the caller graph for this function:

◆ CheckAndLoadTile()

void pathplanners::GeoPlanner::CheckAndLoadTile ( const PlannerState stCurrentState)
private

Check if the tile containing the current state is loaded, and if not, load it.

Parameters
stCurrentState- The current planner state.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-25
528 {
529 // Determine which tile the current state is in.
530 int nTileX = static_cast<int>(std::floor(stCurrentState.dEasting / m_dTileSize));
531 int nTileY = static_cast<int>(std::floor(stCurrentState.dNorthing / m_dTileSize));
532 TileKey stTileKey{nTileX, nTileY};
533
534 // Check if the tile is already loaded.
535 if (m_umTileMapCache.find(stTileKey) == m_umTileMapCache.end())
536 {
537 // Tile is not loaded, so we need to load it.
539 stFilter.dEasting = (nTileX + 0.5) * m_dTileSize; // Center of the tile in easting.
540 stFilter.dNorthing = (nTileY + 0.5) * m_dTileSize; // Center of the tile in northing.
541 stFilter.dRadius = std::sqrt(2) * (m_dTileSize / 2.0); // Radius to cover the entire tile
542 stFilter.dTraversalScore = LiDARHandler::PointFilter::Range<double>{m_dMinTravScore, 1.0}; // Only load points with sufficient traversal score.
543 std::vector<LiDARHandler::PointRow> vTilePoints = m_pLiDARHandler->GetLiDARData(stFilter);
544
545 // Check if we got any points back.
546 if (vTilePoints.empty())
547 {
548 // Log warning message.
549 LOG_WARNING(logging::g_qSharedLogger, "No LiDAR points found in tile ({}, {}).", nTileX, nTileY);
550 return;
551 }
552
553 // First, we insert the points into the tile cache.
554 m_umTileMapCache[stTileKey] = vTilePoints;
555
556 // Log info message.
557 LOG_DEBUG(logging::g_qSharedLogger, "Loaded tile ({}, {}) with {} points into cache.", nTileX, nTileY, vTilePoints.size());
558 }
559
560 // Check if this tile is already loaded into the KD-Tree.
561 if (m_usKDTreeInsertedTiles.find(stTileKey) == m_usKDTreeInsertedTiles.end())
562 {
563 // Get the points for this tile from the cache.
564 std::vector<LiDARHandler::PointRow>& vTilePoints = m_umTileMapCache[stTileKey];
565 // Next, we will insert the points into the KD-Tree for fast spatial queries.
566 for (LiDARHandler::PointRow& stLiDARPoint : vTilePoints)
567 {
568 // Insert this point into the KDTree.
569 m_pKDTree->insert(stLiDARPoint);
570
571 /*
572 Add tile points with IDs to the all states map.
573 */
574 // Convert the szZone ("15S") to an integer zone number (15) and hemisphere (true/false, north/south).
575 int nZoneNumber = std::stoi(stLiDARPoint.szZone.substr(0, 2));
576 bool bIsNorthernHemisphere = stLiDARPoint.dNorthing >= 0; // Simple check based on northing.
577
578 // Don't check if it's already there, just assign to overwrite if it is.
579 m_umAllStates[stLiDARPoint.nID] = PlannerState{stLiDARPoint.nID,
580 stLiDARPoint.dEasting,
581 stLiDARPoint.dNorthing,
582 stLiDARPoint.dAltitude,
583 nZoneNumber,
584 bIsNorthernHemisphere,
585 std::numeric_limits<double>::infinity(),
586 0.0};
587 }
588 // Mark this tile as loaded into the KD-Tree.
589 m_usKDTreeInsertedTiles.insert(stTileKey);
590
591 /*
592 Optimize the KD-Tree after bulk insertion.
593
594 We don't want to optimize too often, so we'll just check the count of the
595 inserted tiles and optimize every so tiles loaded.
596 */
597 if (m_usKDTreeInsertedTiles.size() % 100 == 0)
598 {
599 m_pKDTree->optimize();
600 LOG_INFO(logging::g_qSharedLogger, "Optimized KD-Tree after loading {} tiles.", m_usKDTreeInsertedTiles.size());
601 }
602
603 // Log info message.
604 LOG_DEBUG(logging::g_qSharedLogger, "Loaded tile ({}, {}) with {} points into KD-Tree.", nTileX, nTileY, vTilePoints.size());
605 }
606 }
std::vector< PointRow > GetLiDARData(const PointFilter &stPointFilter)
Retrieves LiDAR data points from the database based on the specified filter.
Definition LiDARHandler.cpp:155
Definition LiDARHandler.h:66
Definition LiDARHandler.h:56
Here is the call graph for this function:
Here is the caller graph for this function:

◆ FindClosestLiDARPoint()

GeoPlanner::PlannerState pathplanners::GeoPlanner::FindClosestLiDARPoint ( const geoops::UTMCoordinate stCoordinate)
private

Find the closest LiDAR point to the given UTM coordinate.

Parameters
stCoordinate- The UTM coordinate to find the closest LiDAR point to.
Returns
GeoPlanner::PlannerState - The PlannerState representing the closest LiDAR point.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-25
618 {
619 // Create instance variables.
620 PlannerState stClosestPoint;
621
622 // First, we need to check to make sure that the tile our given coordinate lies in is loaded.
623 CheckAndLoadTile(PlannerState{-1, stCoordinate.dEasting, stCoordinate.dNorthing, stCoordinate.dAltitude});
624
625 // Now we can perform a nearest neighbor search in the KD-Tree.
626 KDQueryPoint stQueryPoint{stCoordinate.dEasting, stCoordinate.dNorthing};
627 std::pair<pathplanners::KDTree2D::const_iterator, pathplanners::PointKDAccessor::result_type> tpResult = m_pKDTree->find_nearest(stQueryPoint);
628 if (tpResult.first != m_pKDTree->end())
629 {
630 // We found a nearest neighbor, populate the PlannerState.
631 const LiDARHandler::PointRow& stNearestPoint = *(tpResult.first);
632 stClosestPoint.nID = stNearestPoint.nID;
633 stClosestPoint.dEasting = stNearestPoint.dEasting;
634 stClosestPoint.dNorthing = stNearestPoint.dNorthing;
635 stClosestPoint.dAltitude = stNearestPoint.dAltitude;
636 stClosestPoint.dGCost = std::numeric_limits<double>::infinity();
637 stClosestPoint.dHCost = 0.0; // H cost will be calculated later.
638 }
639 else
640 {
641 // No nearest neighbor found, log an error.
642 LOG_ERROR(logging::g_qSharedLogger, "No nearest LiDAR point found for coordinate ({}, {}).", stCoordinate.dEasting, stCoordinate.dNorthing);
643 }
644
645 return stClosestPoint;
646 }
Here is the call graph for this function:
Here is the caller graph for this function:

◆ PlotPathAndTerrain()

void pathplanners::GeoPlanner::PlotPathAndTerrain ( const std::vector< geoops::Waypoint > &  vPath) const
private

Plot the given path and the terrain points that the path goes through.

Parameters
vPath- The vector of waypoints representing the path to plot.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-09-25
657 {
658 // Clear the current layers of the path.
659 m_pPathTracer->ClearLayer("TerrainPoints");
660 m_pPathTracer->ClearLayer("RoverPath");
661
662 // Loop through the path and only display the terrain tiles that the path goes through.
663 std::unordered_set<TileKey, TileKeyHash, TileKeyEqual> usTilesToPlot;
664 for (const geoops::Waypoint& stWaypoint : vPath)
665 {
666 // Determine which tile this waypoint is in.
667 int nTileX = static_cast<int>(std::floor(stWaypoint.GetUTMCoordinate().dEasting / m_dTileSize));
668 int nTileY = static_cast<int>(std::floor(stWaypoint.GetUTMCoordinate().dNorthing / m_dTileSize));
669 usTilesToPlot.insert(TileKey{nTileX, nTileY});
670
671 // Also check the surrounding tiles to give some context.
672 for (int nXOffset = -1; nXOffset <= 1; ++nXOffset)
673 {
674 for (int nYOffset = -1; nYOffset <= 1; ++nYOffset)
675 {
676 usTilesToPlot.insert(TileKey{nTileX + nXOffset, nTileY + nYOffset});
677 }
678 }
679 }
680
681 // Now plot the points from these tiles.
682 for (const TileKey& stTileKey : usTilesToPlot)
683 {
684 std::unordered_map<TileKey, std::vector<LiDARHandler::PointRow>, TileKeyHash, TileKeyEqual>::const_iterator itCachedTile = m_umTileMapCache.find(stTileKey);
685 if (itCachedTile != m_umTileMapCache.end())
686 {
687 // Create instance variables.
688 std::vector<geoops::Waypoint> stTerrainWaypoints;
689 const std::vector<LiDARHandler::PointRow>& vTilePoints = itCachedTile->second;
690
691 // Subsample the points if there are too many to plot.
692 const size_t nMaxPointsToPlot = 10;
693 if (vTilePoints.size() > nMaxPointsToPlot)
694 {
695 double dSubsampleFactor = static_cast<double>(vTilePoints.size()) / static_cast<double>(nMaxPointsToPlot);
696 std::vector<LiDARHandler::PointRow> vSubsampledPoints;
697 for (size_t i = 0; i < vTilePoints.size(); i += static_cast<size_t>(dSubsampleFactor))
698 {
699 vSubsampledPoints.push_back(vTilePoints[i]);
700 }
701 // Use the subsampled points for plotting.
702 stTerrainWaypoints.reserve(vSubsampledPoints.size());
703 for (const LiDARHandler::PointRow& stPoint : vSubsampledPoints)
704 {
705 // Create a waypoint from the PointRow struct.
706 geoops::Waypoint stWaypoint{geoops::UTMCoordinate(stPoint.dEasting,
707 stPoint.dNorthing,
708 std::stoi(stPoint.szZone.substr(0, 2)),
709 (stPoint.dNorthing >= 0),
710 stPoint.dAltitude),
711 geoops::WaypointType::eNavigationWaypoint,
712 0.01,
713 stPoint.nID};
714 // Add the waypoint to the terrain waypoints vector.
715 stTerrainWaypoints.push_back(stWaypoint);
716 }
717 }
718 else
719 {
720 // Use all points if under the max limit.
721 stTerrainWaypoints.reserve(vTilePoints.size());
722
723 for (const LiDARHandler::PointRow& stPoint : vTilePoints)
724 {
725 // Create a waypoint from the PointRow struct.
726 geoops::Waypoint stWaypoint{geoops::UTMCoordinate(stPoint.dEasting,
727 stPoint.dNorthing,
728 std::stoi(stPoint.szZone.substr(0, 2)),
729 (stPoint.dNorthing >= 0),
730 stPoint.dAltitude),
731 geoops::WaypointType::eNavigationWaypoint,
732 0.01,
733 stPoint.nID};
734 // Add the waypoint to the terrain waypoints vector.
735 stTerrainWaypoints.push_back(stWaypoint);
736 }
737 }
738
739 // Add the waypoints to the path tracer.
740 m_pPathTracer->AddDots(stTerrainWaypoints, "TerrainPoints", 0);
741 }
742 }
743
744 // Finally, add the planned path to the path tracer.
745 m_pPathTracer->AddPathPoints(vPath, "RoverPath", 0);
746 }
This struct is used by the WaypointHandler class to store location, size, and type information about ...
Definition GeospatialOperations.hpp:422
Here is the caller graph for this function:

◆ EuclideanDistance()

double pathplanners::GeoPlanner::EuclideanDistance ( double  dEasting1,
double  dNorthing1,
double  Altitude1,
double  dEasting2,
double  dNorthing2,
double  Altitude2 
) const
private

Calculate the distance between two UTM coordinates.

Parameters
dEasting1- The easting of the first point.
dNorthing1- The northing of the first point.
Altitude1- The altitude of the first point.
dEasting2- The easting of the second point.
dNorthing2- The northing of the second point.
Altitude2- The altitude of the second point.
Returns
double - The distance between the two points.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-10-20
763 {
764 // Calculate distance between two points.
765 double dDiffX = dEasting1 - dEasting2;
766 double dDiffY = dNorthing1 - dNorthing2;
767 double dDiffZ = Altitude1 - Altitude2;
768
769 return std::sqrt(dDiffX * dDiffX + dDiffY * dDiffY + dDiffZ * dDiffZ);
770 }
Here is the caller graph for this function:

Member Data Documentation

◆ MinTravScore

const std::function<void(const rovecomm::RoveCommPacket<float>&, const sockaddr_in&)> pathplanners::GeoPlanner::MinTravScore
private
Initial value:
=
[this](const rovecomm::RoveCommPacket<float>& stPacket, const sockaddr_in& stdAddr)
{
(void) stdAddr;
if (stPacket.vData.size() > 0)
{
m_dMinTravScore = static_cast<double>(stPacket.vData[0]);
this->ClearGeoCache();
LOG_NOTICE(logging::g_qSharedLogger,
"Incoming Packet: Setting GeoPlanner minimum travel score to {}. The tile cache has also been cleared.",
this->m_dMinTravScore);
}
}
void ClearGeoCache()
Clear all cached tiles and KD-Tree data.
Definition GeoPlanner.cpp:172

Callback function used to set the minimum travel score for path planning.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-04-04
253 {
254 // Not using this.
255 (void) stdAddr;
256
257 // Set minimum travel score from incoming packet.
258 if (stPacket.vData.size() > 0)
259 {
260 // Set minimum travel score.
261 m_dMinTravScore = static_cast<double>(stPacket.vData[0]);
262 // Clear the tile cache.
263 this->ClearGeoCache();
264
265 // Submit logger message.
266 LOG_NOTICE(logging::g_qSharedLogger,
267 "Incoming Packet: Setting GeoPlanner minimum travel score to {}. The tile cache has also been cleared.",
268 this->m_dMinTravScore);
269 }
270 };

◆ BetaBias

const std::function<void(const rovecomm::RoveCommPacket<float>&, const sockaddr_in&)> pathplanners::GeoPlanner::BetaBias
private
Initial value:
=
[this](const rovecomm::RoveCommPacket<float>& stPacket, const sockaddr_in& stdAddr)
{
(void) stdAddr;
if (stPacket.vData.size() > 0)
{
m_dBeta = static_cast<double>(stPacket.vData[0]);
LOG_NOTICE(logging::g_qSharedLogger, "Incoming Packet: Setting GeoPlanner beta bias to {}", this->m_dBeta);
}
}

Callback function used to set the beta bias for travel scores in path planning.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2024-04-04
281 {
282 // Not using this.
283 (void) stdAddr;
284
285 // Set minimum travel score from incoming packet.
286 if (stPacket.vData.size() > 0)
287 {
288 m_dBeta = static_cast<double>(stPacket.vData[0]);
289 // Submit logger message.
290 LOG_NOTICE(logging::g_qSharedLogger, "Incoming Packet: Setting GeoPlanner beta bias to {}", this->m_dBeta);
291 }
292 };

The documentation for this class was generated from the following files: