diff --git a/companion/src/helpers.cpp b/companion/src/helpers.cpp index a5d00d0a3..0ca4208f5 100644 --- a/companion/src/helpers.cpp +++ b/companion/src/helpers.cpp @@ -1060,3 +1060,55 @@ bool caseInsensitiveLessThan(const QString &s1, const QString &s2) { return s1.toLower() < s2.toLower(); } + +bool GpsGlitchFilter::isGlitch(double latitude, double longitude) +{ + if ((fabs(latitude) < 0.1) && (fabs(longitude) < 0.1)) { + return true; + } + + if (lastValid) { + if (fabs(latitude - lastLat) > 0.01) { + // qDebug() << "GpsGlitchFilter(): latitude glitch " << latitude << lastLat; + if ( ++glitchCount < 10) { + return true; + } + } + if (fabs(longitude - lastLon) > 0.01) { + // qDebug() << "GpsGlitchFilter(): longitude glitch " << longitude << lastLon; + if ( ++glitchCount < 10) { + return true; + } + } + } + lastLat = latitude; + lastLon = longitude; + lastValid = true; + glitchCount = 0; + return false; +} + +bool GpsLatLonFilter::isValid(const QString & latitude, const QString & longitude) +{ + if (lastLat == latitude) { + return false; + } + if (lastLon == longitude) { + return false; + } + lastLat = latitude; + lastLon = longitude; + return true; +} + +double toDecimalCoordinate(const QString & value) +{ + if (value.isEmpty()) return 0.0; + double temp = int(value.left(value.length()-1).toDouble() / 100); + double result = temp + (value.left(value.length() - 1).toDouble() - temp * 100) / 60.0; + QChar direction = value.at(value.size()-1); + if ((direction == 'S') || (direction == 'W')) { + result = -result; + } + return result; +} diff --git a/companion/src/helpers.h b/companion/src/helpers.h index aec01b20f..659c718a8 100644 --- a/companion/src/helpers.h +++ b/companion/src/helpers.h @@ -181,4 +181,31 @@ QSet getFilesSet(const QString &path, const QStringList &filter, int ma bool caseInsensitiveLessThan(const QString &s1, const QString &s2); + +class GpsGlitchFilter +{ +public: + GpsGlitchFilter() : lastValid(false), glitchCount(0) {}; + bool isGlitch(double latitude, double longitude); + +private: + bool lastValid; + int glitchCount; + double lastLat; + double lastLon; +}; + +class GpsLatLonFilter +{ +public: + GpsLatLonFilter() {}; + bool isValid(const QString & latitude, const QString & longitude); + +private: + QString lastLat; + QString lastLon; +}; + +double toDecimalCoordinate(const QString & value); + #endif // HELPERS_H diff --git a/companion/src/logsdialog.cpp b/companion/src/logsdialog.cpp index e6be94bfd..4cd94e5ad 100644 --- a/companion/src/logsdialog.cpp +++ b/companion/src/logsdialog.cpp @@ -221,38 +221,88 @@ void logsDialog::selectionChanged() } } -void logsDialog::on_mapsButton_clicked() +QList logsDialog::filterGePoints(const QList & input) { - int n = csvlog.count(); // number of points in graph - if (n==0) return; - int latcol=0, longcol=0, altcol=0, speedcol=0; - int itemSelected=0.; - bool rangeSelected=false; - ui->FieldsTW->setDisabled(true); - ui->logTable->setDisabled(true); + QList result; - QString gePath=g.gePath(); - if (gePath.isEmpty() || !QFile(gePath).exists()) { - ui->FieldsTW->setDisabled(false); - ui->logTable->setDisabled(false); - return; + int n = input.count(); + if (n == 0) { + return result; } - QSet nondataCols; - for (int i=1; ilogTable->selectionModel()->selectedRows().length() > 0; + + GpsGlitchFilter glitchFilter; + GpsLatLonFilter latLonFilter; + + for (int i = 1; i < n; i++) { + if ((ui->logTable->item(i-1, 1)->isSelected() && rangeSelected) || !rangeSelected) { + + QString latitude = input.at(i).at(latcol).trimmed(); + QString longitude = input.at(i).at(longcol).trimmed(); + double flatitude = toDecimalCoordinate(latitude); + double flongitude = toDecimalCoordinate(longitude); + + // glitch filter + if ( glitchFilter.isGlitch(flatitude, flongitude) ) { + qDebug() << "filterGePoints(): GPS glitch detected at" << i << flatitude << flongitude; + continue; + } + + // lat long pair filter + if ( !latLonFilter.isValid(latitude, longitude) ) { + // qDebug() << "filterGePoints(): Lat-Lon pair wrong, skipping at" << i << latitude << longitude; + continue; + } + + // qDebug() << "point " << latitude << longitude; + result.append(input.at(i)); + } + } + + qDebug() << "filterGePoints(): filtered from" << input.count() << "to " << result.count() << "points"; + return result; +} + +void logsDialog::exportToGoogleEarth() +{ + // filter data points + QList dataPoints = filterGePoints(csvlog); + int n = dataPoints.count(); // number of points to export + if (n==0) return; + + int latcol=0, longcol=0, altcol=0, speedcol=0; + QSet nondataCols; + for (int i=1; ilogTable->item(i-1,1)->isSelected()) { - rangeSelected=true; - itemSelected++; - } - } - if (itemSelected==0) { - itemSelected=n-1; - } QString geIconFilename = generateProcessUniqueTempFileName("track0.png"); if (QFile::exists(geIconFilename)) { @@ -286,25 +327,25 @@ void logsDialog::on_mapsButton_clicked() tr("Cannot write file %1:\n%2.") .arg(geFilename) .arg(geFile.errorString())); - ui->FieldsTW->setDisabled(false); - ui->logTable->setDisabled(false); return; } - QString latitude,longitude; - double flatitude, flongitude,temp; + QTextStream outputStream(&geFile); + + // file header outputStream << "\n\n"; outputStream << "\t\n\t\t" << logFilename << "\n"; outputStream << "\t\t\n"; - outputStream << "\t\t\n"; + outputStream << "\t\t\n"; outputStream << "\t\t\n\t\t\t\n\t\t\t\tnormal\n\t\t\t\t#multiTrack_n\n\t\t\t\n\t\t\t\n\t\t\t\thighlight\n\t\t\t\t#multiTrack_h\n\t\t\t\n\t\t\n"; outputStream << "\t\t\n"; outputStream << "\t\t\n"; outputStream << "\t\t\t\n\t\t\t\tGPS Speed\n\t\t\t\n"; + // declare additional fields - for (int i=0; iFieldsTW->item(0,i)->isSelected() && !nondataCols.contains(i+2)) { - QString origName = csvlog.at(0).at(i+2); + QString origName = dataPoints.at(0).at(i+2); QString safeName = origName; safeName.replace(" ","_"); outputStream << "\t\t\t\n\t\t\t\t" << origName << "\n\t\t\t\n"; } } - outputStream << "\t\t\n"; QString planeName; if (logFilename.indexOf("-")>0) { @@ -320,69 +360,73 @@ void logsDialog::on_mapsButton_clicked() } else { planeName=logFilename; } + + outputStream << "\t\t\n"; outputStream << "\t\t\n\t\t\tLog Data\n\t\t\t\n\t\t\t\t" << planeName << ""; outputStream << "\n\t\t\t\t#multiTrack"; outputStream << "\n\t\t\t\t\n"; outputStream << "\n\t\t\t\t\tabsolute\n"; + + // time data points for (int i=1; ilogTable->item(i-1,1)->isSelected() &&rangeSelected) || !rangeSelected) { - QString tstamp=csvlog.at(i).at(0)+QString("T")+csvlog.at(i).at(1)+QString("Z"); - outputStream << "\t\t\t\t\t"<< tstamp <<"\n"; - } + QString tstamp=dataPoints.at(i).at(0)+QString("T")+dataPoints.at(i).at(1)+QString("Z"); + outputStream << "\t\t\t\t\t"<< tstamp <<"\n"; } + // coordinate data points for (int i=1; ilogTable->item(i-1,1)->isSelected() &&rangeSelected) || !rangeSelected) { - latitude=csvlog.at(i).at(latcol).trimmed(); - longitude=csvlog.at(i).at(longcol).trimmed(); - temp=int(latitude.left(latitude.length()-1).toDouble()/100); - flatitude=temp+(latitude.left(latitude.length()-1).toDouble()-temp*100)/60.0; - temp=int(longitude.left(longitude.length()-1).toDouble()/100); - flongitude=temp+(longitude.left(longitude.length()-1).toDouble()-temp*100)/60.0; - if (latitude.right(1)!="N") { - flatitude*=-1; - } - if (longitude.right(1)!="E") { - flongitude*=-1; - } - latitude.sprintf("%3.8f", flatitude); - longitude.sprintf("%3.8f", flongitude); - outputStream << "\t\t\t\t\t" << longitude << " " << latitude << " " << csvlog.at(i).at(altcol).toFloat() << " \n" ; - } + QString latitude=dataPoints.at(i).at(latcol).trimmed(); + QString longitude=dataPoints.at(i).at(longcol).trimmed(); + latitude.sprintf("%3.8f", toDecimalCoordinate(latitude)); + longitude.sprintf("%3.8f", toDecimalCoordinate(longitude)); + outputStream << "\t\t\t\t\t" << longitude << " " << latitude << " " << dataPoints.at(i).at(altcol).toFloat() << " \n" ; } + + // additional data for data points outputStream << "\t\t\t\t\t\n\t\t\t\t\t\t\n"; + + // gps speed data points outputStream << "\t\t\t\t\t\t\t\n"; for (int i=1; ilogTable->item(i-1,1)->isSelected() &&rangeSelected) || !rangeSelected) { - outputStream << "\t\t\t\t\t\t\t\t"<< csvlog.at(i).at(speedcol) <<"\n"; - } + outputStream << "\t\t\t\t\t\t\t\t"<< dataPoints.at(i).at(speedcol) <<"\n"; } outputStream << "\t\t\t\t\t\t\t\n"; + // add values for additional fields - for (int i=0; iFieldsTW->item(0,i)->isSelected() && !nondataCols.contains(i+2)) { - QString safeName = csvlog.at(0).at(i+2);; + QString safeName = dataPoints.at(0).at(i+2);; safeName.replace(" ","_"); outputStream << "\t\t\t\t\t\t\t\n"; for (int j=1; jlogTable->item(j-1,1)->isSelected() &&rangeSelected) || !rangeSelected) { - outputStream << "\t\t\t\t\t\t\t\t"<< csvlog.at(j).at(i+2) <<"\n"; - } + outputStream << "\t\t\t\t\t\t\t\t"<< dataPoints.at(j).at(i+2) <<"\n"; } outputStream << "\t\t\t\t\t\t\t\n"; } } + outputStream << "\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\n\t\t\n\t\n"; geFile.close(); + + QString gePath = g.gePath(); QStringList parameters; - #ifdef __APPLE__ +#ifdef __APPLE__ parameters << "-a"; parameters << gePath; gePath = "/usr/bin/open"; - #endif +#endif parameters << geFilename; QProcess *process = new QProcess(this); process->start(gePath, parameters); +} + +void logsDialog::on_mapsButton_clicked() +{ + ui->FieldsTW->setDisabled(true); + ui->logTable->setDisabled(true); + + exportToGoogleEarth(); + ui->FieldsTW->setDisabled(false); ui->logTable->setDisabled(false); } diff --git a/companion/src/logsdialog.h b/companion/src/logsdialog.h index 062b8736d..f528cec7e 100644 --- a/companion/src/logsdialog.h +++ b/companion/src/logsdialog.h @@ -67,7 +67,6 @@ private: Ui::logsDialog *ui; QCPAxisRect *axisRect; QCPLegend *rightLegend; - bool cvsFileParse(); bool plotLock; QString logFilename; @@ -76,6 +75,11 @@ private: double yAxesRatios[AXES_LIMIT]; minMax yAxesRanges[AXES_LIMIT]; + + bool cvsFileParse(); + QList filterGePoints(const QList & input); + void exportToGoogleEarth(); + }; #endif // LOGSDIALOG_H