File indexing completed on 2025-01-05 03:58:35
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-09-19 0007 * Description : Track file reader 0008 * 0009 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2010-2014 by Michael G. Hansen <mike at mghansen dot de> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "trackreader.h" 0017 0018 // Qt includes 0019 0020 #include <QFile> 0021 #include <QXmlStreamAttributes> 0022 0023 // KDE includes 0024 0025 #include <klocalizedstring.h> 0026 0027 namespace Digikam 0028 { 0029 0030 class Q_DECL_HIDDEN TrackReader::Private 0031 { 0032 public: 0033 0034 explicit Private() 0035 : fileData (nullptr) 0036 { 0037 } 0038 0039 TrackReadResult* fileData; 0040 }; 0041 0042 TrackReader::TrackReader(TrackReadResult* const dataTarget) 0043 : QObject(nullptr), 0044 d (new Private) 0045 { 0046 d->fileData = dataTarget; 0047 } 0048 0049 TrackReader::~TrackReader() 0050 { 0051 } 0052 0053 QDateTime TrackReader::ParseTime(const QString& tstring) 0054 { 0055 QString timeString = tstring; 0056 0057 if (timeString.isEmpty()) 0058 { 0059 return QDateTime(); 0060 } 0061 0062 // we want to be able to parse these formats: 0063 // "2010-01-14T09:26:02.287-02:00" <-- here we have to cut off the -02:00 and replace it with 'Z' 0064 // "2010-01-14T09:26:02.287+02:00" <-- here we have to cut off the +02:00 and replace it with 'Z' 0065 // "2009-03-11T13:39:55.622Z" 0066 0067 const int timeStringLength = timeString.length(); 0068 const int timeZoneSignPosition = timeStringLength-6; 0069 0070 // does the string contain a timezone offset? 0071 0072 int timeZoneOffsetSeconds = 0; 0073 const int timeZonePlusPosition = timeString.lastIndexOf(QLatin1Char('+')); 0074 const int timeZoneMinusPosition = timeString.lastIndexOf(QLatin1Char('-')); 0075 0076 if ((timeZonePlusPosition == timeZoneSignPosition) || 0077 (timeZoneMinusPosition == timeZoneSignPosition)) 0078 { 0079 const int timeZoneSign = (timeZonePlusPosition == timeZoneSignPosition) ? +1 : -1; 0080 0081 // cut off the last digits: 0082 0083 const QString timeZoneString = timeString.right(6); 0084 timeString.chop(6); 0085 timeString += QLatin1Char('Z'); 0086 0087 // determine the time zone offset: 0088 0089 bool okayHour = false; 0090 bool okayMinute = false; 0091 const int hourOffset = timeZoneString.mid(1, 2).toInt(&okayHour); 0092 const int minuteOffset = timeZoneString.mid(4, 2).toInt(&okayMinute); 0093 0094 if (okayHour&&okayMinute) 0095 { 0096 timeZoneOffsetSeconds = hourOffset*3600 + minuteOffset*60; 0097 timeZoneOffsetSeconds *= timeZoneSign; 0098 } 0099 } 0100 0101 QDateTime theTime = QDateTime::fromString(timeString, Qt::ISODate); 0102 theTime = theTime.addSecs(-timeZoneOffsetSeconds); 0103 0104 return theTime; 0105 } 0106 0107 void TrackReader::parseTrack(QXmlStreamReader& xml) 0108 { 0109 /* check that really getting a track. */ 0110 0111 if ((xml.tokenType() != QXmlStreamReader::StartElement) && 0112 (xml.name() == QLatin1String("trkpt"))) 0113 { 0114 return; 0115 } 0116 0117 TrackManager::TrackPoint currentDataPoint; 0118 0119 /* get first the attributes for track points */ 0120 0121 QXmlStreamAttributes attributes = xml.attributes(); 0122 0123 /* check that track has lat or lon attribute. */ 0124 0125 if (attributes.hasAttribute(QLatin1String("lat")) && 0126 attributes.hasAttribute(QLatin1String("lon"))) 0127 { 0128 currentDataPoint.coordinates.setLatLon(attributes.value(QLatin1String("lat")).toDouble(), 0129 attributes.value(QLatin1String("lon")).toDouble()); 0130 } 0131 0132 /* Next element... */ 0133 0134 xml.readNext(); 0135 0136 while (!((xml.tokenType() == QXmlStreamReader::EndElement) && 0137 (xml.name() == QLatin1String("trkpt"))) && !xml.hasError()) 0138 { 0139 if (xml.tokenType() == QXmlStreamReader::StartElement) 0140 { 0141 QString eText = xml.readElementText(QXmlStreamReader::IncludeChildElements); 0142 0143 if (xml.name() == QLatin1String("time")) 0144 { 0145 currentDataPoint.dateTime = ParseTime(eText.trimmed()); 0146 } 0147 else if (xml.name() == QLatin1String("sat")) 0148 { 0149 bool okay = false; 0150 int nSatellites = eText.toInt(&okay); 0151 0152 if (okay && (nSatellites >= 0)) 0153 { 0154 currentDataPoint.nSatellites = nSatellites; 0155 } 0156 } 0157 else if (xml.name() == QLatin1String("hdop")) 0158 { 0159 bool okay = false; 0160 qreal hDop = eText.toDouble(&okay); 0161 0162 if (okay) 0163 { 0164 currentDataPoint.hDop = hDop; 0165 } 0166 } 0167 else if (xml.name() == QLatin1String("pdop")) 0168 { 0169 bool okay = false; 0170 qreal pDop = eText.toDouble(&okay); 0171 0172 if (okay) 0173 { 0174 currentDataPoint.pDop = pDop; 0175 } 0176 } 0177 else if (xml.name() == QLatin1String("fix")) 0178 { 0179 if (eText == QLatin1String("2d")) 0180 { 0181 currentDataPoint.fixType = 2; 0182 } 0183 else if (eText == QLatin1String("3d")) 0184 { 0185 currentDataPoint.fixType = 3; 0186 } 0187 } 0188 else if (xml.name() == QLatin1String("ele")) 0189 { 0190 bool haveAltitude = false; 0191 const qreal alt = eText.toDouble(&haveAltitude); 0192 0193 if (haveAltitude) 0194 { 0195 currentDataPoint.coordinates.setAlt(alt); 0196 } 0197 } 0198 else if (xml.name() == QLatin1String("speed")) 0199 { 0200 bool haveSpeed = false; 0201 const qreal speed = eText.toDouble(&haveSpeed); 0202 0203 if (haveSpeed) 0204 { 0205 currentDataPoint.speed = speed; 0206 } 0207 } 0208 } 0209 0210 /* ...and next... */ 0211 0212 xml.readNext(); 0213 } 0214 0215 if (currentDataPoint.dateTime.isValid() && currentDataPoint.coordinates.hasCoordinates()) 0216 { 0217 d->fileData->track.points << currentDataPoint; 0218 } 0219 } 0220 0221 TrackReader::TrackReadResult TrackReader::loadTrackFile(const QUrl& url) 0222 { 0223 // TODO: store some kind of error message 0224 0225 TrackReadResult parsedData; 0226 TrackReader trackReader(&parsedData); 0227 parsedData.track.url = url; 0228 parsedData.isValid = false; 0229 0230 QFile file(url.toLocalFile()); 0231 0232 if (!file.open(QFile::ReadOnly | QFile::Text)) 0233 { 0234 parsedData.loadError = i18n("Could not open: %1", file.errorString()); 0235 0236 return parsedData; 0237 } 0238 0239 if (file.size() == 0) 0240 { 0241 parsedData.loadError = i18n("File is empty."); 0242 0243 return parsedData; 0244 } 0245 0246 QXmlStreamReader XmlReader(&file); 0247 0248 while (!XmlReader.atEnd() && !XmlReader.hasError() ) 0249 { 0250 QXmlStreamReader::TokenType token = XmlReader.readNext(); 0251 0252 if (token == QXmlStreamReader::StartElement) 0253 { 0254 if (XmlReader.name().toString() != QLatin1String("trkpt")) 0255 { 0256 continue; 0257 } 0258 else 0259 { 0260 trackReader.parseTrack(XmlReader); 0261 } 0262 } 0263 } 0264 0265 // error not well formed XML file 0266 0267 parsedData.isValid = !XmlReader.hasError() ; 0268 0269 if (!parsedData.isValid) 0270 { 0271 if (XmlReader.error() == QXmlStreamReader::NotWellFormedError) 0272 { 0273 parsedData.loadError = i18n("Probably not a GPX file."); 0274 } 0275 0276 return parsedData; 0277 } 0278 0279 // error no track points 0280 0281 parsedData.isValid = !parsedData.track.points.isEmpty(); 0282 0283 if (!parsedData.isValid) 0284 { 0285 parsedData.loadError = i18n("File is a GPX file, but no track points " 0286 "with valid timestamps were found."); 0287 0288 return parsedData; 0289 } 0290 0291 // The correlation algorithm relies on sorted data, therefore sort now 0292 0293 std::sort(parsedData.track.points.begin(), 0294 parsedData.track.points.end(), 0295 TrackManager::TrackPoint::EarlierThan); 0296 0297 return parsedData; 0298 } 0299 0300 } // namespace Digikam 0301 0302 #include "moc_trackreader.cpp"