2016-11-10 00:08:11 +01:00
|
|
|
#include <cstring>
|
2018-10-11 18:19:35 +02:00
|
|
|
#include "common/util.h"
|
2016-11-10 00:08:11 +01:00
|
|
|
#include "igcparser.h"
|
|
|
|
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
static bool readLat(const char *data, qreal &lat)
|
2016-11-10 00:08:11 +01:00
|
|
|
{
|
2016-11-11 17:57:49 +01:00
|
|
|
int d = str2int(data, 2);
|
|
|
|
int mi = str2int(data + 2, 2);
|
|
|
|
int mf = str2int(data + 4, 3);
|
2016-11-10 00:08:11 +01:00
|
|
|
if (d < 0 || mi < 0 || mf < 0)
|
|
|
|
return false;
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!(data[7] == 'N' || data[7] == 'S'))
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
lat = d + (((qreal)mi + (qreal)mf/1000) / 60);
|
|
|
|
if (lat > 90)
|
|
|
|
return false;
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (data[7] == 'S')
|
2016-11-10 00:08:11 +01:00
|
|
|
lat = -lat;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
static bool readLon(const char *data, qreal &lon)
|
2016-11-10 00:08:11 +01:00
|
|
|
{
|
2016-11-11 17:57:49 +01:00
|
|
|
int d = str2int(data, 3);
|
|
|
|
int mi = str2int(data + 3, 2);
|
|
|
|
int mf = str2int(data + 5, 3);
|
2016-11-10 00:08:11 +01:00
|
|
|
if (d < 0 || mi < 0 || mf < 0)
|
|
|
|
return false;
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!(data[8] == 'E' || data[8] == 'W'))
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
lon = d + (((qreal)mi + (qreal)mf/1000) / 60);
|
|
|
|
if (lon > 180)
|
|
|
|
return false;
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (data[8] == 'W')
|
2016-11-10 00:08:11 +01:00
|
|
|
lon = -lon;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
static bool readAltitude(const char *data, qreal &ele)
|
2016-11-10 00:08:11 +01:00
|
|
|
{
|
2018-01-06 21:44:03 +01:00
|
|
|
int ga;
|
2016-11-10 00:08:11 +01:00
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!(data[0] == 'A' || data[0] == 'V'))
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
|
|
|
|
2018-01-06 21:44:03 +01:00
|
|
|
if (data[6] == '-') {
|
|
|
|
if ((ga = str2int(data + 7, 4)) < 0)
|
|
|
|
return false;
|
|
|
|
ga = -ga;
|
|
|
|
} else {
|
|
|
|
if ((ga = str2int(data + 6, 5)) < 0)
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-10 00:08:11 +01:00
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (data[0] == 'A')
|
2018-01-06 21:44:03 +01:00
|
|
|
ele = (qreal)ga;
|
2016-11-10 00:08:11 +01:00
|
|
|
else
|
|
|
|
ele = NAN;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
static bool readTimestamp(const char *data, QTime &time)
|
|
|
|
{
|
|
|
|
int h = str2int(data, 2);
|
|
|
|
int m = str2int(data + 2, 2);
|
|
|
|
int s = str2int(data + 4, 2);
|
|
|
|
|
|
|
|
if (h < 0 || m < 0 || s < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
time = QTime(h, m, s);
|
2016-11-16 23:54:15 +01:00
|
|
|
if (!time.isValid())
|
|
|
|
return false;
|
2016-11-11 17:57:49 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-11-10 08:46:59 +01:00
|
|
|
static bool readARecord(const char *line, qint64 len)
|
2016-11-10 00:08:11 +01:00
|
|
|
{
|
2020-03-24 22:06:03 +01:00
|
|
|
/* The minimal A record length should be 7 according to the specification,
|
|
|
|
but records with length of 6 exist in the wild */
|
|
|
|
if (len < 6 || line[0] != 'A')
|
2016-11-10 08:46:59 +01:00
|
|
|
return false;
|
|
|
|
|
2020-03-24 22:06:03 +01:00
|
|
|
for (int i = 1; i < 6; i++)
|
2016-11-10 08:46:59 +01:00
|
|
|
if (!::isprint(line[i]))
|
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-01-23 23:22:04 +01:00
|
|
|
bool IGCParser::readHRecord(CTX &ctx, const char *line, int len)
|
2016-11-10 08:46:59 +01:00
|
|
|
{
|
2018-09-09 14:38:37 +02:00
|
|
|
if (len < 11 || ::strncmp(line, "HFDTE", 5))
|
2016-11-10 08:46:59 +01:00
|
|
|
return true;
|
|
|
|
|
2018-09-09 14:38:37 +02:00
|
|
|
int offset = (len < 16 || ::strncmp(line + 5, "DATE:", 5)) ? 5 : 10;
|
|
|
|
|
|
|
|
int d = str2int(line + offset, 2);
|
|
|
|
int m = str2int(line + offset + 2, 2);
|
|
|
|
int y = str2int(line + offset + 4, 2);
|
2016-11-10 00:08:11 +01:00
|
|
|
|
|
|
|
if (y < 0 || m < 0 || d < 0) {
|
2016-11-10 08:46:59 +01:00
|
|
|
_errorString = "Invalid date header format";
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-23 23:22:04 +01:00
|
|
|
ctx.date = QDate(y + 2000 <= QDate::currentDate().year()
|
|
|
|
? 2000 + y : 1900 + y, m, d);
|
|
|
|
if (!ctx.date.isValid()) {
|
2016-11-10 08:46:59 +01:00
|
|
|
_errorString = "Invalid date";
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-10 00:08:11 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-01-23 23:22:04 +01:00
|
|
|
bool IGCParser::readBRecord(CTX &ctx, const char *line, int len,
|
|
|
|
SegmentData &segment)
|
2016-11-10 00:08:11 +01:00
|
|
|
{
|
|
|
|
qreal lat, lon, ele;
|
2019-03-10 18:16:43 +01:00
|
|
|
QTime time;
|
2016-11-10 00:08:11 +01:00
|
|
|
|
2016-11-10 08:46:59 +01:00
|
|
|
if (len < 35)
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
2019-08-25 13:05:27 +02:00
|
|
|
if (line[24] != 'A')
|
|
|
|
return true;
|
2016-11-10 00:08:11 +01:00
|
|
|
|
2019-03-10 18:16:43 +01:00
|
|
|
if (!readTimestamp(line + 1, time)) {
|
2016-11-11 17:57:49 +01:00
|
|
|
_errorString = "Invalid timestamp";
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!readLat(line + 7, lat)) {
|
2016-11-10 00:08:11 +01:00
|
|
|
_errorString = "Invalid latitude";
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!readLon(line + 15, lon)) {
|
2016-11-10 00:08:11 +01:00
|
|
|
_errorString = "Invalid longitude";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!readAltitude(line + 24, ele)) {
|
2016-11-10 00:08:11 +01:00
|
|
|
_errorString = "Invalid altitude";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-01-23 23:22:04 +01:00
|
|
|
if (time < ctx.time && !segment.isEmpty()
|
|
|
|
&& ctx.date == segment.last().timestamp().date())
|
|
|
|
ctx.date = ctx.date.addDays(1);
|
|
|
|
ctx.time = time;
|
2019-03-10 18:16:43 +01:00
|
|
|
|
2016-11-10 00:08:11 +01:00
|
|
|
Trackpoint t(Coordinates(lon, lat));
|
2020-01-23 23:22:04 +01:00
|
|
|
t.setTimestamp(QDateTime(ctx.date, ctx.time, Qt::UTC));
|
2016-11-10 00:08:11 +01:00
|
|
|
t.setElevation(ele);
|
2019-02-11 23:28:08 +01:00
|
|
|
segment.append(t);
|
2016-11-10 00:08:11 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-01-23 23:22:04 +01:00
|
|
|
bool IGCParser::readCRecord(const char *line, int len, RouteData &route)
|
2016-11-11 17:57:49 +01:00
|
|
|
{
|
|
|
|
qreal lat, lon;
|
|
|
|
|
|
|
|
|
|
|
|
if (len < 18)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!readLat(line + 1, lat)) {
|
|
|
|
_errorString = "Invalid latitude";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!readLon(line + 9, lon)) {
|
|
|
|
_errorString = "Invalid longitude";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(lat == 0 && lon == 0)) {
|
|
|
|
QByteArray ba(line + 18, len - 19);
|
|
|
|
|
|
|
|
Waypoint w(Coordinates(lon, lat));
|
|
|
|
w.setName(QString(ba.trimmed()));
|
2017-07-27 19:47:46 +02:00
|
|
|
route.append(w);
|
2016-11-11 17:57:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-07-27 19:47:46 +02:00
|
|
|
bool IGCParser::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-10 00:08:11 +01:00
|
|
|
{
|
2017-07-27 19:47:46 +02:00
|
|
|
Q_UNUSED(waypoints);
|
2019-01-31 01:46:53 +01:00
|
|
|
Q_UNUSED(polygons);
|
2016-11-10 00:08:11 +01:00
|
|
|
qint64 len;
|
|
|
|
char line[76 + 2 + 1 + 1];
|
2016-11-11 17:57:49 +01:00
|
|
|
bool route = false, track = false;
|
2020-01-23 23:22:04 +01:00
|
|
|
CTX ctx;
|
2016-11-11 17:57:49 +01:00
|
|
|
|
2016-11-10 00:08:11 +01:00
|
|
|
|
|
|
|
_errorLine = 1;
|
|
|
|
_errorString.clear();
|
|
|
|
|
2016-11-11 21:53:23 +01:00
|
|
|
while (!file->atEnd()) {
|
|
|
|
len = file->readLine(line, sizeof(line));
|
|
|
|
|
2016-11-10 00:08:11 +01:00
|
|
|
if (len < 0) {
|
|
|
|
_errorString = "I/O error";
|
|
|
|
return false;
|
2016-11-10 08:46:59 +01:00
|
|
|
} else if (len > (qint64)sizeof(line) - 1) {
|
2016-11-10 00:08:11 +01:00
|
|
|
_errorString = "Line limit exceeded";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-11-10 08:46:59 +01:00
|
|
|
if (_errorLine == 1) {
|
|
|
|
if (!readARecord(line, len)) {
|
|
|
|
_errorString = "Invalid/missing A record";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (line[0] == 'H') {
|
2020-01-23 23:22:04 +01:00
|
|
|
if (!readHRecord(ctx, line, len))
|
2016-11-10 08:46:59 +01:00
|
|
|
return false;
|
2016-11-11 17:57:49 +01:00
|
|
|
} else if (line[0] == 'C') {
|
|
|
|
if (route) {
|
2020-01-23 23:22:04 +01:00
|
|
|
if (!readCRecord(line, len, routes.last()))
|
2016-11-11 17:57:49 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
route = true;
|
2017-07-27 19:47:46 +02:00
|
|
|
routes.append(RouteData());
|
2016-11-11 17:57:49 +01:00
|
|
|
}
|
2016-11-10 08:46:59 +01:00
|
|
|
} else if (line[0] == 'B') {
|
2020-01-23 23:22:04 +01:00
|
|
|
if (ctx.date.isNull()) {
|
2016-11-10 08:46:59 +01:00
|
|
|
_errorString = "Missing date header";
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
2016-11-10 08:46:59 +01:00
|
|
|
}
|
2016-11-11 17:57:49 +01:00
|
|
|
if (!track) {
|
2017-07-27 19:47:46 +02:00
|
|
|
tracks.append(TrackData());
|
2019-02-11 23:28:08 +01:00
|
|
|
tracks.last().append(SegmentData());
|
2020-01-23 23:22:04 +01:00
|
|
|
ctx.time = QTime(0, 0);
|
2016-11-11 17:57:49 +01:00
|
|
|
track = true;
|
|
|
|
}
|
2020-01-23 23:22:04 +01:00
|
|
|
if (!readBRecord(ctx, line, len, tracks.last().last()))
|
2016-11-10 00:08:11 +01:00
|
|
|
return false;
|
2016-11-10 08:46:59 +01:00
|
|
|
}
|
2016-11-10 00:08:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
_errorLine++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|