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
LiDARHandler Class Reference
Collaboration diagram for LiDARHandler:

Classes

struct  PointFilter
 
struct  PointRow
 

Public Member Functions

 LiDARHandler ()
 Construct a new LiDARHandler::LiDARHandler object.
 
 LiDARHandler (const LiDARHandler &pOther)=delete
 
LiDARHandleroperator= (const LiDARHandler &pOther)=delete
 
 ~LiDARHandler ()
 Destroy the LiDARHandler::LiDARHandler object.
 
bool OpenDB (const std::string &szDBPath)
 Initializes the LiDARHandler by opening the SQLite database and preparing the query.
 
bool CloseDB ()
 Closes the currently open LiDAR database.
 
std::vector< PointRowGetLiDARData (const PointFilter &stPointFilter)
 Retrieves LiDAR data points from the database based on the specified filter.
 
bool InsertLiDARData (const std::vector< geoops::Waypoint > &vPoints)
 
bool IsDBOpen ()
 Checks if the database is currently open.
 

Private Member Functions

template<typename T >
void AddRangeFilter (std::vector< std::string > &vClauses, std::vector< std::function< void(sqlite3_stmt *, int &)> > &vBinders, const char *pColumn, const std::optional< PointFilter::Range< T > > &stdOptRange)
 Adds a range filter to the SQL query clauses and binders.
 

Private Attributes

sqlite3 * m_pSQLDatabase
 
sqlite3_stmt * m_pSQLStatement
 
bool m_bIsDBOpen
 
std::shared_mutex m_muQueryMutex
 

Constructor & Destructor Documentation

◆ LiDARHandler()

LiDARHandler::LiDARHandler ( )

Construct a new LiDARHandler::LiDARHandler object.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-13
22{
23 // Initialize member variables.
24 m_pSQLDatabase = nullptr;
25 m_pSQLStatement = nullptr;
26 m_bIsDBOpen = false;
27}

◆ ~LiDARHandler()

LiDARHandler::~LiDARHandler ( )

Destroy the LiDARHandler::LiDARHandler object.

Author
ClayJay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om), Eli Byrd (edbgk.nosp@m.k@ms.nosp@m.t.edu)
Date
2025-05-20
36{
37 this->CloseDB(); // Ensure the database is closed on destruction.
38}
bool CloseDB()
Closes the currently open LiDAR database.
Definition LiDARHandler.cpp:104
Here is the call graph for this function:

Member Function Documentation

◆ OpenDB()

bool LiDARHandler::OpenDB ( const std::string &  szDBPath)

Initializes the LiDARHandler by opening the SQLite database and preparing the query.

This method opens the specified SQLite database file and prepares the internal SQL statement used to query nearby point records. It must be called before any queries are made using GetNearbyPoints().

Parameters
szDBPathRelative or absolute path to the SQLite database file. If a relative path is provided, it must be relative to the directory from which the final executable is launched (i.e., the current working directory).
Returns
true - If the database was successfully opened and the SQL statement prepared.
false - If there was an error opening the database or preparing the SQL statement.
Note
If the database file cannot be found or accessed, an error message will be printed via Quill Logger and this function will return false.
Author
ClayJay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-13
61{
62 // Acquire a write lock on the mutex to ensure thread safety.
63 std::unique_lock<std::shared_mutex> lkWriteLock(m_muQueryMutex);
64
65 // Check if the database is already open.
66 if (m_bIsDBOpen)
67 {
68 // Submit logger message.
69 LOG_WARNING(logging::g_qSharedLogger, "Database is already open. Closing existing connection before opening a new one.");
70 // Release lock before calling CloseDB to avoid deadlock.
71 lkWriteLock.unlock();
72 this->CloseDB();
73 lkWriteLock.lock();
74 }
75
76 // Attempt to open the SQLite database.
77 int nReturnCode = sqlite3_open(szDBPath.c_str(), &m_pSQLDatabase);
78 if (nReturnCode != SQLITE_OK)
79 {
80 // Submit logger message.
81 LOG_ERROR(logging::g_qSharedLogger, "Failed to open database at '{}': {}", szDBPath, sqlite3_errmsg(m_pSQLDatabase));
82 // Return false on failure.
83 return false;
84 }
85
86 // Set the database open flag to true.
87 m_bIsDBOpen = true;
88
89 // Log success.
90 LOG_INFO(logging::g_qSharedLogger, "Successfully opened database at '{}'.", szDBPath);
91
92 return true;
93}
Here is the call graph for this function:
Here is the caller graph for this function:

◆ CloseDB()

bool LiDARHandler::CloseDB ( )

Closes the currently open LiDAR database.

Returns
true - If the database was successfully closed.
false - If there was an error closing the database.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-13
105{
106 // Acquire a write lock on the mutex to ensure thread safety.
107 std::unique_lock<std::shared_mutex> lkWriteLock(m_muQueryMutex);
108
109 // Handle the closing of the database and sqlite statement.
110 if (m_bIsDBOpen)
111 {
112 // Finalize the prepared statement if it exists. This is necessary because
113 // failing to do so can result in memory leaks. This just frees resources.
114 if (m_pSQLStatement)
115 {
116 int nReturnCode = sqlite3_finalize(m_pSQLStatement);
117 if (nReturnCode != SQLITE_OK)
118 {
119 // Submit logger message.
120 LOG_ERROR(logging::g_qSharedLogger, "Failed to finalize SQL statement: {}", sqlite3_errmsg(m_pSQLDatabase));
121 // Return false on failure.
122 return false;
123 }
124 m_pSQLStatement = nullptr; // Reset the statement pointer.
125 }
126
127 // Close the database connection.
128 int nReturnCode = sqlite3_close(m_pSQLDatabase);
129 if (nReturnCode != SQLITE_OK)
130 {
131 // Submit logger message.
132 LOG_ERROR(logging::g_qSharedLogger, "Failed to close database: {}", sqlite3_errmsg(m_pSQLDatabase));
133 // Return false on failure.
134 return false;
135 }
136
137 // Reset the database pointer and update the open flag.
138 m_pSQLDatabase = nullptr; // Reset the database pointer.
139 m_bIsDBOpen = false; // Update the database open flag.
140 }
141
142 return true;
143}
Here is the caller graph for this function:

◆ GetLiDARData()

std::vector< LiDARHandler::PointRow > LiDARHandler::GetLiDARData ( const PointFilter stPointFilter)

Retrieves LiDAR data points from the database based on the specified filter.

Parameters
stPointFilter- The filter criteria to apply when querying LiDAR data.
Returns
std::vector<LiDARHandler::PointRow> - A vector of PointRow structures containing the queried LiDAR data points.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-13
156{
157 // Acquire a read lock on the mutex to ensure thread safety.
158 std::shared_lock<std::shared_mutex> lkReadLock(m_muQueryMutex);
159
160 // Record the start time for performance measurement.
161 std::chrono::time_point<std::chrono::high_resolution_clock> tmStartTime = std::chrono::high_resolution_clock::now();
162
163 if (!m_bIsDBOpen)
164 {
165 LOG_ERROR(logging::g_qSharedLogger, "Database is not open.");
166 return {};
167 }
168
169 // Build dynamic WHERE clauses and binders.
170 std::vector<std::string> vClauses;
171 std::vector<std::function<void(sqlite3_stmt*, int&)>> vBinders;
172 int nParamIndex = 1;
173
174 // Spatial bounds are always present (using R-Tree index).
175 vClauses.emplace_back("idx.min_x BETWEEN ? AND ?");
176 vBinders.emplace_back(
177 [&](sqlite3_stmt* sqlSTMT, int& nIndex)
178 {
179 sqlite3_bind_double(sqlSTMT, nIndex++, stPointFilter.dEasting - stPointFilter.dRadius);
180 sqlite3_bind_double(sqlSTMT, nIndex++, stPointFilter.dEasting + stPointFilter.dRadius);
181 });
182 vClauses.emplace_back("idx.min_y BETWEEN ? AND ?");
183 vBinders.emplace_back(
184 [&](sqlite3_stmt* sqlSTMT, int& nIndex)
185 {
186 sqlite3_bind_double(sqlSTMT, nIndex++, stPointFilter.dNorthing - stPointFilter.dRadius);
187 sqlite3_bind_double(sqlSTMT, nIndex++, stPointFilter.dNorthing + stPointFilter.dRadius);
188 });
189
190 // Optional classification filter.
191 // OPTIMIZATION: We filter against the joined 'Classifications' table (c.label).
192 if (stPointFilter.szClassification && !stPointFilter.szClassification->empty())
193 {
194 vClauses.emplace_back("c.label = ?");
195 vBinders.emplace_back([&](sqlite3_stmt* sqlSTMT, int& nIndex)
196 { sqlite3_bind_text(sqlSTMT, nIndex++, stPointFilter.szClassification->c_str(), -1, SQLITE_STATIC); });
197 }
198
199 // Add optional filters for metrics.
200 this->AddRangeFilter(vClauses, vBinders, "p.normal_x", stPointFilter.dNormalX);
201 this->AddRangeFilter(vClauses, vBinders, "p.normal_y", stPointFilter.dNormalY);
202 this->AddRangeFilter(vClauses, vBinders, "p.normal_z", stPointFilter.dNormalZ);
203 this->AddRangeFilter(vClauses, vBinders, "p.slope", stPointFilter.dSlope);
204 this->AddRangeFilter(vClauses, vBinders, "p.rough", stPointFilter.dRoughness);
205 this->AddRangeFilter(vClauses, vBinders, "p.curvature", stPointFilter.dCurvature);
206 this->AddRangeFilter(vClauses, vBinders, "p.trav_score", stPointFilter.dTraversalScore);
207
208 // Construct final SQL query string.
209 std::ostringstream stdOSS;
210
211 // OPTIMIZATION:
212 // 1. Select 'z.label' and 'c.label' to get human-readable text.
213 // 2. LEFT JOIN to handle cases where IDs might not map (prevents data loss).
214 stdOSS << "SELECT p.id, p.easting, p.northing, p.altitude, z.label, c.label,"
215 << " p.normal_x, p.normal_y, p.normal_z, p.slope, p.rough, p.curvature, p.trav_score"
216 << " FROM ProcessedLiDARPoints_idx AS idx"
217 << " JOIN ProcessedLiDARPoints AS p ON p.id = idx.id"
218 << " LEFT JOIN Zones AS z ON p.zone_id = z.id"
219 << " LEFT JOIN Classifications AS c ON p.class_code = c.code"
220 << " WHERE ";
221
222 // Append all clauses to the SQL query.
223 for (size_t siIter = 0; siIter < vClauses.size(); ++siIter)
224 {
225 if (siIter > 0)
226 stdOSS << " AND ";
227 stdOSS << vClauses[siIter];
228 }
229
230 // Final SQL query string.
231 const std::string szSQLQuery = stdOSS.str();
232
233 // Prepare SQL statement.
234 sqlite3_stmt* sqlSTMT = nullptr;
235 int nRC = sqlite3_prepare_v2(m_pSQLDatabase, szSQLQuery.c_str(), -1, &sqlSTMT, nullptr);
236 if (nRC != SQLITE_OK)
237 {
238 LOG_ERROR(logging::g_qSharedLogger, "Failed to prepare SQL: {}", sqlite3_errmsg(m_pSQLDatabase));
239 return {};
240 }
241
242 // Bind parameters.
243 for (std::function<void(sqlite3_stmt*, int&)>& binder : vBinders)
244 {
245 binder(sqlSTMT, nParamIndex);
246 }
247
248 // Execute and collect results.
249 std::vector<PointRow> vResults;
250 while ((nRC = sqlite3_step(sqlSTMT)) == SQLITE_ROW)
251 {
252 PointRow stRow;
253 stRow.nID = sqlite3_column_int(sqlSTMT, 0);
254 stRow.dEasting = sqlite3_column_double(sqlSTMT, 1);
255 stRow.dNorthing = sqlite3_column_double(sqlSTMT, 2);
256 stRow.dAltitude = sqlite3_column_double(sqlSTMT, 3);
257
258 // --- SEGFAULT FIX START ---
259 // Retrieve Zone (Column 4). Check for NULL (if LEFT JOIN failed).
260 const char* pszZone = reinterpret_cast<const char*>(sqlite3_column_text(sqlSTMT, 4));
261 stRow.szZone = pszZone ? pszZone : "Unknown";
262
263 // Retrieve Classification (Column 5). Check for NULL.
264 const char* pszClass = reinterpret_cast<const char*>(sqlite3_column_text(sqlSTMT, 5));
265 stRow.szClassification = pszClass ? pszClass : "Unclassified";
266 // --- SEGFAULT FIX END ---
267
268 stRow.dNormalX = sqlite3_column_double(sqlSTMT, 6);
269 stRow.dNormalY = sqlite3_column_double(sqlSTMT, 7);
270 stRow.dNormalZ = sqlite3_column_double(sqlSTMT, 8);
271 stRow.dSlope = sqlite3_column_double(sqlSTMT, 9);
272 stRow.dRoughness = sqlite3_column_double(sqlSTMT, 10);
273 stRow.dCurvature = sqlite3_column_double(sqlSTMT, 11);
274 stRow.dTraversalScore = sqlite3_column_double(sqlSTMT, 12);
275
276 vResults.push_back(stRow);
277 }
278
279 // Finalize SQL statement.
280 if ((nRC = sqlite3_finalize(sqlSTMT)) != SQLITE_OK)
281 {
282 LOG_ERROR(logging::g_qSharedLogger, "Failed to finalize statement: {}", sqlite3_errmsg(m_pSQLDatabase));
283 return {};
284 }
285
286 // Record the end time for performance measurement.
287 std::chrono::time_point<std::chrono::high_resolution_clock> tmEndTime = std::chrono::high_resolution_clock::now();
288 double dQueryTime = std::chrono::duration<double>(tmEndTime - tmStartTime).count();
289
290 if (dQueryTime > 0.2)
291 {
292 LOG_WARNING(logging::g_qSharedLogger, "Query took {:.2f} seconds to execute.", dQueryTime);
293 }
294 else
295 {
296 LOG_DEBUG(logging::g_qSharedLogger, "Query took {} seconds to execute.", dQueryTime);
297 }
298
299 if (vResults.empty())
300 {
301 LOG_WARNING(logging::g_qSharedLogger, "Query returned no results.");
302 }
303
304 return vResults;
305}
void AddRangeFilter(std::vector< std::string > &vClauses, std::vector< std::function< void(sqlite3_stmt *, int &)> > &vBinders, const char *pColumn, const std::optional< PointFilter::Range< T > > &stdOptRange)
Adds a range filter to the SQL query clauses and binders.
Definition LiDARHandler.cpp:337
Here is the call graph for this function:
Here is the caller graph for this function:

◆ IsDBOpen()

bool LiDARHandler::IsDBOpen ( )

Checks if the database is currently open.

Returns
true - If the database is open.
false - If the database is not open.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-14
317{
318 // Acquire a read lock on the mutex to ensure thread safety.
319 std::shared_lock<std::shared_mutex> lkReadLock(m_muQueryMutex);
320 // Return the database open status.
321 return m_bIsDBOpen;
322}
Here is the caller graph for this function:

◆ AddRangeFilter()

template<typename T >
void LiDARHandler::AddRangeFilter ( std::vector< std::string > &  vClauses,
std::vector< std::function< void(sqlite3_stmt *, int &)> > &  vBinders,
const char *  pColumn,
const std::optional< PointFilter::Range< T > > &  stdOptRange 
)
private

Adds a range filter to the SQL query clauses and binders.

Template Parameters
T- The data type of the range values.
Parameters
vClauses- The vector of SQL clauses to which the range filter will be added.
vBinders- The vector of binders for the SQL statement.
pColumn- The name of the column to apply the range filter on.
stdOptRange- The optional range to filter by. If it is not set, no filter will be added.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2025-07-14
341{
342 // If the range is set, add the filter clause and binder.
343 if (stdOptRange)
344 {
345 // Add the range filter clause based on the type of T.
346 if constexpr (std::is_floating_point<T>::value)
347 {
348 // Only use >= by default for floats
349 vClauses.emplace_back(std::string(pColumn) + " >= ?");
350 vBinders.emplace_back([&](sqlite3_stmt* sqlSTMT, int& nIndex) { sqlite3_bind_double(sqlSTMT, nIndex++, stdOptRange->tMin); });
351 }
352 else
353 {
354 // Use BETWEEN for ints or guaranteed bounded ranges
355 vClauses.emplace_back(std::string(pColumn) + " BETWEEN ? AND ?");
356 vBinders.emplace_back(
357 [&](sqlite3_stmt* sqlSTMT, int& nIndex)
358 {
359 sqlite3_bind_double(sqlSTMT, nIndex++, stdOptRange->tMin);
360 sqlite3_bind_double(sqlSTMT, nIndex++, stdOptRange->tMax);
361 });
362 }
363 }
364}
Here is the caller graph for this function:

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