7#include "TinyRobotics/utils/LoggerClass.h"
9namespace tinyrobotics {
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
49 NMEAParser(GPSFormat fmt) : format(fmt) {}
52 return parse(std::string(sentence), gps);
55 bool parse(
const std::string& sentence,
GPSCoordinate& gps)
const {
58 return parseGGA(sentence, gps);
60 return parseRMC(sentence, gps);
76 static bool safe_stof(
const std::string& s,
float& out) {
77 if (s.empty())
return false;
78 char* endptr =
nullptr;
79 out = strtof(s.c_str(), &endptr);
80 return endptr !=
nullptr && *endptr ==
'\0';
83 static bool safe_stoi(
const std::string& s,
int& out) {
84 if (s.empty())
return false;
85 char* endptr =
nullptr;
86 out = strtol(s.c_str(), &endptr, 10);
87 return endptr !=
nullptr && *endptr ==
'\0';
90 bool parseGGA(
const std::string& sentence,
GPSCoordinate& gps)
const {
92 std::vector<std::string> fields;
94 size_t end = sentence.find(
',');
96 while (end != std::string::npos) {
97 fields.push_back(sentence.substr(start, end - start));
99 end = sentence.find(
',', start);
101 fields.push_back(sentence.substr(start));
103 if (fields.size() < 15) {
104 TRLogger.error(
"NMEAParser: GGA sentence has too few fields: %d" +
120 if (!fields[2].empty() && !fields[3].empty()) {
121 float lat_deg = 0, lat_min = 0;
122 if (fields[2].size() < 4 || !safe_stof(fields[2].substr(0, 2), lat_deg) ||
123 !safe_stof(fields[2].substr(2), lat_min)) {
124 TRLogger.error(
"NMEAParser: Invalid GGA latitude: %s, %s", fields[2],
128 gps.latitude = lat_deg + lat_min / 60.0f;
129 if (fields[3] ==
"S") gps.latitude = -gps.latitude;
133 if (!fields[4].empty() && !fields[5].empty()) {
134 float lon_deg = 0, lon_min = 0;
135 if (fields[4].size() < 5 || !safe_stof(fields[4].substr(0, 3), lon_deg) ||
136 !safe_stof(fields[4].substr(3), lon_min)) {
137 TRLogger.error(
"NMEAParser: Invalid GGA longitude: %s, %s", fields[4],
141 gps.longitude = lon_deg + lon_min / 60.0f;
142 if (fields[5] ==
"W") gps.longitude = -gps.longitude;
166 if (!fields[9].empty()) {
168 if (!safe_stof(fields[9], alt)) {
169 TRLogger.error(
"NMEAParser: Invalid GGA altitude: %s", fields[9]);
179 bool parseRMC(
const std::string& sentence,
GPSCoordinate& gps)
const {
182 std::vector<std::string> fields;
184 size_t end = sentence.find(
',');
186 while (end != std::string::npos) {
187 fields.push_back(sentence.substr(start, end - start));
189 end = sentence.find(
',', start);
191 fields.push_back(sentence.substr(start));
193 if (fields.size() < 12) {
194 TRLogger.error(
"NMEAParser: RMC sentence has too few fields: %d",
209 if (!fields[3].empty() && !fields[4].empty()) {
210 float lat_deg = 0, lat_min = 0;
211 if (fields[3].size() < 4 || !safe_stof(fields[3].substr(0, 2), lat_deg) ||
212 !safe_stof(fields[3].substr(2), lat_min)) {
213 TRLogger.error(
"NMEAParser: Invalid RMC latitude: %s, %s", fields[3],
217 gps.latitude = lat_deg + lat_min / 60.0f;
218 if (fields[4] ==
"S") gps.latitude = -gps.latitude;
222 if (!fields[5].empty() && !fields[6].empty()) {
223 float lon_deg = 0, lon_min = 0;
224 if (fields[5].size() < 5 || !safe_stof(fields[5].substr(0, 3), lon_deg) ||
225 !safe_stof(fields[5].substr(3), lon_min)) {
226 TRLogger.error(
"NMEAParser: Invalid RMC longitude: %s/%s", fields[5],
230 gps.longitude = lon_deg + lon_min / 60.0f;
231 if (fields[6] ==
"W") gps.longitude = -gps.longitude;
235 if (!fields[7].empty()) {
236 float speed_knots = 0;
237 if (!safe_stof(fields[7], speed_knots)) {
238 TRLogger.error(
"NMEAParser: Invalid RMC speed: %s", fields[7]);
247 std::vector<std::string> split(
const std::string& str,
char delim) {
248 std::vector<std::string> tokens;
249 std::stringstream ss{str};
251 while (std::getline(ss, token, delim)) {
252 tokens.push_back(token);
Represents a geodetic GPS coordinate with latitude, longitude, and optional altitude.
Definition: GPSCoordinate.h:52
Parses NMEA sentences from GPS modules and extracts GPS data.
Definition: NMEAParser.h:47