File indexing completed on 2025-03-09 03:50:39
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-09-19 0007 * Description : GPS data file parser. 0008 * (GPX format https://www.topografix.com/gpx.asp). 0009 * 0010 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "geodataparser.h" 0017 #include "geodataparser_time.h" 0018 0019 // C++ includes 0020 0021 #include <cmath> 0022 #include <cstdlib> 0023 0024 // Qt includes 0025 0026 #include <QDomDocument> 0027 #include <QFile> 0028 #include <QString> 0029 #include <QIODevice> 0030 0031 // Local includes 0032 0033 #include "digikam_debug.h" 0034 0035 namespace DigikamGenericGeolocationEditPlugin 0036 { 0037 0038 GeoDataParser::GeoDataParser() 0039 { 0040 clear(); 0041 } 0042 0043 void GeoDataParser::clear() 0044 { 0045 m_GeoDataMap.clear(); 0046 } 0047 0048 int GeoDataParser::numPoints() const 0049 { 0050 return m_GeoDataMap.count(); 0051 } 0052 0053 bool GeoDataParser::matchDate(const QDateTime& photoDateTime, int maxGapTime, int secondsOffset, 0054 bool offsetContainsTimeZone, 0055 bool interpolate, int interpolationDstTime, 0056 GeoDataContainer* const gpsData) 0057 { 0058 // GPS device are sync in time by satellite using GMT time. 0059 0060 QDateTime cameraGMTDateTime = photoDateTime.addSecs(secondsOffset*(-1)); 0061 0062 if (offsetContainsTimeZone) 0063 { 0064 cameraGMTDateTime.setTimeSpec(Qt::UTC); 0065 } 0066 0067 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << " photoDateTime: " << photoDateTime << photoDateTime.timeSpec(); 0068 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "cameraGMTDateTime: " << cameraGMTDateTime << cameraGMTDateTime.timeSpec(); 0069 0070 // We are trying to find the right date in the GPS points list. 0071 0072 bool findItem = false; 0073 int nbSecItem = maxGapTime; 0074 int nbSecs; 0075 0076 for (GeoDataMap::ConstIterator it = m_GeoDataMap.constBegin() ; it != m_GeoDataMap.constEnd() ; ++it ) 0077 { 0078 // Here we check a possible accuracy in seconds between the 0079 // Camera GMT time and the GPS device GMT time. 0080 0081 nbSecs = qAbs(cameraGMTDateTime.secsTo( it.key() )); 0082 /* 0083 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << it.key() << cameraGMTDateTime << nbSecs; 0084 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << it.key().timeSpec() << cameraGMTDateTime.timeSpec() << nbSecs; 0085 */ 0086 // We're trying to find the minimal accuracy. 0087 0088 if ((nbSecs < maxGapTime) && (nbSecs < nbSecItem)) 0089 { 0090 if (gpsData) 0091 { 0092 *gpsData = m_GeoDataMap[it.key()]; 0093 } 0094 0095 findItem = true; 0096 nbSecItem = nbSecs; 0097 } 0098 } 0099 0100 if (findItem) 0101 return true; 0102 0103 // If we can't find it, we will trying to interpolate the GPS point. 0104 0105 if (interpolate) 0106 { 0107 // The interpolate GPS point will be separate by at the maximum of 'interpolationDstTime' 0108 // seconds before and after the next and previous real GPS point found. 0109 0110 QDateTime prevDateTime = findPrevDate(cameraGMTDateTime, interpolationDstTime); 0111 QDateTime nextDateTime = findNextDate(cameraGMTDateTime, interpolationDstTime); 0112 0113 if (!nextDateTime.isNull() && !prevDateTime.isNull()) 0114 { 0115 GeoDataContainer prevGPSPoint = m_GeoDataMap[prevDateTime]; 0116 GeoDataContainer nextGPSPoint = m_GeoDataMap[nextDateTime]; 0117 0118 double alt1 = prevGPSPoint.altitude(); 0119 double lon1 = prevGPSPoint.longitude(); 0120 double lat1 = prevGPSPoint.latitude(); 0121 uint t1 = prevDateTime.toSecsSinceEpoch(); 0122 double alt2 = nextGPSPoint.altitude(); 0123 double lon2 = nextGPSPoint.longitude(); 0124 double lat2 = nextGPSPoint.latitude(); 0125 uint t2 = nextDateTime.toSecsSinceEpoch(); 0126 uint tCor = cameraGMTDateTime.toSecsSinceEpoch(); 0127 0128 if (tCor-t1 != 0) 0129 { 0130 if (gpsData) 0131 { 0132 gpsData->setAltitude(alt1 + (alt2-alt1) * (tCor-t1)/(t2-t1)); 0133 gpsData->setLatitude(lat1 + (lat2-lat1) * (tCor-t1)/(t2-t1)); 0134 gpsData->setLongitude(lon1 + (lon2-lon1) * (tCor-t1)/(t2-t1)); 0135 gpsData->setInterpolated(true); 0136 } 0137 0138 return true; 0139 } 0140 } 0141 } 0142 0143 return false; 0144 } 0145 0146 QDateTime GeoDataParser::findNextDate(const QDateTime& dateTime, int secs) 0147 { 0148 // We will find the item in GPS data list where the time is 0149 // at the maximum bigger than 'secs' mn of the value to match. 0150 0151 QDateTime itemFound = dateTime.addSecs(secs); 0152 bool found = false; 0153 0154 for (GeoDataMap::ConstIterator it = m_GeoDataMap.constBegin() ; it != m_GeoDataMap.constEnd() ; ++it) 0155 { 0156 if (it.key() > dateTime) 0157 { 0158 if (it.key() < itemFound) 0159 { 0160 itemFound = it.key(); 0161 found = true; 0162 } 0163 } 0164 } 0165 0166 if (found) 0167 { 0168 return itemFound; 0169 } 0170 0171 return QDateTime(); 0172 } 0173 0174 QDateTime GeoDataParser::findPrevDate(const QDateTime& dateTime, int secs) 0175 { 0176 // We will find the item in GPS data list where the time is 0177 // at the maximum smaller than 'secs' mn of the value to match. 0178 0179 QDateTime itemFound = dateTime.addSecs((-1)*secs); 0180 bool found = false; 0181 0182 for (GeoDataMap::ConstIterator it = m_GeoDataMap.constBegin() ; it != m_GeoDataMap.constEnd() ; ++it) 0183 { 0184 if (it.key() < dateTime) 0185 { 0186 if (it.key() > itemFound) 0187 { 0188 itemFound = it.key(); 0189 found = true; 0190 } 0191 } 0192 } 0193 0194 if (found) 0195 { 0196 return itemFound; 0197 } 0198 0199 return QDateTime(); 0200 } 0201 0202 bool GeoDataParser::loadGPXFile(const QUrl& url) 0203 { 0204 QFile gpxfile(url.toLocalFile()); 0205 0206 if (!gpxfile.open(QIODevice::ReadOnly)) 0207 { 0208 return false; 0209 } 0210 0211 QDomDocument gpxDoc(QLatin1String("gpx")); 0212 0213 if (!gpxDoc.setContent(&gpxfile)) 0214 { 0215 gpxfile.close(); 0216 return false; 0217 } 0218 0219 QDomElement gpxDocElem = gpxDoc.documentElement(); 0220 0221 if (gpxDocElem.tagName() != QLatin1String("gpx")) 0222 { 0223 gpxfile.close(); 0224 return false; 0225 } 0226 0227 for (QDomNode nTrk = gpxDocElem.firstChild(); !nTrk.isNull(); nTrk = nTrk.nextSibling()) 0228 { 0229 QDomElement trkElem = nTrk.toElement(); 0230 0231 if (trkElem.isNull()) 0232 continue; 0233 0234 if (trkElem.tagName() != QLatin1String("trk")) 0235 continue; 0236 0237 for (QDomNode nTrkseg = trkElem.firstChild() ; !nTrkseg.isNull() ; nTrkseg = nTrkseg.nextSibling()) 0238 { 0239 QDomElement trksegElem = nTrkseg.toElement(); 0240 0241 if (trksegElem.isNull()) 0242 continue; 0243 0244 if (trksegElem.tagName() != QLatin1String("trkseg")) 0245 continue; 0246 0247 for (QDomNode nTrkpt = trksegElem.firstChild() ; !nTrkpt.isNull() ; nTrkpt = nTrkpt.nextSibling()) 0248 { 0249 QDomElement trkptElem = nTrkpt.toElement(); 0250 0251 if (trkptElem.isNull()) 0252 continue; 0253 0254 if (trkptElem.tagName() != QLatin1String("trkpt")) 0255 continue; 0256 0257 QDateTime ptDateTime; 0258 double ptAltitude = 0.0; 0259 double ptLatitude = 0.0; 0260 double ptLongitude = 0.0; 0261 0262 // Get GPS position. If not available continue to next point. 0263 0264 QString lat = trkptElem.attribute(QLatin1String("lat")); 0265 QString lon = trkptElem.attribute(QLatin1String("lon")); 0266 0267 if (lat.isEmpty() || lon.isEmpty()) 0268 continue; 0269 0270 ptLatitude = lat.toDouble(); 0271 ptLongitude = lon.toDouble(); 0272 0273 // Get metadata of track point (altitude and time stamp) 0274 0275 for (QDomNode nTrkptMeta = trkptElem.firstChild() ; !nTrkptMeta.isNull() ; nTrkptMeta = nTrkptMeta.nextSibling()) 0276 { 0277 QDomElement trkptMetaElem = nTrkptMeta.toElement(); 0278 0279 if (trkptMetaElem.isNull()) 0280 continue; 0281 0282 if (trkptMetaElem.tagName() == QLatin1String("time")) 0283 { 0284 // Get GPS point time stamp. If not available continue to next point. 0285 0286 const QString time = trkptMetaElem.text(); 0287 0288 if (time.isEmpty()) 0289 continue; 0290 0291 ptDateTime = GeoDataParserParseTime(time); 0292 } 0293 0294 if (trkptMetaElem.tagName() == QLatin1String("ele")) 0295 { 0296 // Get GPS point altitude. If not available continue to next point. 0297 0298 QString ele = trkptMetaElem.text(); 0299 0300 if (!ele.isEmpty()) 0301 { 0302 ptAltitude = ele.toDouble(); 0303 } 0304 } 0305 } 0306 0307 if (ptDateTime.isNull()) 0308 { 0309 continue; 0310 } 0311 0312 GeoDataContainer gpsData(ptAltitude, ptLatitude, ptLongitude, false); 0313 m_GeoDataMap.insert( ptDateTime, gpsData ); 0314 } 0315 } 0316 } 0317 /* 0318 qCDebug(DIGIKAM_DPLUGIN_GENERIC_LOG) << "GPX File " << url.fileName() 0319 << " parsed with " << numPoints() 0320 << " points extracted" ; 0321 */ 0322 gpxfile.close(); 0323 0324 return true; 0325 } 0326 0327 } // namespace DigikamGenericGeolocationEditPlugin