diff options
Diffstat (limited to 'desktop-widgets/importgps.cpp')
-rw-r--r-- | desktop-widgets/importgps.cpp | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/desktop-widgets/importgps.cpp b/desktop-widgets/importgps.cpp new file mode 100644 index 000000000..5e4845951 --- /dev/null +++ b/desktop-widgets/importgps.cpp @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "desktop-widgets/importgps.h" + +/* Import dive coordinates from a GPS device and synchronise them with the dive profile information + of a dive computer. This file contains the infrastructure to: + 1) Read a .GPX file from a GPS system. + 2) Find the first gpx trackpoint that follows after the start of the dive. + 3) Allow modification of the coordinates dependent on international time zone and + on differences in local time settings between the GPS and the dive computer. + 4) Saving the coordinates into the Coordinates text box in the Dive Site Edit panel + and which which causes the map to show the location of the dive site. + The structure coords is used to store critical information. */ +ImportGPS::ImportGPS(QWidget *parent, QString fileName, class Ui::LocationInformation *LocationUI) : QDialog(parent), + fileName(fileName), LocationUI(LocationUI) +{ + ui.setupUi(this); + connect(ui.timeDiffEdit, &QTimeEdit::timeChanged, this, &ImportGPS::timeDiffEditChanged); + connect(ui.timeZoneEdit, &QTimeEdit::timeChanged, this, &ImportGPS::timeZoneEditChanged); + connect(ui.timezone_backwards, &QRadioButton::toggled, this, &ImportGPS::changeZoneBackwards); + connect(ui.timezone_forward, &QRadioButton::toggled, this, &ImportGPS::changeZoneForward); + connect(ui.diff_backwards, &QRadioButton::toggled, this, &ImportGPS::changeDiffBackwards); + connect(ui.diff_forward, &QRadioButton::toggled, this, &ImportGPS::changeDiffForward); + connect(ui.GPSbuttonBox, &QDialogButtonBox::clicked, this, &ImportGPS::buttonClicked); + coords.settingsDiff_offset = 0; + coords.timeZone_offset = 0; + coords.lat = 0; + coords.lon = 0; + pixmapSize = (int) (ui.diveDateLabel->height() / 2); +} + +void ImportGPS::buttonClicked(QAbstractButton *button) +{ + if (ui.GPSbuttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole) { + // Write the coordinates in decimal degree format to the Coordinates QLineEdit of the LocationaInformationWidget UI: + LocationUI->diveSiteCoordinates->setText(QString::number(coords.lat) + ", " + QString::number(coords.lon)); + LocationUI->diveSiteCoordinates->editingFinished(); + } else { + close(); + } +} + +// Read text from the present position in the file until +// the first 'delim' character is encountered. +int ImportGPS::getSubstring(QFile *file, QString *bufptr, char delim) +{ + char c; + bufptr->clear(); + do { + if (file->read(&c, 1) <= 0) // EOF + return 1; + if (c == delim) break; + bufptr->append(QChar(c)); + } while (c != delim); + return 0; +} + +// Find the next occurence of a specified target GPX element in the file, +// characerised by a "<xxx " or "<xxx>" character sequence. +// 'target' specifies the name of the element searched for. +// termc is the ending character of the element name search = '>' or ' '. +int ImportGPS::findXmlElement(QFile *fileptr, QString target, QString *bufptr, char termc) +{ + bool match = false; + char c; + char skipdelim = (termc == ' ') ? '>' : ' '; + do { // Skip input until first start new of element (<) is encountered: + if (getSubstring(fileptr, bufptr, '<')) + return 1; // EOF + bufptr->clear(); + do { // Read name of following element and store it in buf + if (fileptr->read(&c, 1) <= 0) // EOF encountered + return 1; + if ((c == '>') || (c == ' ')) // found a valid delimiter + break; + bufptr->append(QChar(c)); + } while ((c != '>') && (c != ' ')); + if (*bufptr == "/trk") // End of GPS track found: return EOF + return 1; + if (c == skipdelim) + continue; // if inappropriate delimiter was found, redo from start + if (*bufptr == target) // Compare xml element name from gpx file with the + match = true; // the target element searched for. + } while (match == false); + return 0; +} + +// Find the coordinates at the time specified in coords.start_dive +// by searching the gpx file "fileName". Here is a typical trkpt element in GPX: +// <trkpt lat="-26.84" lon="32.88"><ele>-53.7</ele><time>2017-08-06T04:56:42Z</time></trkpt> +int ImportGPS::getCoordsFromFile() +{ + struct tm tm1; + time_t when = 0; + double lon, lat; + int line = 0; + int64_t time_offset = coords.settingsDiff_offset + coords.timeZone_offset; + time_t divetime; + bool first_line = true; + bool found = false; + divetime = coords.start_dive; + QString buf; + QFile f1; + f1.setFileName(fileName); + if (!f1.open(QIODevice::ReadOnly | QIODevice::Text)) { + QByteArray local8bitBAString1 = fileName.toLocal8Bit(); + char *fname = local8bitBAString1.data(); // convert QString to a C string fileName + fprintf(stderr, "GPS file open error: file name = %s\n", fname); + return 1; + } + +#ifdef GPSDEBUG + struct tm time; // decode the time of start of dive: + utc_mkdate(divetime, &time); + int dyr,dmon,dday,dhr,dmin; + dyr = time.tm_year; + dmon = time.tm_mon; + dday = time.tm_mday; + dhr = time.tm_hour; + dmin = time.tm_min; +#endif + + do { + line++; // this is the sequence number of the trkpt xml element processed + // Find next trkpt xml element (This function also detects </trk> that signals EOF): + if (findXmlElement(&f1, QString("trkpt"), &buf, ' ')) // This is the normal exit point + break; // for this routine + // == Get coordinates: == + if (getSubstring(&f1, &buf, '"')) // read up to the end of the "lat=" label + break; // on EOF + if (buf != "lat=") { + fprintf(stderr, "GPX parse error: cannot find latitude (trkpt #%d)\n", line); + return 1; + } + if (getSubstring(&f1, &buf, '"')) // read up to the end of the latitude value + break; // on EOF + lat = buf.toDouble(); // Convert lat to decimal + if (getSubstring(&f1, &buf, ' ')) // Read past space char + break; // on EOF + if (getSubstring(&f1, &buf, '"')) // Read up to end of "lon=" label + break; // on EOF + if (buf != "lon=") { + fprintf(stderr, "GPX parse error: cannot find longitude (trkpt #%d)\n", line); + return 1; + } + if (getSubstring(&f1, &buf, '"')) // get string with longitude + break; // on EOF + lon = buf.toDouble(); // Convert longitude to decimal + // == get time: == + if (findXmlElement(&f1, QString("time"), &buf, '>')) // Find the <time> element + break; // on EOF + if (getSubstring(&f1, &buf, '<')) // Read the string containing date/time + break; // on EOF + bool ok; + tm1.tm_year = buf.left(4).toInt(&ok, 10); // Extract the different substrings: + tm1.tm_mon = buf.mid(5,2).toInt(&ok,10) - 1; + tm1.tm_mday = buf.mid(8,2).toInt(&ok,10); + tm1.tm_hour = buf.mid(11,2).toInt(&ok,10); + tm1.tm_min = buf.mid(14,2).toInt(&ok,10); + tm1.tm_sec = buf.mid(17,2).toInt(&ok,10); + when = utc_mktime(&tm1) + time_offset; + if (first_line) { + first_line = false; + coords.start_track = when; // Local time of start of GPS track + } + if ((when > divetime && found == false)) { // This GPS local time corresponds to the start time of the dive + coords.lon = lon; // save the coordinates + coords.lat = lat; + found = true; + } +#ifdef GPSDEBUG + utc_mkdate(when, &time); // print time and coordinates of each of the trkpt elements of the GPX file + fprintf(stderr, " %02d: lat=%f lon=%f timestamp=%ld (%ld) %02d/%02d/%02d %02d:%02d dt=%ld %02d/%02d/%02d %02d:%02d\n", line, lat, + lon, when, time_offset, time.tm_year, time.tm_mon+1, time.tm_mday, time.tm_hour, time.tm_min, divetime, dyr, dmon+1, dday,dhr, dmin ); +#endif + } while (true); // This loop executes until EOF causes a break out of the loop + coords.end_track = when; // This is the local time of the end of the GPS track + f1.close(); + return 0; +} + +// Fill the visual elements of the synchronisation panel with information +void ImportGPS::updateUI() +{ + struct tm time; + int dive_day, gps_day; + char datestr[50]; + QString problemString = ""; + + utc_mkdate(coords.start_track, &time); // Display GPS date and local start and end times of track: + gps_day = time.tm_mday; + datestr[0] = 0x0; + strftime(datestr, sizeof(datestr), "%A %d %B ", &time); // GPS date + ui.trackDateLabel->setText("GPS date = " + QString(datestr) + QString::number(time.tm_year)); + ui.startTimeLabel->setText(QString::number(time.tm_hour) + ":" + QString("%1").arg(time.tm_min, 2, 10, QChar('0'))); // track start time + utc_mkdate(coords.end_track, &time); + ui.endTimeLabel->setText(QString::number(time.tm_hour) + ":" + QString("%1").arg(time.tm_min, 2, 10, QChar('0'))); // track end time + + utc_mkdate(coords.start_dive, &time); // Display dive date and start and end times of dive: + dive_day = time.tm_mday; + datestr[0] = 0x0; + strftime(datestr, sizeof(datestr), "%A %d %B ", localtime(&(coords.start_dive))); // dive date + ui.diveDateLabel->setText("Dive date = " + QString(datestr) + QString::number(time.tm_year)); + ui.startTimeSyncLabel->setText( QString::number(time.tm_hour) + ":" + QString("%1").arg(time.tm_min, 2, 10, QChar('0'))); // dive start time + utc_mkdate(coords.end_dive, &time); + ui.endTimeSyncLabel->setText(QString::number(time.tm_hour) + ":" + QString("%1").arg(time.tm_min, 2, 10, QChar('0'))); // dive end time + + // This section implements extensive warnings in case there is not synchronisation between dive and GPS data: + + if (gps_day != dive_day) + problemString = "(different dates)"; + // Create 3 icons to indicate the quality of the synchrinisation between dive and GPS + QPixmap goodResultIcon (":gps_good_result-icon"); + ui.goodIconLabel->setPixmap(goodResultIcon.scaled(pixmapSize,pixmapSize,Qt::KeepAspectRatio)); + ui.goodIconLabel->setVisible(false); + + QPixmap warningResultIcon (":gps_warning_result-icon"); + ui.warningIconLabel->setPixmap(warningResultIcon.scaled(pixmapSize,pixmapSize,Qt::KeepAspectRatio)); + ui.warningIconLabel->setVisible(false); + + QPixmap badResultIcon (":gps_bad_result-icon"); + ui.badIconLabel->setPixmap(badResultIcon.scaled(pixmapSize,pixmapSize,Qt::KeepAspectRatio)); + ui.badIconLabel->setVisible(false); + // Show information or warning message as well as synch quality icon + if (coords.start_dive < coords.start_track) { + ui.resultLabel->setStyleSheet("QLabel { color: red;} "); + ui.resultLabel->setText("PROBLEM: Dive started before the GPS track "+ problemString); + ui.badIconLabel->setVisible(true); + } else { + if (coords.start_dive > coords.end_track) { + ui.resultLabel->setStyleSheet("QLabel { color: red;} "); + ui.resultLabel->setText("PROBLEM: Dive started after the GPS track " + problemString); + ui.badIconLabel->setVisible(true); + } else { + if (coords.end_dive > coords.end_track) { + ui.resultLabel->setStyleSheet("QLabel { color: red;} "); + ui.resultLabel->setText("WARNING: Dive ended after the GPS track " + problemString); + ui.warningIconLabel->setVisible(true); + } else { + ui.resultLabel->setStyleSheet("QLabel { color: darkgreen;} "); + ui.resultLabel->setText("Dive coordinates: "+ QString::number(coords.lat) + "S, " + QString::number(coords.lon) + "E"); + ui.goodIconLabel->setVisible(true); + } + } + } +} + +void ImportGPS::changeZoneForward() +{ + coords.timeZone_offset = abs(coords.timeZone_offset); + getCoordsFromFile(); // If any of the time controls are changed + updateUI(); // .. then recalculate the synchronisation +} + +void ImportGPS::changeZoneBackwards() +{ + if (coords.timeZone_offset > 0) + coords.timeZone_offset = 0 - coords.timeZone_offset; + getCoordsFromFile(); + updateUI(); +} + +void ImportGPS::changeDiffForward() +{ + coords.settingsDiff_offset = abs(coords.settingsDiff_offset); + getCoordsFromFile(); + updateUI(); +} + +void ImportGPS::changeDiffBackwards() +{ + if (coords.settingsDiff_offset > 0) + coords.settingsDiff_offset = 0 - coords.settingsDiff_offset; + getCoordsFromFile(); + updateUI(); +} + +void ImportGPS::timeDiffEditChanged() +{ + coords.settingsDiff_offset = ui.timeDiffEdit->time().hour() * 3600 + ui.timeDiffEdit->time().minute() * 60; + if (ui.diff_backwards->isChecked()) + coords.settingsDiff_offset = 0 - coords.settingsDiff_offset; + getCoordsFromFile(); + updateUI(); +} + +void ImportGPS::timeZoneEditChanged() +{ + coords.timeZone_offset = ui.timeZoneEdit->time().hour() * 3600; + if (ui.timezone_backwards->isChecked()) + coords.timeZone_offset = 0 - coords.timeZone_offset; + getCoordsFromFile(); + updateUI(); +} + |