// 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 "" 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: // -53.7 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 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