Initial commit: add Location Plotter
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
#include "LocationParser.h"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
double toDouble(QString value, bool *ok)
|
||||
{
|
||||
value.replace(',', '.');
|
||||
return value.toDouble(ok);
|
||||
}
|
||||
|
||||
QString normalizedCoordinateText(QString value)
|
||||
{
|
||||
value.replace(',', '.');
|
||||
if (value.startsWith('+')) {
|
||||
value.remove(0, 1);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
QString normalizeKey(QString key)
|
||||
{
|
||||
key = key.toLower();
|
||||
key.remove('_');
|
||||
key.remove('-');
|
||||
key.remove(' ');
|
||||
|
||||
if (key == "lat" || key == "latitude" || key == "lattitude") {
|
||||
return "latitude";
|
||||
}
|
||||
|
||||
if (key == "long" || key == "longitude" || key == "lon" || key == "lng") {
|
||||
return "longitude";
|
||||
}
|
||||
|
||||
if (key == "hheight" || key == "height" || key == "alt" || key == "altitude") {
|
||||
return "hheight";
|
||||
}
|
||||
|
||||
if (key == "sat" || key == "sats" || key == "satellite" || key == "satellites"
|
||||
|| key == "activesat" || key == "activesats" || key == "activesatellite"
|
||||
|| key == "activesatellites" || key == "numsat" || key == "numsats"
|
||||
|| key == "numbersat" || key == "numbersats" || key == "satactive"
|
||||
|| key == "satsactive" || key == "satelliteactive" || key == "satellitesactive"
|
||||
|| key == "uydu" || key == "uydular") {
|
||||
return "satellites";
|
||||
}
|
||||
|
||||
if (key == "gdop" || key == "pdop" || key == "hdop" || key == "vdop" || key == "tdop") {
|
||||
return key;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool isValidLatitude(double latitude)
|
||||
{
|
||||
return std::isfinite(latitude) && latitude >= -90.0 && latitude <= 90.0;
|
||||
}
|
||||
|
||||
bool isValidLongitude(double longitude)
|
||||
{
|
||||
return std::isfinite(longitude) && longitude >= -180.0 && longitude <= 180.0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<ParsedTelemetry> LocationParser::parseLine(const QString &line)
|
||||
{
|
||||
const QString trimmed = line.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static const QRegularExpression labeledValueRegex(
|
||||
QStringLiteral(R"((^|[^A-Za-z0-9_])(?<key>lat(?:t?itude)?|long(?:itude)?|lon|lng|hheight|height|alt(?:itude)?|active[_ -]?sat(?:ellite)?s?|num(?:ber)?[_ -]?sat(?:ellite)?s?|sat(?:ellite)?s?[_ -]?active|sat(?:ellite)?s?|sats|uydu(?:lar)?|[gphvt][_ -]?dop)(?:\s*(?:=|:|->)?\s*)(?<value>[+-]?(?:\d+(?:[\.,]\d+)?|[\.,]\d+)(?:[eE][+-]?\d+)?))"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const QRegularExpression satelliteCountPhraseRegex(
|
||||
QStringLiteral(R"((^|[^A-Za-z0-9_\.])(?<value>\d+)\s+(?:active\s+)?(?:sat(?:ellite)?s?|uydu(?:lar)?)(?=$|[\s,.;)\]]))"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
|
||||
ParsedTelemetry telemetry;
|
||||
telemetry.rawLine = trimmed;
|
||||
bool hadCoordinateField = false;
|
||||
bool hadLatitudeField = false;
|
||||
bool hadLongitudeField = false;
|
||||
|
||||
auto matches = labeledValueRegex.globalMatch(trimmed);
|
||||
while (matches.hasNext()) {
|
||||
const QRegularExpressionMatch match = matches.next();
|
||||
const QString field = normalizeKey(match.captured(QStringLiteral("key")));
|
||||
if (field.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const double value = toDouble(match.captured(QStringLiteral("value")), &ok);
|
||||
if (!ok || !std::isfinite(value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field == "latitude") {
|
||||
hadCoordinateField = true;
|
||||
hadLatitudeField = true;
|
||||
telemetry.latitude = value;
|
||||
telemetry.latitudeText = normalizedCoordinateText(
|
||||
match.captured(QStringLiteral("value")));
|
||||
} else if (field == "longitude") {
|
||||
hadCoordinateField = true;
|
||||
hadLongitudeField = true;
|
||||
telemetry.longitude = value;
|
||||
telemetry.longitudeText = normalizedCoordinateText(
|
||||
match.captured(QStringLiteral("value")));
|
||||
} else if (field == "hheight") {
|
||||
telemetry.hheight = value;
|
||||
} else if (field == "satellites") {
|
||||
const int count = static_cast<int>(std::llround(value));
|
||||
if (count >= 0) {
|
||||
telemetry.satellites = count;
|
||||
}
|
||||
} else if (field == "gdop") {
|
||||
if (value >= 0.0) {
|
||||
telemetry.dop.gdop = value;
|
||||
}
|
||||
} else if (field == "pdop") {
|
||||
if (value >= 0.0) {
|
||||
telemetry.dop.pdop = value;
|
||||
}
|
||||
} else if (field == "hdop") {
|
||||
if (value >= 0.0) {
|
||||
telemetry.dop.hdop = value;
|
||||
}
|
||||
} else if (field == "vdop") {
|
||||
if (value >= 0.0) {
|
||||
telemetry.dop.vdop = value;
|
||||
}
|
||||
} else if (field == "tdop") {
|
||||
if (value >= 0.0) {
|
||||
telemetry.dop.tdop = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto satellitePhraseMatches = satelliteCountPhraseRegex.globalMatch(trimmed);
|
||||
while (satellitePhraseMatches.hasNext()) {
|
||||
const QRegularExpressionMatch match = satellitePhraseMatches.next();
|
||||
bool ok = false;
|
||||
const int count = match.captured(QStringLiteral("value")).toInt(&ok);
|
||||
if (ok && count >= 0) {
|
||||
telemetry.satellites = count;
|
||||
}
|
||||
}
|
||||
|
||||
if (telemetry.latitude.has_value() && !isValidLatitude(*telemetry.latitude)) {
|
||||
telemetry.latitude.reset();
|
||||
telemetry.latitudeText.clear();
|
||||
}
|
||||
if (telemetry.longitude.has_value() && !isValidLongitude(*telemetry.longitude)) {
|
||||
telemetry.longitude.reset();
|
||||
telemetry.longitudeText.clear();
|
||||
}
|
||||
|
||||
if (hadLatitudeField && hadLongitudeField
|
||||
&& (!telemetry.hasLocation()
|
||||
|| !isValidCoordinate(*telemetry.latitude, *telemetry.longitude))) {
|
||||
telemetry.latitude.reset();
|
||||
telemetry.longitude.reset();
|
||||
telemetry.latitudeText.clear();
|
||||
telemetry.longitudeText.clear();
|
||||
telemetry.hheight.reset();
|
||||
} else if (hadCoordinateField && !telemetry.latitude.has_value()
|
||||
&& !telemetry.longitude.has_value()) {
|
||||
telemetry.hheight.reset();
|
||||
}
|
||||
|
||||
if (!telemetry.hasAnyValue()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return telemetry;
|
||||
}
|
||||
|
||||
bool LocationParser::isValidCoordinate(double latitude, double longitude)
|
||||
{
|
||||
return std::isfinite(latitude) && std::isfinite(longitude)
|
||||
&& latitude >= -90.0 && latitude <= 90.0
|
||||
&& longitude >= -180.0 && longitude <= 180.0;
|
||||
}
|
||||
|
||||
QSet<QString> LocationParser::parseGpsPrns(const QString &line)
|
||||
{
|
||||
QSet<QString> result;
|
||||
const auto addPrn = [&result](QString value, bool zeroBased = false) {
|
||||
value = value.trimmed().toUpper();
|
||||
if (value.startsWith(QLatin1Char('G'))) value.remove(0, 1);
|
||||
bool ok = false;
|
||||
int number = value.toInt(&ok);
|
||||
if (ok && zeroBased) ++number;
|
||||
if (ok && number >= 1 && number <= 63) {
|
||||
result.insert(QStringLiteral("G%1").arg(number, 2, 10, QLatin1Char('0')));
|
||||
}
|
||||
};
|
||||
|
||||
static const QRegularExpression gpsToken(
|
||||
QStringLiteral(R"((?:^|[^A-Za-z0-9])G(?<prn>\d{1,2})(?=$|[^0-9]))"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
auto gpsMatches = gpsToken.globalMatch(line);
|
||||
while (gpsMatches.hasNext()) addPrn(gpsMatches.next().captured(QStringLiteral("prn")));
|
||||
|
||||
static const QRegularExpression labeledList(
|
||||
QStringLiteral(R"((?<label>\bPRNS?\b|\bSVS?\b|\bSVNS?\b|\bSVIDS?\b)\s*(?:=|:|->)?\s*[\[(]?\s*(?<list>(?:G?\d{1,2})(?:\s*[,;/| ]\s*G?\d{1,2})*)\s*[\])]?)"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
static const QRegularExpression number(QStringLiteral(R"(G?(\d{1,2}))"),
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
auto listMatches = labeledList.globalMatch(line);
|
||||
while (listMatches.hasNext()) {
|
||||
const QRegularExpressionMatch listMatch = listMatches.next();
|
||||
const bool zeroBased = listMatch.captured(QStringLiteral("label")).startsWith(
|
||||
QStringLiteral("SVN"), Qt::CaseInsensitive);
|
||||
auto values = number.globalMatch(listMatch.captured(QStringLiteral("list")));
|
||||
while (values.hasNext()) addPrn(values.next().captured(1), zeroBased);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user