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
controllers::PredictiveStanleyController Class Reference

This class implements the Predictive Stanley Controller. This controller is used to follow a path using the Stanley method with predictive control. More...

#include <PredictiveStanleyController.h>

Collaboration diagram for controllers::PredictiveStanleyController:

Classes

struct  DriveVector
 

Public Member Functions

 PredictiveStanleyController (const double dControlGain=0.1, const double dAngularVelocityLimit=90.0, const int nPredictionHorizon=5, const double dPredictionTimeStep=0.01)
 Construct a new Predictive Stanley Controller:: Predictive Stanley Controller object.
 
 ~PredictiveStanleyController ()
 Destroy the Predictive Stanley Controller:: Predictive Stanley Controller object.
 
DriveVector Calculate (const geoops::RoverPose &stCurrentPose, const double dMaxSpeed=constants::NAVIGATING_MOTOR_POWER)
 Calculate an updated steering angle for the rover based on the current pose using the predictive stanley controller.
 
void SetReferencePath (const std::vector< geoops::Waypoint > &vReferencePath)
 Sets the path that the controller will follow.
 
void SetReferencePath (const std::vector< geoops::UTMCoordinate > &vReferencePath)
 Sets the path that the controller will follow.
 
void SetReferencePath (const std::vector< geoops::GPSCoordinate > &vReferencePath)
 Sets the path that the controller will follow.
 
void SetControlGain (const double dControlGain)
 Setter for the control gain of the stanley controller.
 
void SetAngularVelocityLimit (const double dAngularVelocityLimit)
 Setter for the angular velocity limit of the stanley controller.
 
std::vector< geoops::WaypointGetReferencePath () const
 Accessor for the reference path that the controller is following.
 
double GetControlGain () const
 Accessor for the control gain of the stanley controller.
 
double GetAngularVelocityLimit () const
 Accessor for the angular velocity limit of the stanley controller.
 
double GetReferencePathTargetIndex () const
 Accessor for the current target index in the reference path.
 

Private Member Functions

std::pair< geoops::Waypoint, int > FindClosestWaypointInPath (const geoops::UTMCoordinate &stCurrentPosition, const int nStartIndex) const
 Given a position, find the point on the reference path that is closest. Returns both the mapped waypoint projection and the segment start index. Now bounds the search window for O(1) loop speed and is marked const to avoid modifying internal object state during predictions.
 

Private Attributes

UnicycleModel m_UnicycleModel
 
double m_dControlGain
 
double m_dAngularVelocityLimit
 
int m_nPredictionHorizon
 
double m_dPredictionTimeStep
 
int m_nCurrentReferencePathTargetIndex
 
std::vector< geoops::Waypointm_vReferencePath
 

Detailed Description

This class implements the Predictive Stanley Controller. This controller is used to follow a path using the Stanley method with predictive control.

Note
See docs/WhitePapers/2020-A-Path-Tracking-Algorithm-Using-Predictive-Stanley-Lateral-Controller.pdf
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10

Constructor & Destructor Documentation

◆ PredictiveStanleyController()

controllers::PredictiveStanleyController::PredictiveStanleyController ( const double  dControlGain = 0.1,
const double  dAngularVelocityLimit = 90.0,
const int  nPredictionHorizon = 5,
const double  dPredictionTimeStep = 0.01 
)

Construct a new Predictive Stanley Controller:: Predictive Stanley Controller object.

Parameters
dControlGain- The control gain for the controller.
dSteeringAngleLimit- The maximum steering angle the rover can turn.
nPredictionHorizon- The number of predictions to make.
dPredictionTimeStep- The time step to predict the future state. How far into the future to predict.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
45 {
46 // Initialize member variables.
47 m_dControlGain = dControlGain;
48 m_dAngularVelocityLimit = dAngularVelocityLimit;
49 m_nPredictionHorizon = nPredictionHorizon;
50 m_dPredictionTimeStep = dPredictionTimeStep;
51 m_nCurrentReferencePathTargetIndex = 0;
52 m_UnicycleModel = UnicycleModel(0.0, 0.0, 0.0);
53 }
This class implements the Unicycle Model. This model is used to predict the future state of the rover...
Definition UnicycleModel.hpp:35

◆ ~PredictiveStanleyController()

controllers::PredictiveStanleyController::~PredictiveStanleyController ( )

Destroy the Predictive Stanley Controller:: Predictive Stanley Controller object.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
63 {
64 // Nothing to do yet.
65 }

Member Function Documentation

◆ Calculate()

PredictiveStanleyController::DriveVector controllers::PredictiveStanleyController::Calculate ( const geoops::RoverPose stCurrentPose,
const double  dMaxSpeed = constants::NAVIGATING_MOTOR_POWER 
)

Calculate an updated steering angle for the rover based on the current pose using the predictive stanley controller.

Parameters
stCurrentPose- The current pose of the rover.
dMaxSpeed- The maximum speed the rover can travel.
Returns
double - The new output steering angle for the rover.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
79 {
80 // Create instance variables.
81 double dSteeringAngle = 0.0;
82 std::vector<UnicycleModel::Prediction> vPredictions;
83
84 // Check if the reference path is empty.
85 if (m_vReferencePath.empty())
86 {
87 // Submit logger message.
88 LOG_WARNING(logging::g_qSharedLogger, "PredictiveStanleyController::Calculate: Reference path is empty. Cannot calculate drive powers.");
89
90 return DriveVector{0.0, 0.0};
91 }
92
93 // First, update the controller's true index based on the actual current physical position.
94 std::pair<geoops::Waypoint, int> stClosestPointResult = FindClosestWaypointInPath(stCurrentPose.GetUTMCoordinate(), m_nCurrentReferencePathTargetIndex);
95 if (stClosestPointResult.second >= 0)
96 {
97 m_nCurrentReferencePathTargetIndex = stClosestPointResult.second;
98 }
99
100 // Check if we are at the end of the path. Normally stanley would continue driving in the last direction of the calculated path
101 // headings, but we want to make sure we get to the end point, so we'll just drive straight to it once at the end of the path.
102 // We check if we are on the last segment (size - 2).
103 if (m_nCurrentReferencePathTargetIndex >= static_cast<int>(m_vReferencePath.size()) - 2)
104 {
105 // Get the start and end points of the last segment.
106 geoops::UTMCoordinate stLastPoint = m_vReferencePath.back().GetUTMCoordinate();
107 geoops::UTMCoordinate stSecondToLastPoint = m_vReferencePath[m_vReferencePath.size() - 2].GetUTMCoordinate();
108 geoops::UTMCoordinate stRoverPos = stCurrentPose.GetUTMCoordinate();
109
110 // Calculate vector of the last segment (Start -> End).
111 double dSegmentX = stLastPoint.dEasting - stSecondToLastPoint.dEasting;
112 double dSegmentY = stLastPoint.dNorthing - stSecondToLastPoint.dNorthing;
113 double dSegmentLenSq = dSegmentX * dSegmentX + dSegmentY * dSegmentY;
114
115 // Calculate vector from Segment Start -> Rover.
116 double dRoverVectorX = stRoverPos.dEasting - stSecondToLastPoint.dEasting;
117 double dRoverVectorY = stRoverPos.dNorthing - stSecondToLastPoint.dNorthing;
118
119 // Project rover onto the segment vector with a dot product.
120 // t represents the normalized distance along the segment (0.0 = start, 1.0 = end).
121 double dNormalDistance = 0.0;
122 if (dSegmentLenSq > 1e-6)
123 {
124 dNormalDistance = (dRoverVectorX * dSegmentX + dRoverVectorY * dSegmentY) / dSegmentLenSq;
125 }
126
127 // Check if we have passed the end (t >= 1.0) or are very close to it.
128 // We add a small tolerance (e.g., 0.99) or strictly check t >= 1.0.
129 if (dNormalDistance >= 1.0)
130 {
131 // We have reached or passed the end.
132 // Calculate heading to the last point to ensure we turn around if we overshot.
133 double dHeadingToLastWaypoint = geoops::CalculateGeoMeasurement(stRoverPos, stLastPoint).dStartRelativeBearing;
134 return DriveVector{dHeadingToLastWaypoint, 1.0};
135 }
136 }
137
138 // Update the unicycle model with the current state.
139 m_UnicycleModel.UpdateState(stCurrentPose.GetUTMCoordinate().dEasting, stCurrentPose.GetUTMCoordinate().dNorthing, stCurrentPose.GetCompassHeading());
140 // Predict the future state of the model.
141 m_UnicycleModel.Predict(m_dPredictionTimeStep, m_nPredictionHorizon, vPredictions);
142
143 // Keep a running search index for the prediction loop to ensure we smoothly track ahead.
144 int nPredSearchIndex = m_nCurrentReferencePathTargetIndex;
145
146 // Loop through all the predicted future states to compute the steering angle.
147 for (size_t nIter = 0; nIter < vPredictions.size(); ++nIter)
148 {
149 // Create instance variables.
150 double dPredictedXPosition = vPredictions[nIter].dXPosition;
151 double dPredictedYPosition = vPredictions[nIter].dYPosition;
152 double dPredictedTheta = vPredictions[nIter].dTheta;
153
154 // Create a UTM coordinate for the predicted position.
155 geoops::UTMCoordinate stPredictedPosition = stCurrentPose.GetUTMCoordinate();
156 stPredictedPosition.dEasting = dPredictedXPosition;
157 stPredictedPosition.dNorthing = dPredictedYPosition;
158
159 // Find the closest point to the reference path for this prediction step.
160 std::pair<geoops::Waypoint, int> stdClosestPointResult = FindClosestWaypointInPath(stPredictedPosition, nPredSearchIndex);
161 geoops::Waypoint stClosestWaypoint = stdClosestPointResult.first;
162 int nBestIndex = stdClosestPointResult.second;
163 if (nBestIndex >= 0)
164 {
165 nPredSearchIndex = nBestIndex;
166 }
167
168 // Compute the path forward vector from the segment start -> next waypoint.
169 int nIdx = nPredSearchIndex;
170 int nNextIdx = std::min(nIdx + 1, static_cast<int>(m_vReferencePath.size() - 1));
171 const geoops::UTMCoordinate& stSegmentStart = m_vReferencePath[nIdx].GetUTMCoordinate();
172 const geoops::UTMCoordinate& stSegmentEnd = m_vReferencePath[nNextIdx].GetUTMCoordinate();
173
174 // Forward vector of the path segment.
175 double dForwardVectorX = stSegmentEnd.dEasting - stSegmentStart.dEasting;
176 double dForwardVectorY = stSegmentEnd.dNorthing - stSegmentStart.dNorthing;
177 double dForwardNorm = std::hypot(dForwardVectorX, dForwardVectorY);
178 // Degenerate segment. Skip this prediction step.
179 if (dForwardNorm < 1e-9)
180 {
181 continue;
182 }
183
184 // Unit forward vector.
185 double dFwdUnitX = dForwardVectorX / dForwardNorm;
186 double dFwdUnitY = dForwardVectorY / dForwardNorm;
187 // Math angle of segment (atan2 gives angle from +X (East), CCW positive) in degrees.
188 double dSegmentMathDeg = std::atan2(dForwardVectorY, dForwardVectorX) * (180.0 / M_PI);
189 // Convert math angle to COMPASS heading (0 = North, clockwise positive):
190 // compass = (90 - math) mod 360.
191 double dPathHeadingDeg = std::fmod(90.0 - dSegmentMathDeg + 360.0, 360.0);
192 // Predicted theta is produced by UnicycleModel in DEGREES (compass). Treat as degrees.
193 double dPredThetaDeg = dPredictedTheta;
194 // Small helper: signed smallest-angle difference in degrees in (-180, 180].
195 std::function<double(double, double)> AngleDiffDeg = [](double targetDeg, double sourceDeg) -> double
196 {
197 double diff = std::fmod(targetDeg - sourceDeg + 540.0, 360.0) - 180.0;
198 return diff;
199 };
200 // Heading error (degrees): positive means we should rotate clockwise to match target (compass conv).
201 double dHeadingError = AngleDiffDeg(dPathHeadingDeg, dPredThetaDeg);
202
203 // Vehicle vector relative to the closest point. (projection)
204 double dVehicleVectorX = dPredictedXPosition - stClosestWaypoint.GetUTMCoordinate().dEasting;
205 double dVehicleVectorY = dPredictedYPosition - stClosestWaypoint.GetUTMCoordinate().dNorthing;
206 // Longitudinal projection and lateral vector.
207 double dLongitudinal = dVehicleVectorX * dFwdUnitX + dVehicleVectorY * dFwdUnitY;
208 double dLateralX = dVehicleVectorX - dLongitudinal * dFwdUnitX;
209 double dLateralY = dVehicleVectorY - dLongitudinal * dFwdUnitY;
210 double dLateralDistance = std::hypot(dLateralX, dLateralY);
211 // Sign using cross product. (left positive)
212 double dCross = dFwdUnitX * dVehicleVectorY - dFwdUnitY * dVehicleVectorX;
213 double dCrossTrackError = (dCross > 0.0 ? 1.0 : -1.0) * dLateralDistance; // meters
214
215 // Apply an exponential weight factor that decreases as we predict further into the future.
216 double dTimeWeight = std::exp(-1.5 * static_cast<double>(nIter));
217
218 // Use model velocity if available, otherwise fall back to provided dMaxSpeed
219 double dModelSpeed = m_UnicycleModel.GetVelocity();
220 double dSpeed = (dModelSpeed > 0.0) ? dModelSpeed : dMaxSpeed;
221 dSpeed = std::max(dSpeed, 1e-4);
222
223 // Clamp the speed so the denominator never drops below the floor.
224 // If actual speed is 0.1, the math uses 0.3. If actual is 0.8, the math uses 0.8.
225 double dEffectiveSpeed = std::max(dSpeed, constants::STANLEY_MIN_STABLE_SPEED);
226 // Calculate Stanley term using the effective speed.
227 double dStanleyTermRad = std::atan2(m_dControlGain * dCrossTrackError, dEffectiveSpeed);
228 double dStanleyTermDeg = dStanleyTermRad * (180.0 / M_PI);
229 // Combine the heading error and the stanley term.
230 dSteeringAngle += (dStanleyTermDeg + dHeadingError) * dTimeWeight;
231
232 // Limit the steering angle to the given limit.
233 dSteeringAngle = std::clamp(dSteeringAngle, -m_dAngularVelocityLimit, m_dAngularVelocityLimit);
234 }
235
236 // The new steering heading must be from 0-360 degrees.
237 double dAbsoluteHeadingGoal = numops::InputAngleModulus(stCurrentPose.GetCompassHeading() + dSteeringAngle, 0.0, 360.0);
238
239 return DriveVector{dAbsoluteHeadingGoal, dMaxSpeed};
240 }
void UpdateState(const double dXPosition, const double dYPosition, const double dTheta)
Update the state of the model, given a new position and heading. This method will automatically calcu...
Definition UnicycleModel.hpp:151
double GetVelocity() const
Accessor for the Velocity private member.
Definition UnicycleModel.hpp:300
void Predict(const double dTimeStep, const int nNumPredictions, std::vector< Prediction > &vPredictions)
Accessor for the State private member.
Definition UnicycleModel.hpp:187
std::pair< geoops::Waypoint, int > FindClosestWaypointInPath(const geoops::UTMCoordinate &stCurrentPosition, const int nStartIndex) const
Given a position, find the point on the reference path that is closest. Returns both the mapped waypo...
Definition PredictiveStanleyController.cpp:396
GeoMeasurement CalculateGeoMeasurement(const GPSCoordinate &stCoord1, const GPSCoordinate &stCoord2)
The shortest path between two points on an ellipsoid at (lat1, lon1) and (lat2, lon2) is called the g...
Definition GeospatialOperations.hpp:553
constexpr T InputAngleModulus(T tValue, T tMinValue, T tMaxValue)
Calculates the modulus of an input angle.
Definition NumberOperations.hpp:162
double GetCompassHeading() const
Accessor for the Compass Heading private member.
Definition GeospatialOperations.hpp:787
const geoops::UTMCoordinate & GetUTMCoordinate() const
Accessor for the geoops::UTMCoordinate member variable.
Definition GeospatialOperations.hpp:767
This struct stores/contains information about a UTM coordinate.
Definition GeospatialOperations.hpp:211
This struct is used by the WaypointHandler class to store location, size, and type information about ...
Definition GeospatialOperations.hpp:423
const geoops::UTMCoordinate & GetUTMCoordinate() const
Accessor for the geoops::UTMCoordinate member variable.
Definition GeospatialOperations.hpp:508
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetReferencePath() [1/3]

void controllers::PredictiveStanleyController::SetReferencePath ( const std::vector< geoops::Waypoint > &  vReferencePath)

Sets the path that the controller will follow.

Parameters
vReferencePath- A reference to a vector of waypoints that make up the path.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
251 {
252 // Reset the current target index.
253 m_nCurrentReferencePathTargetIndex = 0;
254 // Reset the bicycle model.
255 m_UnicycleModel.ResetState();
256
257 // Smooth the path by fitting it to a B-spline.
258 std::vector<geoops::Waypoint> vSmoothedPath = pathplanners::postprocessing::FitPathWithBSpline(vReferencePath);
259
260 // Set the reference path.
261 m_vReferencePath = vSmoothedPath;
262 }
void ResetState(const double dXPosition, const double dYPosition, const double dTheta)
Resets the state of the model to a new position and heading.
Definition UnicycleModel.hpp:109
std::vector< geoops::Waypoint > FitPathWithBSpline(const std::vector< geoops::Waypoint > &vRawPath)
Fits a B-spline to the given path using cubic interpolation.
Definition PathPostProcessing.hpp:51
Here is the call graph for this function:
Here is the caller graph for this function:

◆ SetReferencePath() [2/3]

void controllers::PredictiveStanleyController::SetReferencePath ( const std::vector< geoops::UTMCoordinate > &  vReferencePath)

Sets the path that the controller will follow.

Parameters
vReferencePath- A reference to a vector of UTM coordinates that make up the path.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
273 {
274 // Convert UTM coordinates to waypoints.
275 std::vector<geoops::Waypoint> vConvertedPath;
276 for (const geoops::UTMCoordinate& stUTMCoord : vReferencePath)
277 {
278 vConvertedPath.emplace_back(geoops::Waypoint(stUTMCoord, geoops::WaypointType::eNavigationWaypoint, 0.0, -1));
279 }
280
281 // Set the reference path.
282 this->SetReferencePath(vConvertedPath);
283 }
void SetReferencePath(const std::vector< geoops::Waypoint > &vReferencePath)
Sets the path that the controller will follow.
Definition PredictiveStanleyController.cpp:250
Here is the call graph for this function:

◆ SetReferencePath() [3/3]

void controllers::PredictiveStanleyController::SetReferencePath ( const std::vector< geoops::GPSCoordinate > &  vReferencePath)

Sets the path that the controller will follow.

Parameters
vReferencePath- A reference to a vector of GPS coordinates that make up the path.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
294 {
295 // Convert GPS coordinates to waypoints.
296 std::vector<geoops::Waypoint> vConvertedPath;
297 for (const geoops::GPSCoordinate& stGPSCoord : vReferencePath)
298 {
299 vConvertedPath.emplace_back(geoops::Waypoint(stGPSCoord, geoops::WaypointType::eNavigationWaypoint, 0.0, -1));
300 }
301
302 // Set the reference path.
303 this->SetReferencePath(vConvertedPath);
304 }
This struct stores/contains information about a GPS data.
Definition GeospatialOperations.hpp:100
Here is the call graph for this function:

◆ SetControlGain()

void controllers::PredictiveStanleyController::SetControlGain ( const double  dControlGain)

Setter for the control gain of the stanley controller.

Parameters
dControlGain- The control gain for the controller.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
315 {
316 m_dControlGain = dControlGain;
317 }
Here is the caller graph for this function:

◆ SetAngularVelocityLimit()

void controllers::PredictiveStanleyController::SetAngularVelocityLimit ( const double  dAngularVelocityLimit)

Setter for the angular velocity limit of the stanley controller.

Parameters
dAngularVelocityLimit- The angular velocity limit for the controller.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-11
328 {
329 m_dAngularVelocityLimit = dAngularVelocityLimit;
330 }
Here is the caller graph for this function:

◆ GetReferencePath()

std::vector< geoops::Waypoint > controllers::PredictiveStanleyController::GetReferencePath ( ) const

Accessor for the reference path that the controller is following.

Returns
std::vector<geoops::Waypoint> - A copy of the reference path.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
367 {
368 return m_vReferencePath;
369 }
Here is the caller graph for this function:

◆ GetControlGain()

double controllers::PredictiveStanleyController::GetControlGain ( ) const

Accessor for the control gain of the stanley controller.

Returns
double - The control gain for the controller.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
341 {
342 return m_dControlGain;
343 }
Here is the caller graph for this function:

◆ GetAngularVelocityLimit()

double controllers::PredictiveStanleyController::GetAngularVelocityLimit ( ) const

Accessor for the angular velocity limit of the stanley controller.

Returns
double - The angular velocity limit for the controller.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-11
354 {
355 return m_dAngularVelocityLimit;
356 }
Here is the caller graph for this function:

◆ GetReferencePathTargetIndex()

double controllers::PredictiveStanleyController::GetReferencePathTargetIndex ( ) const

Accessor for the current target index in the reference path.

Returns
double - The current target index in the reference path.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
380 {
381 return m_nCurrentReferencePathTargetIndex;
382 }
Here is the caller graph for this function:

◆ FindClosestWaypointInPath()

std::pair< geoops::Waypoint, int > controllers::PredictiveStanleyController::FindClosestWaypointInPath ( const geoops::UTMCoordinate stCurrentPosition,
const int  nStartIndex 
) const
private

Given a position, find the point on the reference path that is closest. Returns both the mapped waypoint projection and the segment start index. Now bounds the search window for O(1) loop speed and is marked const to avoid modifying internal object state during predictions.

Parameters
stCurrentPosition- The position to project onto the path.
nStartIndex- The index to start searching from.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-01-10
397 {
398 // Create instance variables.
399 geoops::Waypoint stClosestWaypoint;
400 double dClosestDistanceSq = std::numeric_limits<double>::max();
401 const size_t nWaypoints = m_vReferencePath.size();
402
403 // Check for empty path.
404 if (nWaypoints == 0)
405 {
406 return {stClosestWaypoint, -1};
407 }
408
409 // If only a single waypoint, return it directly.
410 if (nWaypoints == 1)
411 {
412 return {m_vReferencePath.front(), 0};
413 }
414
415 // Setup the search window bounds to avoid O(N) exhaustive loops
416 const size_t nMaxLookahead = 50;
417 size_t nStart = static_cast<size_t>(std::max(0, nStartIndex));
418 size_t nEnd = std::min(nWaypoints - 1, nStart + nMaxLookahead);
419
420 // We'll search across bounded path segments (i -> i+1) and compute the closest point on each segment.
421 int nBestSegmentIndex = -1;
422 geoops::UTMCoordinate stBestProjection;
423
424 for (size_t siIter = nStart; siIter < nEnd; ++siIter)
425 {
426 const geoops::UTMCoordinate& stA = m_vReferencePath[siIter].GetUTMCoordinate();
427 const geoops::UTMCoordinate& stB = m_vReferencePath[siIter + 1].GetUTMCoordinate();
428
429 // Segment vector. (B - A)
430 double dSX = stB.dEasting - stA.dEasting;
431 double dSY = stB.dNorthing - stA.dNorthing;
432
433 // Vector from A -> P.
434 double dPX = stCurrentPosition.dEasting - stA.dEasting;
435 double dPY = stCurrentPosition.dNorthing - stA.dNorthing;
436
437 double sSegmentLengthSquared = dSX * dSX + dSY * dSY;
438 double dProjT = 0.0;
439 if (sSegmentLengthSquared > 0.0)
440 {
441 // projection parameter t = dot(P-A, B-A) / |B-A|^2.
442 dProjT = (dPX * dSX + dPY * dSY) / sSegmentLengthSquared;
443 dProjT = std::clamp(dProjT, 0.0, 1.0);
444 }
445 else
446 {
447 // Degenerate segment (A == B). Use A as projection.
448 dProjT = 0.0;
449 }
450
451 // Projection point coordinates.
452 double dProjX = stA.dEasting + dProjT * dSX;
453 double dProjY = stA.dNorthing + dProjT * dSY;
454
455 // Squared distance from P to projection.
456 double dx = stCurrentPosition.dEasting - dProjX;
457 double dy = stCurrentPosition.dNorthing - dProjY;
458 double distSq = dx * dx + dy * dy;
459
460 if (distSq < dClosestDistanceSq)
461 {
462 dClosestDistanceSq = distSq;
463 nBestSegmentIndex = static_cast<int>(siIter);
464 stBestProjection.dEasting = dProjX;
465 stBestProjection.dNorthing = dProjY;
466 stBestProjection.nZone = stA.nZone;
467 stBestProjection.bWithinNorthernHemisphere = stA.bWithinNorthernHemisphere;
468 }
469 }
470
471 // If we didn't find a segment (shouldn't happen), fall back to the closest waypoint vertex search.
472 if (nBestSegmentIndex < 0)
473 {
474 double dClosestDist = std::numeric_limits<double>::max();
475 int nFallbackIndex = -1;
476
477 // Note we search up to <= nEnd here to include the vertex at the very end of the search window
478 for (size_t siIter = nStart; siIter <= nEnd; ++siIter)
479 {
480 double dDistance = geoops::CalculateGeoMeasurement(stCurrentPosition, m_vReferencePath[siIter].GetUTMCoordinate()).dDistanceMeters;
481 if (dDistance < dClosestDist)
482 {
483 dClosestDist = dDistance;
484 stClosestWaypoint = m_vReferencePath[siIter];
485 nFallbackIndex = static_cast<int>(siIter);
486 }
487 }
488 return {stClosestWaypoint, nFallbackIndex};
489 }
490
491 // Build a waypoint for the projected point.
492 // Use the properties of the segment start waypoint as a base, then set the UTM to the projection.
493 geoops::Waypoint stSegmentStart = m_vReferencePath[nBestSegmentIndex];
494 geoops::UTMCoordinate stBestCoord = geoops::UTMCoordinate(stBestProjection.dEasting,
495 stBestProjection.dNorthing,
496 stBestProjection.nZone,
497 stBestProjection.bWithinNorthernHemisphere,
498 stSegmentStart.GetUTMCoordinate().dAltitude,
499 stSegmentStart.GetUTMCoordinate().d2DAccuracy,
500 stSegmentStart.GetUTMCoordinate().d3DAccuracy,
501 stSegmentStart.GetUTMCoordinate().dMeridianConvergence,
502 stSegmentStart.GetUTMCoordinate().dScale,
503 stSegmentStart.GetUTMCoordinate().eCoordinateAccuracyFixType,
504 stSegmentStart.GetUTMCoordinate().bIsDifferential);
505 stClosestWaypoint = geoops::Waypoint(stBestCoord);
506
507 return {stClosestWaypoint, nBestSegmentIndex};
508 }
Here is the call graph for this function:
Here is the caller graph for this function:

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