1
0
mirror of https://github.com/tumic0/GPXSee.git synced 2024-12-01 07:01:16 +01:00
GPXSee/src/data/nmeaparser.cpp

539 lines
9.5 KiB
C++
Raw Normal View History

2016-11-16 23:54:15 +01:00
#include <cstring>
2018-10-11 18:19:35 +02:00
#include "common/util.h"
2016-11-16 23:54:15 +01:00
#include "nmeaparser.h"
2016-11-22 00:13:41 +01:00
static bool validSentence(const char *line, int len)
{
const char *lp;
2016-11-23 18:44:22 +01:00
if (len < 12 || line[0] != '$')
2016-11-22 00:13:41 +01:00
return false;
2016-11-23 18:44:22 +01:00
for (lp = line + len - 1; lp > line + 3; lp--)
2016-11-22 00:13:41 +01:00
if (!::isspace(*lp))
break;
if (*(lp-2) != '*' || !::isalnum(*(lp-1)) || !::isalnum(*(lp)))
return false;
return true;
}
static bool readFloat(const char *data, int len, qreal &f)
{
bool ok;
2021-01-23 15:15:29 +01:00
f = QByteArray::fromRawData(data, len).toFloat(&ok);
2016-11-22 00:13:41 +01:00
return ok;
}
bool NMEAParser::readAltitude(const char *data, int len, qreal &ele)
{
if (!len) {
ele = NAN;
return true;
}
if (!readFloat(data, len, ele)) {
_errorString = "Invalid altitude";
return false;
}
return true;
}
bool NMEAParser::readGeoidHeight(const char *data, int len, qreal &gh)
{
if (!len) {
gh = 0;
return true;
}
if (!readFloat(data, len, gh)) {
_errorString = "Invalid geoid height";
return false;
}
return true;
}
bool NMEAParser::readTime(const char *data, int len, QTime &time)
2016-11-16 23:54:15 +01:00
{
2016-11-17 19:43:02 +01:00
int h, m, s, ms = 0;
2016-11-16 23:54:15 +01:00
2016-11-22 00:13:41 +01:00
if (!len) {
time = QTime();
return true;
}
2016-11-17 19:43:02 +01:00
if (len < 6)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
2021-01-17 19:33:06 +01:00
h = Util::str2int(data, 2);
m = Util::str2int(data + 2, 2);
s = Util::str2int(data + 4, 2);
2016-11-17 19:43:02 +01:00
if (h < 0 || m < 0 || s < 0)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
2016-11-17 19:43:02 +01:00
if (len > 7 && data[6] == '.') {
2021-01-17 19:33:06 +01:00
if ((ms = Util::str2int(data + 7, len - 7)) < 0)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-17 19:43:02 +01:00
}
2016-11-16 23:54:15 +01:00
time = QTime(h, m, s, ms);
if (!time.isValid())
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
return true;
2016-11-22 00:13:41 +01:00
error:
_errorString = "Invalid time";
return false;
2016-11-16 23:54:15 +01:00
}
2016-11-22 00:13:41 +01:00
bool NMEAParser::readDate(const char *data, int len, QDate &date)
2016-11-16 23:54:15 +01:00
{
int y, m, d;
2016-11-22 00:13:41 +01:00
if (!len) {
date = QDate();
return true;
}
if (len < 6)
goto error;
2016-11-16 23:54:15 +01:00
2021-01-17 19:33:06 +01:00
d = Util::str2int(data, 2);
m = Util::str2int(data + 2, 2);
y = Util::str2int(data + 4, len - 4);
2016-11-16 23:54:15 +01:00
if (d < 0 || m < 0 || y < 0)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
2016-11-22 00:13:41 +01:00
if (len - 4 == 2)
date = QDate(y + 2000 < QDate::currentDate().year()
? 2000 + y : 1900 + y, m, d);
else
date = QDate(y, m, d);
2016-11-16 23:54:15 +01:00
if (!date.isValid())
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
return true;
2016-11-22 00:13:41 +01:00
error:
_errorString = "Invalid date";
return false;
2016-11-16 23:54:15 +01:00
}
2016-11-22 00:13:41 +01:00
bool NMEAParser::readLat(const char *data, int len, qreal &lat)
2016-11-16 23:54:15 +01:00
{
int d, mi;
qreal mf;
bool ok;
2016-11-22 00:13:41 +01:00
if (!len) {
lat = NAN;
return true;
}
2016-11-16 23:54:15 +01:00
if (len < 7 || data[4] != '.')
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
2021-01-17 19:33:06 +01:00
d = Util::str2int(data, 2);
mi = Util::str2int(data + 2, 2);
2021-01-23 15:15:29 +01:00
mf = QByteArray::fromRawData(data + 4, len - 4).toFloat(&ok);
2016-11-16 23:54:15 +01:00
if (d < 0 || mi < 0 || !ok)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
lat = d + (((qreal)mi + mf) / 60.0);
if (lat > 90)
2016-11-22 00:13:41 +01:00
goto error;
return true;
error:
_errorString = "Invalid ltitude";
return false;
}
bool NMEAParser::readNS(const char *data, int len, qreal &lat)
{
if (!len) {
lat = NAN;
return true;
}
if (len != 1 || !(*data == 'N' || *data == 'S')) {
_errorString = "Invalid N/S value";
2016-11-16 23:54:15 +01:00
return false;
2016-11-22 00:13:41 +01:00
}
if (*data == 'S')
lat = -lat;
2016-11-16 23:54:15 +01:00
return true;
}
2016-11-22 00:13:41 +01:00
bool NMEAParser::readLon(const char *data, int len, qreal &lon)
2016-11-16 23:54:15 +01:00
{
int d, mi;
qreal mf;
bool ok;
2016-11-22 00:13:41 +01:00
if (!len) {
lon = NAN;
return true;
}
2016-11-16 23:54:15 +01:00
if (len < 8 || data[5] != '.')
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
2021-01-17 19:33:06 +01:00
d = Util::str2int(data, 3);
mi = Util::str2int(data + 3, 2);
2021-01-23 15:15:29 +01:00
mf = QByteArray::fromRawData(data + 5, len - 5).toFloat(&ok);
2016-11-16 23:54:15 +01:00
if (d < 0 || mi < 0 || !ok)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
lon = d + (((qreal)mi + mf) / 60.0);
if (lon > 180)
2016-11-22 00:13:41 +01:00
goto error;
2016-11-16 23:54:15 +01:00
return true;
2016-11-22 00:13:41 +01:00
error:
_errorString = "Invalid longitude";
return false;
2016-11-16 23:54:15 +01:00
}
2016-11-22 00:13:41 +01:00
bool NMEAParser::readEW(const char *data, int len, qreal &lon)
2016-11-17 19:43:02 +01:00
{
2016-11-22 00:13:41 +01:00
if (!len) {
lon = NAN;
return true;
}
2016-11-17 19:43:02 +01:00
2016-11-22 00:13:41 +01:00
if (len != 1 || !(*data == 'E' || *data == 'W')) {
_errorString = "Invalid E/W value";
2016-11-17 19:43:02 +01:00
return false;
2016-11-22 00:13:41 +01:00
}
2016-11-17 19:43:02 +01:00
2016-11-22 00:13:41 +01:00
if (*data == 'W')
lon = -lon;
2016-11-17 19:43:02 +01:00
return true;
}
bool NMEAParser::readRMC(CTX &ctx, const char *line, int len,
SegmentData &segment)
2016-11-16 23:54:15 +01:00
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QTime time;
2016-11-22 00:13:41 +01:00
QDate date;
2016-11-16 23:54:15 +01:00
bool valid = true;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
2016-11-22 00:13:41 +01:00
if (!readTime(vp, lp - vp, time))
2016-11-16 23:54:15 +01:00
return false;
break;
case 2:
if (*vp != 'A')
valid = false;
break;
case 3:
2016-11-22 00:13:41 +01:00
if (!readLat(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 4:
2016-11-22 00:13:41 +01:00
if (!readNS(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 5:
2016-11-22 00:13:41 +01:00
if (!readLon(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 6:
2016-11-22 00:13:41 +01:00
if (!readEW(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 9:
2016-11-22 00:13:41 +01:00
if (!readDate(vp, lp - vp, date))
2016-11-16 23:54:15 +01:00
return false;
break;
}
col++;
vp = lp + 1;
}
}
if (col < 9) {
_errorString = "Invalid RMC sentence";
return false;
}
2016-11-22 00:13:41 +01:00
if (!date.isNull()) {
if (ctx.date.isNull() && !ctx.time.isNull() && !segment.isEmpty())
segment.last().setTimestamp(QDateTime(date, ctx.time, Qt::UTC));
ctx.date = date;
2016-11-22 00:13:41 +01:00
}
2017-07-27 19:47:46 +02:00
Coordinates c(lon, lat);
if (valid && !ctx.GGA && c.isValid()) {
2017-07-27 19:47:46 +02:00
Trackpoint t(c);
if (!ctx.date.isNull() && !time.isNull())
t.setTimestamp(QDateTime(ctx.date, time, Qt::UTC));
2019-02-11 23:28:08 +01:00
segment.append(t);
2016-11-16 23:54:15 +01:00
}
return true;
}
bool NMEAParser::readGGA(CTX &ctx, const char *line, int len,
SegmentData &segment)
2016-11-16 23:54:15 +01:00
{
int col = 1;
const char *vp = line;
qreal lat, lon, ele, gh;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
if (!readTime(vp, lp - vp, ctx.time))
2016-11-16 23:54:15 +01:00
return false;
break;
case 2:
2016-11-22 00:13:41 +01:00
if (!readLat(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 3:
2016-12-13 18:54:58 +01:00
if (!readNS(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 4:
2016-11-22 00:13:41 +01:00
if (!readLon(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 5:
2016-11-22 00:13:41 +01:00
if (!readEW(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 9:
2016-11-22 00:13:41 +01:00
if (!readAltitude(vp, lp - vp, ele))
2016-11-16 23:54:15 +01:00
return false;
break;
case 10:
2016-11-22 00:13:41 +01:00
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid altitude units";
return false;
}
break;
case 11:
2016-11-22 00:13:41 +01:00
if (!readGeoidHeight(vp, lp - vp, gh))
2016-11-16 23:54:15 +01:00
return false;
break;
case 12:
2016-11-22 00:13:41 +01:00
if ((lp - vp) && !((lp - vp) == 1 && *vp == 'M')) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid geoid height units";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 12) {
_errorString = "Invalid GGA sentence";
return false;
}
2017-07-27 19:47:46 +02:00
Coordinates c(lon, lat);
if (c.isValid()) {
Trackpoint t(c);
if (!(ctx.time.isNull() || ctx.date.isNull()))
t.setTimestamp(QDateTime(ctx.date, ctx.time, Qt::UTC));
2016-11-22 00:13:41 +01:00
if (!std::isnan(ele))
t.setElevation(ele - gh);
2019-02-11 23:28:08 +01:00
segment.append(t);
2016-11-16 23:54:15 +01:00
ctx.GGA = true;
2016-11-22 00:13:41 +01:00
}
2016-11-16 23:54:15 +01:00
return true;
}
bool NMEAParser::readWPL(const char *line, int len, QVector<Waypoint> &waypoints)
2016-11-16 23:54:15 +01:00
{
int col = 1;
const char *vp = line;
qreal lat, lon;
QString name;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 1:
2016-11-22 00:13:41 +01:00
if (!readLat(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 2:
2016-11-22 00:13:41 +01:00
if (!readNS(vp, lp - vp, lat))
2016-11-16 23:54:15 +01:00
return false;
break;
case 3:
2016-11-22 00:13:41 +01:00
if (!readLon(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 4:
2016-11-22 00:13:41 +01:00
if (!readEW(vp, lp - vp, lon))
2016-11-16 23:54:15 +01:00
return false;
break;
case 5:
name = QString(QByteArray(vp, lp - vp));
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid WPL sentence";
return false;
}
2017-07-27 19:47:46 +02:00
Coordinates c(lon, lat);
if (c.isValid()) {
Waypoint w(c);
2016-11-22 00:13:41 +01:00
w.setName(name);
2017-07-27 19:47:46 +02:00
waypoints.append(w);
2016-11-22 00:13:41 +01:00
}
2016-11-16 23:54:15 +01:00
return true;
}
bool NMEAParser::readZDA(CTX &ctx, const char *line, int len)
2016-11-16 23:54:15 +01:00
{
int col = 1;
const char *vp = line;
int d, m, y;
for (const char *lp = line; lp < line + len; lp++) {
if (*lp == ',' || *lp == '*') {
switch (col) {
case 2:
2016-11-22 00:13:41 +01:00
if (!(lp - vp))
return true;
2021-01-17 19:33:06 +01:00
if ((d = Util::str2int(vp, lp - vp)) < 0) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid day";
return false;
}
break;
case 3:
2016-11-22 00:13:41 +01:00
if (!(lp - vp))
return true;
2021-01-17 19:33:06 +01:00
if ((m = Util::str2int(vp, lp - vp)) < 0) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid month";
return false;
}
break;
case 4:
2016-11-22 00:13:41 +01:00
if (!(lp - vp))
return true;
2021-01-17 19:33:06 +01:00
if ((y = Util::str2int(vp, lp - vp)) < 0) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid year";
return false;
}
break;
}
col++;
vp = lp + 1;
}
}
if (col < 4) {
_errorString = "Invalid ZDA sentence";
return false;
}
ctx.date = QDate(y, m, d);
if (!ctx.date.isValid()) {
2016-11-16 23:54:15 +01:00
_errorString = "Invalid date";
return false;
}
return true;
}
2017-07-27 19:47:46 +02:00
bool NMEAParser::parse(QFile *file, QList<TrackData> &tracks,
2019-01-31 01:46:53 +01:00
QList<RouteData> &routes, QList<Area> &polygons,
QVector<Waypoint> &waypoints)
2016-11-16 23:54:15 +01:00
{
2017-07-27 19:47:46 +02:00
Q_UNUSED(routes);
2019-01-31 01:46:53 +01:00
Q_UNUSED(polygons);
2016-11-16 23:54:15 +01:00
qint64 len;
char line[80 + 2 + 1 + 1];
2019-02-11 23:28:08 +01:00
SegmentData segment;
CTX ctx;
2016-11-16 23:54:15 +01:00
_errorLine = 1;
_errorString.clear();
2016-11-22 00:13:41 +01:00
2016-11-16 23:54:15 +01:00
while (!file->atEnd()) {
len = file->readLine(line, sizeof(line));
if (len < 0) {
_errorString = "I/O error";
return false;
} else if (len > (qint64)sizeof(line) - 1) {
_errorString = "Line limit exceeded";
return false;
}
2016-11-22 00:13:41 +01:00
if (validSentence(line, len)) {
if (!memcmp(line + 3, "RMC,", 4)) {
if (!readRMC(ctx, line + 7, len - 7, segment))
2016-11-22 00:13:41 +01:00
return false;
} else if (!memcmp(line + 3, "GGA,", 4)) {
if (!readGGA(ctx, line + 7, len - 7, segment))
2016-11-22 00:13:41 +01:00
return false;
} else if (!memcmp(line + 3, "WPL,", 4)) {
if (!readWPL(line + 7, len - 7, waypoints))
2016-11-22 00:13:41 +01:00
return false;
} else if (!memcmp(line + 3, "ZDA,", 4)) {
if (!readZDA(ctx, line + 7, len - 7))
2016-11-22 00:13:41 +01:00
return false;
}
2016-11-16 23:54:15 +01:00
}
_errorLine++;
}
2019-02-11 23:28:08 +01:00
if (!segment.size() && !waypoints.size()) {
2016-11-17 19:43:02 +01:00
_errorString = "No usable NMEA sentence found";
return false;
}
2019-02-11 23:28:08 +01:00
if (segment.size()) {
tracks.append(TrackData());
tracks.last().append(segment);
}
2016-11-16 23:54:15 +01:00
return true;
}