Initial commit: add Location Plotter
Ubuntu build / build-and-test (ubuntu-22.04) (push) Has been cancelled
Ubuntu build / build-and-test (ubuntu-24.04) (push) Has been cancelled

This commit is contained in:
2026-07-16 13:15:54 +03:00
commit 2856c5a560
25 changed files with 4630 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
#include "../src/MainWindow.h"
#include "../src/MapWidget.h"
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDoubleSpinBox>
#include <QLabel>
#include <QLineEdit>
#include <QMetaObject>
#include <QSpinBox>
#include <QTableView>
#include <QTabWidget>
#include <QSplitter>
#include <cassert>
namespace {
void sendLine(MainWindow &window, const QString &line)
{
const bool invoked = QMetaObject::invokeMethod(
&window, "handleSerialLine", Qt::DirectConnection, Q_ARG(QString, line));
assert(invoked);
}
void flushUpdates(MainWindow &window)
{
const bool invoked = QMetaObject::invokeMethod(
&window, "flushUiUpdates", Qt::DirectConnection);
assert(invoked);
}
} // namespace
int main(int argc, char **argv)
{
QApplication app(argc, argv);
MainWindow window;
auto *table = window.findChild<QTableView *>();
auto *referenceLatInput = window.findChild<QLineEdit *>(QStringLiteral("referenceLatInput"));
auto *referenceLonInput = window.findChild<QLineEdit *>(QStringLiteral("referenceLonInput"));
auto *cepValue = window.findChild<QLabel *>(QStringLiteral("cepValue"));
auto *maxPointsInput = window.findChild<QSpinBox *>(QStringLiteral("maxPointsInput"));
auto *elevationMaskSpin = window.findChild<QDoubleSpinBox *>(QStringLiteral("elevationMaskSpin"));
auto *showTelemetry = window.findChild<QCheckBox *>(QStringLiteral("showTelemetryCheck"));
auto *showRecentPoints = window.findChild<QCheckBox *>(QStringLiteral("showRecentPointsCheck"));
auto *showAverage = window.findChild<QCheckBox *>(QStringLiteral("showAverageCheck"));
auto *portSelector = window.findChild<QComboBox *>(QStringLiteral("portSelector"));
auto *map = window.findChild<MapWidget *>();
auto *mainViewTabs = window.findChild<QTabWidget *>(QStringLiteral("mainViewTabs"));
auto *sideSplitter = window.findChild<QSplitter *>(QStringLiteral("sideSplitter"));
assert(table != nullptr);
assert(referenceLatInput != nullptr);
assert(referenceLonInput != nullptr);
assert(cepValue != nullptr);
assert(maxPointsInput != nullptr);
assert(elevationMaskSpin != nullptr);
assert(elevationMaskSpin->value() == 10);
assert(elevationMaskSpin->minimum() == 0);
assert(elevationMaskSpin->maximum() == 90.0);
elevationMaskSpin->setValue(12.5);
assert(elevationMaskSpin->value() == 12.5);
assert(showTelemetry != nullptr);
assert(showRecentPoints != nullptr);
assert(showAverage != nullptr);
assert(portSelector != nullptr);
assert(!portSelector->isEditable());
assert(portSelector->count() >= 1);
for (int i = 0; i < portSelector->count(); ++i) {
assert(portSelector->itemText(i).startsWith(QStringLiteral("/dev/ttyUSB")));
assert(!portSelector->itemText(i).trimmed().isEmpty());
}
assert(map != nullptr);
assert(mainViewTabs != nullptr);
assert(mainViewTabs->count() == 2);
assert(sideSplitter != nullptr);
assert(sideSplitter->orientation() == Qt::Vertical);
assert(QMetaObject::invokeMethod(map, "referenceLocationSelected", Qt::DirectConnection,
Q_ARG(double, 40.25), Q_ARG(double, 29.5)));
assert(referenceLatInput->text() == QStringLiteral("40.250000000000000"));
assert(referenceLonInput->text() == QStringLiteral("29.500000000000000"));
referenceLatInput->clear();
referenceLonInput->clear();
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
showTelemetry->setChecked(false);
assert(!showRecentPoints->isEnabled());
assert(!showAverage->isEnabled());
showTelemetry->setChecked(true);
assert(showRecentPoints->isEnabled());
assert(showAverage->isEnabled());
sendLine(window, QStringLiteral("lattitude : 40.78925034214206846173"));
sendLine(window, QStringLiteral("longitude : 29.46277530841727809729"));
assert(table->model()->rowCount() == 0);
sendLine(window, QStringLiteral("NAV solve done"));
flushUpdates(window);
assert(table->model()->rowCount() == 1);
assert(table->model()->data(table->model()->index(0, 1)).toString()
== QStringLiteral("40.78925034214206846173"));
assert(table->model()->data(table->model()->index(0, 2)).toString()
== QStringLiteral("29.46277530841727809729"));
referenceLatInput->setText(QStringLiteral("40.78925034214206846173"));
referenceLonInput->setText(QStringLiteral("29.46277530841727809729"));
const bool referenceUpdated = QMetaObject::invokeMethod(
&window, "updateReferenceLocation", Qt::DirectConnection);
assert(referenceUpdated);
assert(cepValue->text().startsWith(QStringLiteral("0.00 m")));
assert(cepValue->text().contains(QStringLiteral("N=1")));
const bool cleared = QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection);
assert(cleared);
assert(table->model()->rowCount() == 0);
assert(referenceLatInput->text() == QStringLiteral("40.78925034214206846173"));
assert(referenceLonInput->text() == QStringLiteral("29.46277530841727809729"));
assert(cepValue->text() == QStringLiteral("-"));
referenceLatInput->setText(QStringLiteral("40.0"));
referenceLonInput->setText(QStringLiteral("29.0"));
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
sendLine(window, QStringLiteral("lat=40.000 long=29.000"));
sendLine(window, QStringLiteral("lat=40.001 long=29.000"));
sendLine(window, QStringLiteral("lat=40.002 long=29.000"));
flushUpdates(window);
assert(cepValue->text().startsWith(QStringLiteral("111 m")));
assert(cepValue->text().contains(QStringLiteral("N=3")));
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
referenceLatInput->setText(QStringLiteral("40.78925034214206846173"));
referenceLonInput->setText(QStringLiteral("29.46277530841727809729"));
assert(QMetaObject::invokeMethod(&window, "updateReferenceLocation", Qt::DirectConnection));
maxPointsInput->setValue(10000);
for (int i = 0; i < 10000; ++i) {
sendLine(window, QStringLiteral("lat=40.78925 long=29.46277 sats=7"));
}
flushUpdates(window);
assert(table->model()->rowCount() == 10000);
assert(cepValue->text().contains(QStringLiteral("N=10000")));
assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection));
maxPointsInput->setValue(10);
const QByteArray screenshotPath = qgetenv("LOCATION_PLOTTER_TEST_SCREENSHOT");
if (!screenshotPath.isEmpty()) {
sendLine(window, QStringLiteral("NAV solve start sats:5"));
sendLine(window, QStringLiteral("tracking PRNs: G03, G08, G14, G21, G27"));
sendLine(window, QStringLiteral(
"lat=40.78925034214206846173 long=29.46277530841727809729 "
"hheight=-89.05779508221894502640 sats=7 PDOP=1.235"));
flushUpdates(window);
mainViewTabs->setCurrentIndex(1);
window.show();
app.processEvents();
window.repaint();
app.processEvents();
assert(window.grab().save(QString::fromLocal8Bit(screenshotPath), "PNG"));
}
return 0;
}
+199
View File
@@ -0,0 +1,199 @@
#include "../src/LocationParser.h"
#include "../src/RinexNavigation.h"
#include <QCoreApplication>
#include <QFile>
#include <cassert>
#include <cmath>
namespace {
bool closeTo(double left, double right)
{
return std::fabs(left - right) < 0.000001;
}
} // namespace
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("DBG lat=41.0082000 long=28.9784000 hheight=123.45 sats=7"));
assert(parsed.has_value());
assert(parsed->hasLocation());
assert(closeTo(*parsed->latitude, 41.0082));
assert(closeTo(*parsed->longitude, 28.9784));
assert(closeTo(*parsed->hheight, 123.45));
assert(parsed->satellites == 7);
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("latitude: -12,5 longitude: 33,25 height: 99 activeSats: 4"));
assert(parsed.has_value());
assert(parsed->hasLocation());
assert(closeTo(*parsed->latitude, -12.5));
assert(closeTo(*parsed->longitude, 33.25));
assert(parsed->latitudeText == QStringLiteral("-12.5"));
assert(parsed->longitudeText == QStringLiteral("33.25"));
assert(closeTo(*parsed->hheight, 99.0));
assert(parsed->satellites == 4);
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral("satellites_active=11"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(parsed->satellites == 11);
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("lat=200 long=28 hheight=100 active_sats=8"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(parsed->latitudeText.isEmpty());
assert(parsed->longitudeText.isEmpty());
assert(!parsed->hheight.has_value());
assert(parsed->satellites == 8);
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("lattitude : 40.78925034214206846173"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(closeTo(*parsed->latitude, 40.78925034214206846173));
assert(parsed->latitudeText == QStringLiteral("40.78925034214206846173"));
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("longitude : 29.46277530841727809729"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(closeTo(*parsed->longitude, 29.46277530841727809729));
assert(parsed->longitudeText == QStringLiteral("29.46277530841727809729"));
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("height : -89.05779508221894502640 "));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(closeTo(*parsed->hheight, -89.05779508221894502640));
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("NAV solve start sats:4 mode:periodic abs_span_ms:2.176"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(parsed->satellites == 4);
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("[NAV] navigation software running with 5 satellites"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(parsed->satellites == 5);
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral("GDOP : 3.9576744550"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(closeTo(*parsed->dop.gdop, 3.9576744550));
}
{
const auto parsed = LocationParser::parseLine(
QStringLiteral("PDOP : 3.2540598660 HDOP : 2.3631161789 VDOP : 2.2370935468 TDOP : 2.2526165852"));
assert(parsed.has_value());
assert(!parsed->hasLocation());
assert(closeTo(*parsed->dop.pdop, 3.2540598660));
assert(closeTo(*parsed->dop.hdop, 2.3631161789));
assert(closeTo(*parsed->dop.vdop, 2.2370935468));
assert(closeTo(*parsed->dop.tdop, 2.2526165852));
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral("plain debug noise"));
assert(!parsed.has_value());
}
{
const QSet<QString> prns = LocationParser::parseGpsPrns(
QStringLiteral("tracking PRNs: 3, G08, 14; SV=G21 SVID: 27"));
assert(prns.contains(QStringLiteral("G03")));
assert(prns.contains(QStringLiteral("G08")));
assert(prns.contains(QStringLiteral("G14")));
assert(prns.contains(QStringLiteral("G21")));
assert(prns.contains(QStringLiteral("G27")));
assert(prns.size() == 5);
}
{
const QString line = QStringLiteral(
"[OK] [TRK] tracked satellites=5 svn=[2,3,5,8,30]");
const auto parsed = LocationParser::parseLine(line);
assert(parsed.has_value());
assert(parsed->satellites == 5);
const QSet<QString> prns = LocationParser::parseGpsPrns(line);
assert(prns == QSet<QString>({QStringLiteral("G03"), QStringLiteral("G04"),
QStringLiteral("G06"), QStringLiteral("G09"),
QStringLiteral("G31")}));
}
{
const QSet<QString> prns = LocationParser::parseGpsPrns(
QStringLiteral("[TRK] tracked satellites=2 svn=[0,31]"));
assert(prns == QSet<QString>({QStringLiteral("G01"), QStringLiteral("G32")}));
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral(
"[OK] [NAV] DOP GDOP=7.0907891622 PDOP=5.6333862175 "
"HDOP=2.9819528958 VDOP=4.7794348204 TDOP=4.3063035967"));
assert(parsed.has_value());
assert(closeTo(*parsed->dop.gdop, 7.0907891622));
assert(closeTo(*parsed->dop.pdop, 5.6333862175));
assert(closeTo(*parsed->dop.hdop, 2.9819528958));
assert(closeTo(*parsed->dop.vdop, 4.7794348204));
assert(closeTo(*parsed->dop.tdop, 4.3063035967));
}
{
const auto parsed = LocationParser::parseLine(QStringLiteral(
"[OK] [NAV] position lat=40.77831900791127850425 "
"lon=29.43579105837637044374 height=198.80186179187148809433"));
assert(parsed.has_value());
assert(parsed->hasLocation());
assert(closeTo(*parsed->latitude, 40.77831900791127850425));
assert(closeTo(*parsed->longitude, 29.43579105837637044374));
assert(closeTo(*parsed->hheight, 198.80186179187148809433));
}
const QString rinexPath = qEnvironmentVariable("LOCATION_PLOTTER_TEST_RINEX");
if (!rinexPath.isEmpty()) {
QFile file(rinexPath);
assert(file.open(QIODevice::ReadOnly));
QString error;
const auto satellites = RinexNavigation::parseGpsNavigation(
file.readAll(), QDateTime::currentDateTimeUtc(), &error);
assert(satellites.size() >= 20);
for (const auto &satellite : satellites) {
assert(satellite.prn.startsWith(QStringLiteral("G")));
assert(std::isfinite(satellite.x));
assert(std::isfinite(satellite.y));
assert(std::isfinite(satellite.z));
}
}
return 0;
}