diff --git a/README.md b/README.md index 663bbb2..f291b0e 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ VDOP : 2.2370935468 TDOP : 2.2526165852 tracking PRNs: G03, G08, 14, 21 SV=G27 +[LOCATION] source=gps lat=40.7357 lon=31.5985 height=812.0 gdop=4.7 pdop=4.1 hdop=2.7 vdop=3.0 tdop=2.3 tracked=6 used=5 +[LOCATION] source=imu_corrected lat=40.7356 lon=31.5985 height=812.7 gdop=4.7 pdop=4.1 hdop=2.7 vdop=3.0 tdop=2.3 tracked=6 used=5 +[LOCATION] source=trajectory lat=40.7358 lon=31.5987 height=812.1 tracked=4 used=4 lattitude : 40.78925034214206846173 longitude : 29.46277530841727809729 height : -89.05779508221894502640 @@ -38,6 +41,9 @@ Accepted location labels: `activeSats`, `satellites_active`, `num_sats`, `uydu` - DOP: `GDOP`, `PDOP`, `HDOP`, `VDOP`, `TDOP` - GPS identifiers: `G03`, `PRN: 3`, `PRNs: 3, 8, 14`, `SV=G21`, `SVID: 27` +- Location source: `source=gps`, `source=imu_corrected`, `source=trajectory` +- `tracked` is accepted as the active satellite count; `used` remains visible in + the raw UART line and denotes satellites used by the WLS solution. If a satellite count arrives without a location, the active satellite display is updated. If a location arrives without a satellite count, the latest known satellite count is attached @@ -47,6 +53,48 @@ section and are attached to the next plotted point. If latitude, longitude, and height arrive as separate consecutive lines, they are combined into a single plotted point. +The GPS firmware's level-1 paired protocol is source-aware. Every received +`source=gps` location is added to the blue GPS series and every +`source=imu_corrected` location is immediately added to the red corrected series. +Paths are built independently, so alternating UART lines do not create GPS-to-IMU +zigzags. When corrected points exist, CEP50, average and scatter calculations use +only that corrected series; legacy input without a source keeps the previous behavior. +Synthetic `source=trajectory` points form a third independent green ground-truth +path. The map legend identifies all three series. + +## Sentetik ESKF senaryolarini açma + +GPS deposundan 48 kombinasyonun replay loglarini üretin: + +```bash +cd /home/onur/Desktop/gps +make -C soc/software/fusion/tests scenario-logs +``` + +Sonra GUI'yi açıp `Senaryo Logu Ac` düğmesinden örneğin +`soc/software/fusion/tests/output/scenarios/car_gps200_imu20.log` dosyasını seçin. +GUI logdaki bütün rotayı otomatik sığdırır. Aynı dosya komut satırından da +doğrudan açılabilir: + +```bash +cd /home/onur/Desktop/location_plotter +cmake -S . -B build +cmake --build build -j +./build/location_plotter --replay \ + /home/onur/Desktop/gps/soc/software/fusion/tests/output/scenarios/car_gps200_imu20.log +``` + +Tek bir özel kombinasyon üretmek için: + +```bash +cd /home/onur/Desktop/gps/soc/software/fusion/tests +make platform_scenario_matrix_test +./platform_scenario_matrix_test --log aircraft 500 19 aircraft.log +``` + +Desteklenen senaryolar `stationary`, `pedestrian`, `car`, `aircraft`; GNSS +periyotları 200/500/1000 ms, IMU periyotları 19/20/200/300 ms'dir. + ## Ubuntu'da kurulum Ubuntu 22.04 ve daha yeni surumlerde gerekli paketleri kurmak, uygulamayi derlemek ve @@ -136,6 +184,9 @@ without logging out. marker can be toggled on the map. - The map layers panel can hide all telemetry overlays, show only the newest location instead of the recent path, and independently toggle the average marker. +- GPS points and paths are blue. IMU+ESKF corrected points and their independent + path are red. Synthetic ground-truth trajectory is green; the locations table + shows the parsed source in its `Kaynak` column. - The application calculates empirical CEP50 as the median horizontal distance of the retained measurements from their spherical mean, then draws the circle around that measurement center. This is the radius containing 50 percent of the retained position fixes and is independent of diff --git a/src/LocationParser.cpp b/src/LocationParser.cpp index f4520fa..bb6621a 100644 --- a/src/LocationParser.cpp +++ b/src/LocationParser.cpp @@ -49,6 +49,10 @@ QString normalizeKey(QString key) return "satellites"; } + if (key == "tracked") { + return "satellites"; + } + if (key == "gdop" || key == "pdop" || key == "hdop" || key == "vdop" || key == "tdop") { return key; } @@ -76,7 +80,7 @@ std::optional LocationParser::parseLine(const QString &line) } static const QRegularExpression labeledValueRegex( - QStringLiteral(R"((^|[^A-Za-z0-9_])(?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*)(?[+-]?(?:\d+(?:[\.,]\d+)?|[\.,]\d+)(?:[eE][+-]?\d+)?))"), + QStringLiteral(R"((^|[^A-Za-z0-9_])(?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|tracked|uydu(?:lar)?|[gphvt][_ -]?dop)(?:\s*(?:=|:|->)?\s*)(?[+-]?(?:\d+(?:[\.,]\d+)?|[\.,]\d+)(?:[eE][+-]?\d+)?))"), QRegularExpression::CaseInsensitiveOption); static const QRegularExpression satelliteCountPhraseRegex( QStringLiteral(R"((^|[^A-Za-z0-9_\.])(?\d+)\s+(?:active\s+)?(?:sat(?:ellite)?s?|uydu(?:lar)?)(?=$|[\s,.;)\]]))"), @@ -84,6 +88,21 @@ std::optional LocationParser::parseLine(const QString &line) ParsedTelemetry telemetry; telemetry.rawLine = trimmed; + static const QRegularExpression sourceRegex( + QStringLiteral(R"((?:^|\s)source\s*=\s*(?gps|imu[_-]?corrected|trajectory|ground[_-]?truth)(?=$|\s))"), + QRegularExpression::CaseInsensitiveOption); + const QRegularExpressionMatch sourceMatch = sourceRegex.match(trimmed); + if (sourceMatch.hasMatch()) { + const QString source = sourceMatch.captured(QStringLiteral("source")); + if (source.startsWith(QStringLiteral("imu"), Qt::CaseInsensitive)) { + telemetry.source = LocationSource::ImuCorrected; + } else if (source.startsWith(QStringLiteral("trajectory"), Qt::CaseInsensitive) + || source.startsWith(QStringLiteral("ground"), Qt::CaseInsensitive)) { + telemetry.source = LocationSource::Trajectory; + } else { + telemetry.source = LocationSource::Gps; + } + } bool hadCoordinateField = false; bool hadLatitudeField = false; bool hadLongitudeField = false; diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index f404068..f7b9cf1 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include #include #include @@ -155,6 +157,17 @@ LocationAnalysis analyzeLocations(const QVector &points) return analysis; } +QVector preferredAnalysisPoints(const QVector &points) +{ + QVector corrected; + for (const TelemetryPoint &point : points) { + if (point.source == LocationSource::ImuCorrected) { + corrected.push_back(point); + } + } + return corrected.isEmpty() ? points : corrected; +} + QString metersText(double meters) { if (!std::isfinite(meters)) { @@ -255,7 +268,7 @@ public: int columnCount(const QModelIndex &parent = QModelIndex()) const override { - return parent.isValid() ? 0 : 6; + return parent.isValid() ? 0 : 7; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override @@ -275,14 +288,16 @@ public: case 0: return point.receivedAt.toLocalTime().toString(QStringLiteral("HH:mm:ss.zzz")); case 1: - return coordinateText(point.latitude, point.latitudeText); + return locationSourceText(point.source); case 2: - return coordinateText(point.longitude, point.longitudeText); + return coordinateText(point.latitude, point.latitudeText); case 3: - return optionalDoubleText(point.hheight, 2); + return coordinateText(point.longitude, point.longitudeText); case 4: - return optionalIntText(point.satellites); + return optionalDoubleText(point.hheight, 2); case 5: + return optionalIntText(point.satellites); + case 6: return optionalDopText(point.dop.pdop); default: return {}; @@ -296,7 +311,7 @@ public: return {}; } static const QStringList headers = { - QStringLiteral("Saat"), QStringLiteral("Lat"), QStringLiteral("Long"), + QStringLiteral("Saat"), QStringLiteral("Kaynak"), QStringLiteral("Lat"), QStringLiteral("Long"), QStringLiteral("HHeight"), QStringLiteral("Uydu"), QStringLiteral("PDOP")}; return section >= 0 && section < headers.size() ? headers[section] : QVariant(); } @@ -366,6 +381,50 @@ void MainWindow::clearPoints() resetTelemetryState(); } +bool MainWindow::loadTelemetryLog(const QString &filePath) +{ + QFile file(filePath); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + const QString message = QStringLiteral("Log acilamadi: %1").arg(file.errorString()); + m_statusLabel->setText(message); + statusBar()->showMessage(message, 8000); + return false; + } + const QList lines = file.readAll().split('\n'); + resetTelemetryState(); + const int replayCapacity = static_cast(std::min(10000, lines.size())); + m_maxPointsSpin->setValue(std::max(10, replayCapacity)); + int parsedLocations = 0; + for (const QByteArray &rawLine : lines) { + const QString line = QString::fromUtf8(rawLine).trimmed(); + if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) { + continue; + } + const auto telemetry = LocationParser::parseLine(line); + if (telemetry.has_value() && telemetry->hasLocation()) { + ++parsedLocations; + } + handleSerialLine(line); + } + flushUiUpdates(); + m_map->fitToPoints(); + const QString message = QStringLiteral("Replay yuklendi: %1 konum - %2") + .arg(parsedLocations).arg(filePath); + m_statusLabel->setText(message); + statusBar()->showMessage(message, 8000); + return parsedLocations > 0; +} + +void MainWindow::openTelemetryLog() +{ + const QString filePath = QFileDialog::getOpenFileName( + this, QStringLiteral("Sentetik telemetri logunu ac"), QString(), + QStringLiteral("Telemetry logs (*.log *.txt);;All files (*)")); + if (!filePath.isEmpty()) { + loadTelemetryLog(filePath); + } +} + void MainWindow::handleSerialLine(const QString &line) { static const QRegularExpression solveStartRegex( @@ -698,6 +757,9 @@ void MainWindow::buildUi() m_connectButton->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); m_clearButton = new QPushButton(QStringLiteral("Temizle"), this); m_clearButton->setIcon(style()->standardIcon(QStyle::SP_TrashIcon)); + m_openLogButton = new QPushButton(QStringLiteral("Senaryo Logu Ac"), this); + m_openLogButton->setObjectName(QStringLiteral("openLogButton")); + m_openLogButton->setIcon(style()->standardIcon(QStyle::SP_DialogOpenButton)); m_autoFollowCheck = new QCheckBox(QStringLiteral("Kamerayi takip et"), this); m_autoFollowCheck->setChecked(true); @@ -716,6 +778,7 @@ void MainWindow::buildUi() toolbarLayout->addWidget(new QLabel(QStringLiteral("Max"), this)); toolbarLayout->addWidget(m_maxPointsSpin); toolbarLayout->addWidget(m_connectButton); + toolbarLayout->addWidget(m_openLogButton); toolbarLayout->addWidget(m_clearButton); toolbarLayout->addWidget(m_autoFollowCheck); toolbarLayout->addWidget(zoomOutButton); @@ -932,6 +995,7 @@ void MainWindow::buildUi() connect(m_connectButton, &QPushButton::clicked, this, &MainWindow::toggleSerial); connect(m_clearButton, &QPushButton::clicked, this, &MainWindow::clearPoints); + connect(m_openLogButton, &QPushButton::clicked, this, &MainWindow::openTelemetryLog); connect(m_maxPointsSpin, qOverload(&QSpinBox::valueChanged), this, &MainWindow::trimToMaxPoints); connect(m_autoFollowCheck, &QCheckBox::toggled, m_map, &MapWidget::setAutoFollow); @@ -974,6 +1038,7 @@ void MainWindow::updateConnectionControls() void MainWindow::addTelemetryPoint(const ParsedTelemetry &telemetry) { TelemetryPoint point; + point.source = telemetry.source; point.latitude = *telemetry.latitude; point.longitude = *telemetry.longitude; point.latitudeText = telemetry.latitudeText; @@ -1028,7 +1093,8 @@ void MainWindow::updateDopLabels() void MainWindow::updateAnalysis() { - const LocationAnalysis analysis = analyzeLocations(m_points); + const QVector analysisPoints = preferredAnalysisPoints(m_points); + const LocationAnalysis analysis = analyzeLocations(analysisPoints); if (!analysis.valid) { m_cepLabel->setText(QStringLiteral("-")); m_meanLocationLabel->setText(QStringLiteral("-")); @@ -1051,11 +1117,11 @@ void MainWindow::updateAnalysis() m_map->setAverageMarker(analysis.averageLat, analysis.averageLon, analysis.rmsScatterMeters); - const double cep50 = calculateCep50(m_points, analysis.averageLat, + const double cep50 = calculateCep50(analysisPoints, analysis.averageLat, analysis.averageLon); m_cepLabel->setText(QStringLiteral("%1 (P50, N=%2)") .arg(metersText(cep50)) - .arg(m_points.size())); + .arg(analysisPoints.size())); m_map->setCepMarker(analysis.averageLat, analysis.averageLon, cep50); } @@ -1078,6 +1144,7 @@ void MainWindow::mergePendingTelemetry(const ParsedTelemetry &telemetry) } if (telemetry.latitude.has_value()) { + m_pendingTelemetry.source = telemetry.source; m_pendingTelemetry.latitude = telemetry.latitude; m_pendingTelemetry.latitudeText = telemetry.latitudeText; } diff --git a/src/MainWindow.h b/src/MainWindow.h index 3b79f33..fceceee 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -30,6 +30,7 @@ class MainWindow : public QMainWindow { public: explicit MainWindow(QWidget *parent = nullptr); + bool loadTelemetryLog(const QString &filePath); private slots: void toggleSerial(); @@ -41,6 +42,7 @@ private slots: void trimToMaxPoints(); void updateReferenceLocation(); void flushUiUpdates(); + void openTelemetryLog(); private: void buildUi(); @@ -70,6 +72,7 @@ private: QLineEdit *m_referenceLonEdit = nullptr; QPushButton *m_connectButton = nullptr; QPushButton *m_clearButton = nullptr; + QPushButton *m_openLogButton = nullptr; QCheckBox *m_autoFollowCheck = nullptr; QCheckBox *m_showTelemetryCheck = nullptr; QCheckBox *m_showRecentPointsCheck = nullptr; diff --git a/src/MapWidget.cpp b/src/MapWidget.cpp index f68e159..8df3fb4 100644 --- a/src/MapWidget.cpp +++ b/src/MapWidget.cpp @@ -108,6 +108,41 @@ void MapWidget::clearPoints() update(); } +void MapWidget::fitToPoints() +{ + if (m_points.isEmpty()) { + return; + } + double minimumLatitude = m_points.first().latitude; + double maximumLatitude = minimumLatitude; + double minimumLongitude = m_points.first().longitude; + double maximumLongitude = minimumLongitude; + for (const TelemetryPoint &point : m_points) { + minimumLatitude = std::min(minimumLatitude, point.latitude); + maximumLatitude = std::max(maximumLatitude, point.latitude); + minimumLongitude = std::min(minimumLongitude, point.longitude); + maximumLongitude = std::max(maximumLongitude, point.longitude); + } + m_centerLat = 0.5 * (minimumLatitude + maximumLatitude); + m_centerLon = 0.5 * (minimumLongitude + maximumLongitude); + const double availableWidth = std::max(100.0, width() * 0.82); + const double availableHeight = std::max(100.0, height() * 0.82); + for (int zoom = MaxZoom; zoom >= MinZoom; --zoom) { + const QPointF topLeft = coordinateToWorld(maximumLatitude, minimumLongitude, zoom); + const QPointF bottomRight = coordinateToWorld(minimumLatitude, maximumLongitude, zoom); + if (std::fabs(bottomRight.x() - topLeft.x()) <= availableWidth + && std::fabs(bottomRight.y() - topLeft.y()) <= availableHeight) { + m_zoom = zoom; + break; + } + } + if (m_autoFollow) { + m_autoFollow = false; + emit autoFollowChanged(false); + } + update(); +} + void MapWidget::setAverageMarker(double latitude, double longitude, double radiusMeters) { m_hasAverageMarker = true; @@ -216,6 +251,7 @@ void MapWidget::paintEvent(QPaintEvent *) painter.fillRect(rect(), QColor(219, 225, 220)); drawTiles(painter); drawTelemetry(painter); + drawLegend(painter); if (m_points.isEmpty()) { drawEmptyState(painter); @@ -387,23 +423,31 @@ void MapWidget::drawTelemetry(QPainter &painter) drawCepOverlay(painter); - QPainterPath path; - for (int i = 0; i < m_points.size(); ++i) { - const QPointF screen = coordinateToScreen(m_points[i].latitude, m_points[i].longitude); - if (i == 0) { - path.moveTo(screen); - } else { - path.lineTo(screen); + const auto drawSourcePath = [this, &painter](LocationSource source, const QColor &color) { + QPainterPath path; + bool havePoint = false; + int pointCount = 0; + for (const TelemetryPoint &point : m_points) { + if (point.source != source) { + continue; + } + const QPointF screen = coordinateToScreen(point.latitude, point.longitude); + havePoint ? path.lineTo(screen) : path.moveTo(screen); + havePoint = true; + ++pointCount; } - } - - if (m_showRecentPoints && m_points.size() > 1) { - QPen pathPen(QColor(210, 105, 30), 3.0); - pathPen.setCapStyle(Qt::RoundCap); - pathPen.setJoinStyle(Qt::RoundJoin); - painter.setPen(pathPen); - painter.drawPath(path); - } + if (m_showRecentPoints && pointCount > 1) { + QPen pathPen(color, 3.0); + pathPen.setCapStyle(Qt::RoundCap); + pathPen.setJoinStyle(Qt::RoundJoin); + painter.setPen(pathPen); + painter.drawPath(path); + } + }; + drawSourcePath(LocationSource::Trajectory, QColor(22, 163, 74)); + drawSourcePath(LocationSource::Generic, QColor(210, 105, 30)); + drawSourcePath(LocationSource::Gps, QColor(37, 99, 235)); + drawSourcePath(LocationSource::ImuCorrected, QColor(220, 38, 38)); drawAverageOverlay(painter); drawCepMarker(painter); @@ -415,8 +459,15 @@ void MapWidget::drawTelemetry(QPainter &painter) const QPointF screen = coordinateToScreen(m_points[i].latitude, m_points[i].longitude); const double radius = isLast ? 8.0 : 5.0; + const QColor pointColor = m_points[i].source == LocationSource::ImuCorrected + ? QColor(220, 38, 38) + : (m_points[i].source == LocationSource::Gps + ? QColor(37, 99, 235) + : (m_points[i].source == LocationSource::Trajectory + ? QColor(22, 163, 74) + : (isLast ? QColor(210, 105, 30) : QColor(234, 88, 12)))); painter.setPen(QPen(Qt::white, isLast ? 3.0 : 2.0)); - painter.setBrush(isLast ? QColor(31, 150, 82) : QColor(37, 99, 235)); + painter.setBrush(pointColor); painter.drawEllipse(screen, radius, radius); if (drawNumbers) { @@ -436,6 +487,7 @@ void MapWidget::drawTelemetry(QPainter &painter) ? QString::number(last.longitude, 'f', 15) : last.longitudeText; lines << QStringLiteral("Lat: %1").arg(latitudeText); lines << QStringLiteral("Long: %1").arg(longitudeText); + lines << QStringLiteral("Kaynak: %1").arg(locationSourceText(last.source)); if (last.hheight.has_value()) { lines << QStringLiteral("HHeight: %1").arg(*last.hheight, 0, 'f', 2); } @@ -466,6 +518,37 @@ void MapWidget::drawTelemetry(QPainter &painter) painter.drawText(bubble.adjusted(9, 7, -9, -7), Qt::AlignLeft | Qt::AlignVCenter, label); } +void MapWidget::drawLegend(QPainter &painter) +{ + if (m_points.isEmpty() || !m_showTelemetryOverlay) { + return; + } + const struct { + const char *text; + QColor color; + } entries[] = { + {"Trajectory (truth)", QColor(22, 163, 74)}, + {"GPS (4 satellite)", QColor(37, 99, 235)}, + {"IMU + ESKF corrected", QColor(220, 38, 38)} + }; + const QRectF box(12.0, 12.0, 190.0, 76.0); + painter.setPen(QPen(QColor(50, 58, 65, 180), 1.0)); + painter.setBrush(QColor(255, 255, 255, 232)); + painter.drawRoundedRect(box, 6.0, 6.0); + painter.setFont(QFont(painter.font().family(), 9, QFont::DemiBold)); + for (int index = 0; index < 3; ++index) { + const double y = 27.0 + index * 22.0; + painter.setPen(QPen(entries[index].color, 3.0)); + painter.drawLine(QPointF(23.0, y), QPointF(43.0, y)); + painter.setBrush(entries[index].color); + painter.drawEllipse(QPointF(33.0, y), 4.0, 4.0); + painter.setPen(QColor(25, 32, 38)); + painter.drawText(QRectF(50.0, y - 9.0, 142.0, 18.0), + Qt::AlignLeft | Qt::AlignVCenter, + QString::fromLatin1(entries[index].text)); + } +} + void MapWidget::drawCepOverlay(QPainter &painter) { if (!m_hasCepMarker || !m_cepRadiusMeters.has_value() || *m_cepRadiusMeters <= 0.0) { diff --git a/src/MapWidget.h b/src/MapWidget.h index d479835..213ba1a 100644 --- a/src/MapWidget.h +++ b/src/MapWidget.h @@ -20,6 +20,7 @@ public: explicit MapWidget(QWidget *parent = nullptr); void setPoints(const QVector &points); + void fitToPoints(); void clearPoints(); void setAverageMarker(double latitude, double longitude, double radiusMeters); void clearAverageMarker(); @@ -56,6 +57,7 @@ private: void drawTiles(QPainter &painter); void drawTelemetry(QPainter &painter); + void drawLegend(QPainter &painter); void drawAverageOverlay(QPainter &painter); void drawCepOverlay(QPainter &painter); void drawCepMarker(QPainter &painter); diff --git a/src/TelemetryPoint.h b/src/TelemetryPoint.h index f573309..437683f 100644 --- a/src/TelemetryPoint.h +++ b/src/TelemetryPoint.h @@ -5,6 +5,28 @@ #include +enum class LocationSource { + Generic, + Gps, + ImuCorrected, + Trajectory +}; + +inline QString locationSourceText(LocationSource source) +{ + switch (source) { + case LocationSource::Gps: + return QStringLiteral("GPS"); + case LocationSource::ImuCorrected: + return QStringLiteral("IMU corrected"); + case LocationSource::Trajectory: + return QStringLiteral("Trajectory (truth)"); + case LocationSource::Generic: + default: + return QStringLiteral("Generic"); + } +} + struct DopValues { std::optional gdop; std::optional pdop; @@ -20,6 +42,7 @@ struct DopValues { }; struct TelemetryPoint { + LocationSource source = LocationSource::Generic; double latitude = 0.0; double longitude = 0.0; QString latitudeText; @@ -32,6 +55,7 @@ struct TelemetryPoint { }; struct ParsedTelemetry { + LocationSource source = LocationSource::Generic; std::optional latitude; std::optional longitude; QString latitudeText; diff --git a/src/main.cpp b/src/main.cpp index b7dcfa6..85a1ce1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,7 @@ #include "MainWindow.h" #include +#include int main(int argc, char *argv[]) { @@ -9,6 +10,9 @@ int main(int argc, char *argv[]) QApplication::setOrganizationName(QStringLiteral("rocket")); MainWindow window; + if (argc == 3 && QString::fromLocal8Bit(argv[1]) == QStringLiteral("--replay")) { + window.loadTelemetryLog(QFileInfo(QString::fromLocal8Bit(argv[2])).absoluteFilePath()); + } window.show(); return QApplication::exec(); diff --git a/tests/main_window_tests.cpp b/tests/main_window_tests.cpp index 2c07e2e..b90cad2 100644 --- a/tests/main_window_tests.cpp +++ b/tests/main_window_tests.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -99,11 +100,48 @@ int main(int argc, char **argv) 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("40.78925034214206846173")); + assert(table->model()->data(table->model()->index(0, 3)).toString() == QStringLiteral("29.46277530841727809729")); + sendLine(window, QStringLiteral( + "[LOCATION] source=gps lat=40.1 lon=29.1 height=100 gdop=2 pdop=1.5 " + "hdop=1 vdop=1.2 tdop=0.8 tracked=5 used=5")); + sendLine(window, QStringLiteral( + "[LOCATION] source=imu_corrected lat=40.10001 lon=29.10001 height=100.2 " + "gdop=2 pdop=1.5 hdop=1 vdop=1.2 tdop=0.8 tracked=5 used=5")); + flushUpdates(window); + assert(table->model()->rowCount() == 3); + assert(table->model()->data(table->model()->index(1, 1)).toString() + == QStringLiteral("GPS")); + assert(table->model()->data(table->model()->index(2, 1)).toString() + == QStringLiteral("IMU corrected")); + sendLine(window, QStringLiteral( + "[LOCATION] source=trajectory lat=40.10002 lon=29.10002 height=100 tracked=4 used=4")); + flushUpdates(window); + assert(table->model()->rowCount() == 4); + assert(table->model()->data(table->model()->index(3, 1)).toString() + == QStringLiteral("Trajectory (truth)")); + + QTemporaryFile replayLog; + assert(replayLog.open()); + replayLog.write( + "[LOCATION] source=trajectory lat=40.0 lon=29.0 height=100 tracked=4 used=4\n" + "[LOCATION] source=gps lat=40.0001 lon=29.0001 height=104 tracked=4 used=4\n" + "[LOCATION] source=imu_corrected lat=40.00002 lon=29.00002 height=101 tracked=4 used=4\n"); + replayLog.flush(); + assert(window.loadTelemetryLog(replayLog.fileName())); + assert(table->model()->rowCount() == 3); + assert(table->model()->data(table->model()->index(0, 1)).toString() + == QStringLiteral("Trajectory (truth)")); + + assert(QMetaObject::invokeMethod(&window, "clearPoints", Qt::DirectConnection)); + sendLine(window, QStringLiteral( + "lat=40.78925034214206846173 long=29.46277530841727809729")); + flushUpdates(window); + assert(table->model()->rowCount() == 1); + referenceLatInput->setText(QStringLiteral("40.78925034214206846173")); referenceLonInput->setText(QStringLiteral("29.46277530841727809729")); const bool referenceUpdated = QMetaObject::invokeMethod( diff --git a/tests/parser_tests.cpp b/tests/parser_tests.cpp index fddc221..1b701af 100644 --- a/tests/parser_tests.cpp +++ b/tests/parser_tests.cpp @@ -179,6 +179,34 @@ int main(int argc, char **argv) assert(closeTo(*parsed->hheight, 198.80186179187148809433)); } + { + const auto parsed = LocationParser::parseLine(QStringLiteral( + "[LOCATION] source=imu_corrected lat=40.7356 lon=31.5985 height=812.7 " + "gdop=4.7 pdop=4.1 hdop=2.7 vdop=3.0 tdop=2.3 tracked=6 used=5")); + assert(parsed.has_value()); + assert(parsed->hasLocation()); + assert(parsed->source == LocationSource::ImuCorrected); + assert(parsed->satellites == 6); + assert(closeTo(*parsed->dop.hdop, 2.7)); + } + + { + const auto parsed = LocationParser::parseLine(QStringLiteral( + "[LOCATION] source=gps lat=40.7357 lon=31.5986 height=811 tracked=5 used=5")); + assert(parsed.has_value()); + assert(parsed->source == LocationSource::Gps); + assert(parsed->satellites == 5); + } + + { + const auto parsed = LocationParser::parseLine(QStringLiteral( + "[LOCATION] source=trajectory lat=40.7358 lon=31.5987 height=810 tracked=4 used=4")); + assert(parsed.has_value()); + assert(parsed->hasLocation()); + assert(parsed->source == LocationSource::Trajectory); + assert(parsed->satellites == 4); + } + const QString rinexPath = qEnvironmentVariable("LOCATION_PLOTTER_TEST_RINEX"); if (!rinexPath.isEmpty()) { QFile file(rinexPath);