File indexing completed on 2025-01-05 03:56:28
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-09-15 0007 * Description : Exiv2 library interface. 0008 * GPS manipulation methods 0009 * 0010 * SPDX-FileCopyrightText: 2006-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0011 * SPDX-FileCopyrightText: 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 0012 * SPDX-FileCopyrightText: 2010-2012 by Michael G. Hansen <mike at mghansen dot de> 0013 * 0014 * SPDX-License-Identifier: GPL-2.0-or-later 0015 * 0016 * ============================================================ */ 0017 0018 #include "metaengine_p.h" 0019 0020 // C ANSI includes 0021 0022 #include <math.h> 0023 0024 // C++ includes 0025 0026 #include <climits> 0027 #include <cmath> 0028 0029 // Local includes 0030 0031 #include "digikam_debug.h" 0032 #include "geodetictools.h" 0033 0034 #if defined(Q_CC_CLANG) 0035 # pragma clang diagnostic push 0036 # pragma clang diagnostic ignored "-Wdeprecated-declarations" 0037 #endif 0038 0039 namespace Digikam 0040 { 0041 0042 bool MetaEngine::getGPSInfo(double& altitude, double& latitude, double& longitude) const 0043 { 0044 // Some GPS device do not set Altitude. So a valid GPS position can be with a zero value. 0045 // No need to check return value. 0046 0047 getGPSAltitude(&altitude); 0048 0049 if (!getGPSLatitudeNumber(&latitude)) 0050 { 0051 return false; 0052 } 0053 0054 if (!getGPSLongitudeNumber(&longitude)) 0055 { 0056 return false; 0057 } 0058 0059 return true; 0060 } 0061 0062 bool MetaEngine::getGPSLatitudeNumber(double* const latitude) const 0063 { 0064 *latitude = 0.0; 0065 0066 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image. 0067 0068 if (convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLatitude"), latitude)) 0069 { 0070 return true; 0071 } 0072 0073 // Now try to get the reference from Exif. 0074 0075 const QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef"); 0076 0077 if (!latRef.isEmpty() && d->decodeGPSCoordinate("Exif.GPSInfo.GPSLatitude", latitude)) 0078 { 0079 if (latRef[0] == 'S') 0080 { 0081 *latitude *= -1.0; 0082 } 0083 0084 if ((*latitude < -90.0) || (*latitude > 90.0)) 0085 { 0086 return false; 0087 } 0088 0089 return true; 0090 } 0091 0092 return false; 0093 } 0094 0095 bool MetaEngine::getGPSLongitudeNumber(double* const longitude) const 0096 { 0097 *longitude = 0.0; 0098 0099 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image. 0100 0101 if (convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLongitude"), longitude)) 0102 { 0103 return true; 0104 } 0105 0106 // Now try to get the reference from Exif. 0107 0108 const QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef"); 0109 0110 if (!lngRef.isEmpty() && d->decodeGPSCoordinate("Exif.GPSInfo.GPSLongitude", longitude)) 0111 { 0112 if (lngRef[0] == 'W') 0113 { 0114 *longitude *= -1.0; 0115 } 0116 0117 if ((*longitude < -180.0) || (*longitude > 180.0)) 0118 { 0119 return false; 0120 } 0121 0122 return true; 0123 } 0124 0125 return false; 0126 } 0127 0128 bool MetaEngine::getGPSAltitude(double* const altitude) const 0129 { 0130 QMutexLocker lock(&s_metaEngineMutex); 0131 0132 try 0133 { 0134 double num, den; 0135 *altitude = 0.0; 0136 0137 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image. 0138 0139 QString altRefXmp = getXmpTagString("Xmp.exif.GPSAltitudeRef"); 0140 0141 if (altRefXmp.isEmpty()) 0142 { 0143 altRefXmp = QLatin1String("0"); 0144 } 0145 0146 const QString altXmp = getXmpTagString("Xmp.exif.GPSAltitude"); 0147 0148 if (!altXmp.isEmpty()) 0149 { 0150 num = altXmp.section(QLatin1Char('/'), 0, 0).toDouble(); 0151 den = altXmp.section(QLatin1Char('/'), 1, 1).toDouble(); 0152 0153 if (den == 0.0) 0154 { 0155 return false; 0156 } 0157 0158 *altitude = num / den; 0159 0160 if (altRefXmp == QLatin1String("1")) 0161 { 0162 *altitude *= -1.0; 0163 } 0164 0165 return true; 0166 } 0167 0168 // Get the reference from Exif (above/below sea level) 0169 0170 QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef"); 0171 0172 if (altRef.isEmpty()) 0173 { 0174 altRef = "0"; 0175 } 0176 0177 // Altitude decoding from Exif. 0178 0179 Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude"); 0180 Exiv2::ExifData exifData(d->exifMetadata()); 0181 Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey3); 0182 0183 if (it != exifData.end() && (*it).count()) 0184 { 0185 num = (double)((*it).toRational(0).first); 0186 den = (double)((*it).toRational(0).second); 0187 0188 if (den == 0.0) 0189 { 0190 return false; 0191 } 0192 0193 *altitude = num / den; 0194 0195 if (altRef[0] == '1') 0196 { 0197 *altitude *= -1.0; 0198 } 0199 } 0200 else 0201 { 0202 return false; 0203 } 0204 0205 return true; 0206 } 0207 catch (Exiv2::AnyError& e) 0208 { 0209 d->printExiv2ExceptionError(QLatin1String("Cannot get GPS tag with Exiv2:"), e); 0210 } 0211 catch (...) 0212 { 0213 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0214 } 0215 0216 return false; 0217 } 0218 0219 QString MetaEngine::getGPSLatitudeString() const 0220 { 0221 double latitude; 0222 0223 if (!getGPSLatitudeNumber(&latitude)) 0224 { 0225 return QString(); 0226 } 0227 0228 return convertToGPSCoordinateString(true, latitude); 0229 } 0230 0231 QString MetaEngine::getGPSLongitudeString() const 0232 { 0233 double longitude; 0234 0235 if (!getGPSLongitudeNumber(&longitude)) 0236 { 0237 return QString(); 0238 } 0239 0240 return convertToGPSCoordinateString(false, longitude); 0241 } 0242 0243 bool MetaEngine::initializeGPSInfo() 0244 { 0245 QMutexLocker lock(&s_metaEngineMutex); 0246 0247 try 0248 { 0249 // TODO: what happens if these already exist? 0250 0251 // Do all the easy constant ones first. 0252 // GPSVersionID tag: standard says is should be four bytes: 02 00 00 00 0253 // (and, must be present). 0254 0255 Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte); 0256 value->read("2 0 0 0"); 0257 d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get()); 0258 0259 // Datum: the datum of the measured data. If not given, we insert WGS-84. 0260 0261 d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84"; 0262 0263 #ifdef _XMP_SUPPORT_ 0264 0265 setXmpTagString("Xmp.exif.GPSVersionID", QLatin1String("2.0.0.0")); 0266 setXmpTagString("Xmp.exif.GPSMapDatum", QLatin1String("WGS-84")); 0267 0268 #endif // _XMP_SUPPORT_ 0269 0270 return true; 0271 } 0272 catch (Exiv2::AnyError& e) 0273 { 0274 d->printExiv2ExceptionError(QLatin1String("Cannot initialize GPS data with Exiv2:"), e); 0275 } 0276 catch (...) 0277 { 0278 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0279 } 0280 0281 return false; 0282 } 0283 0284 bool MetaEngine::setGPSInfo(const double altitude, const double latitude, const double longitude) 0285 { 0286 return setGPSInfo(&altitude, latitude, longitude); 0287 } 0288 0289 bool MetaEngine::setGPSInfo(const double* const altitude, const double latitude, const double longitude) 0290 { 0291 QMutexLocker lock(&s_metaEngineMutex); 0292 0293 try 0294 { 0295 // In first, we need to clean up all existing GPS info 0296 // if the coordinate deviation is greater than 100 m. 0297 0298 double lat = 0.0; 0299 double lon = 0.0; 0300 0301 if (getGPSLatitudeNumber(&lat) && getGPSLongitudeNumber(&lon)) 0302 { 0303 GeodeticCalculator calc; 0304 calc.setStartingGeographicPoint(lon, lat); 0305 calc.setDestinationGeographicPoint(longitude, latitude); 0306 0307 if (calc.orthodromicDistance() > 100.0) 0308 { 0309 removeGPSInfo(); 0310 } 0311 } 0312 else 0313 { 0314 removeGPSInfo(); 0315 } 0316 0317 // now re-initialize the GPS info: 0318 0319 if (!initializeGPSInfo()) 0320 { 0321 return false; 0322 } 0323 0324 char scratchBuf[100]; 0325 long int nom, denom; 0326 long int deg, min; 0327 0328 // Now start adding data. 0329 0330 // ALTITUDE. 0331 0332 if (altitude) 0333 { 0334 // Altitude reference: byte "00" meaning "above sea level", "01" meaning "behind sea level". 0335 0336 Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte); 0337 0338 if ((*altitude) >= 0) value->read("0"); 0339 else value->read("1"); 0340 0341 d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get()); 0342 0343 // And the actual altitude, as absolute value.. 0344 0345 convertToRational(fabs(*altitude), &nom, &denom, 4); 0346 snprintf(scratchBuf, 100, "%ld/%ld", nom, denom); 0347 d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf; 0348 0349 #ifdef _XMP_SUPPORT_ 0350 0351 setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QLatin1String("0") : QLatin1String("1")); 0352 setXmpTagString("Xmp.exif.GPSAltitude", QLatin1String(scratchBuf)); 0353 0354 #endif // _XMP_SUPPORT_ 0355 0356 } 0357 0358 // LATITUDE 0359 // Latitude reference: 0360 // latitude < 0 : "S" 0361 // latitude > 0 : "N" 0362 0363 d->exifMetadata()["Exif.GPSInfo.GPSLatitudeRef"] = (latitude < 0 ) ? "S" : "N"; 0364 0365 // Now the actual latitude itself. 0366 // This is done as three rationals. 0367 // I choose to do it as: 0368 // dd/1 - degrees. 0369 // mmmm/100 - minutes 0370 // 0/1 - seconds 0371 // Exif standard says you can do it with minutes 0372 // as mm/1 and then seconds as ss/1, but its 0373 // (slightly) more accurate to do it as 0374 // mmmm/100 than to split it. 0375 // We also absolute the value (with fabs()) 0376 // as the sign is encoded in LatRef. 0377 // Further note: original code did not translate between 0378 // dd.dddddd to dd mm.mm - that's why we now multiply 0379 // by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000. 0380 0381 deg = (int)floor(fabs(latitude)); // Slice off after decimal. 0382 min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000); 0383 snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min); 0384 d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf; 0385 0386 #ifdef _XMP_SUPPORT_ 0387 0388 /** 0389 * NOTE: The XMP spec does not mention Xmp.exif.GPSLatitudeRef, 0390 * because the reference is included in Xmp.exif.GPSLatitude. 0391 * See bug #450982. 0392 */ 0393 0394 setXmpTagString("Xmp.exif.GPSLatitude", convertToGPSCoordinateString(true, latitude)); 0395 0396 #endif // _XMP_SUPPORT_ 0397 0398 // LONGITUDE 0399 // Longitude reference: 0400 // longitude < 0 : "W" 0401 // longitude > 0 : "E" 0402 0403 d->exifMetadata()["Exif.GPSInfo.GPSLongitudeRef"] = (longitude < 0 ) ? "W" : "E"; 0404 0405 // Now the actual longitude itself. 0406 // This is done as three rationals. 0407 // I choose to do it as: 0408 // dd/1 - degrees. 0409 // mmmm/100 - minutes 0410 // 0/1 - seconds 0411 // Exif standard says you can do it with minutes 0412 // as mm/1 and then seconds as ss/1, but its 0413 // (slightly) more accurate to do it as 0414 // mmmm/100 than to split it. 0415 // We also absolute the value (with fabs()) 0416 // as the sign is encoded in LongRef. 0417 // Further note: original code did not translate between 0418 // dd.dddddd to dd mm.mm - that's why we now multiply 0419 // by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000. 0420 0421 deg = (int)floor(fabs(longitude)); // Slice off after decimal. 0422 min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000); 0423 snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min); 0424 d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf; 0425 0426 #ifdef _XMP_SUPPORT_ 0427 0428 /** 0429 * NOTE: The XMP spec does not mention Xmp.exif.GPSLongitudeRef, 0430 * because the reference is included in Xmp.exif.GPSLongitude. 0431 * See bug #450982. 0432 */ 0433 0434 setXmpTagString("Xmp.exif.GPSLongitude", convertToGPSCoordinateString(false, longitude)); 0435 0436 #endif // _XMP_SUPPORT_ 0437 0438 return true; 0439 } 0440 catch (Exiv2::AnyError& e) 0441 { 0442 d->printExiv2ExceptionError(QLatin1String("Cannot set Exif GPS tag with Exiv2:"), e); 0443 } 0444 catch (...) 0445 { 0446 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0447 } 0448 0449 return false; 0450 } 0451 0452 bool MetaEngine::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude) 0453 { 0454 double longitudeValue, latitudeValue; 0455 0456 if (!convertFromGPSCoordinateString(latitude, &latitudeValue)) 0457 { 0458 return false; 0459 } 0460 0461 if (!convertFromGPSCoordinateString(longitude, &longitudeValue)) 0462 { 0463 return false; 0464 } 0465 0466 return setGPSInfo(&altitude, latitudeValue, longitudeValue); 0467 } 0468 0469 bool MetaEngine::removeGPSInfo() 0470 { 0471 QMutexLocker lock(&s_metaEngineMutex); 0472 0473 try 0474 { 0475 QStringList gpsTagsKeys; 0476 0477 for (Exiv2::ExifData::const_iterator it = d->exifMetadata().begin() ; 0478 it != d->exifMetadata().end() ; ++it) 0479 { 0480 QString key = QString::fromStdString(it->key()); 0481 0482 if (key.section(QLatin1Char('.'), 1, 1) == QLatin1String("GPSInfo")) 0483 { 0484 gpsTagsKeys.append(key); 0485 } 0486 } 0487 0488 for (QStringList::const_iterator it2 = gpsTagsKeys.constBegin() ; it2 != gpsTagsKeys.constEnd() ; ++it2) 0489 { 0490 Exiv2::ExifKey gpsKey((*it2).toLatin1().constData()); 0491 Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey); 0492 0493 if (it3 != d->exifMetadata().end()) 0494 { 0495 d->exifMetadata().erase(it3); 0496 } 0497 } 0498 0499 #ifdef _XMP_SUPPORT_ 0500 0501 /** 0502 * NOTE: The XMP spec does not mention Xmp.exif.GPSLongitudeRef, 0503 * and Xmp.exif.GPSLatitudeRef. But because we write historicaly until 7.6.0 release 0504 * them in setGPSInfo(), we should also remove them here. 0505 * See bug #450982. 0506 */ 0507 removeXmpTag("Xmp.exif.GPSLatitudeRef"); 0508 removeXmpTag("Xmp.exif.GPSLongitudeRef"); 0509 0510 removeXmpTag("Xmp.exif.GPSVersionID"); 0511 removeXmpTag("Xmp.exif.GPSLatitude"); 0512 removeXmpTag("Xmp.exif.GPSLongitude"); 0513 removeXmpTag("Xmp.exif.GPSAltitudeRef"); 0514 removeXmpTag("Xmp.exif.GPSAltitude"); 0515 removeXmpTag("Xmp.exif.GPSTimeStamp"); 0516 removeXmpTag("Xmp.exif.GPSSatellites"); 0517 removeXmpTag("Xmp.exif.GPSStatus"); 0518 removeXmpTag("Xmp.exif.GPSMeasureMode"); 0519 removeXmpTag("Xmp.exif.GPSDOP"); 0520 removeXmpTag("Xmp.exif.GPSSpeedRef"); 0521 removeXmpTag("Xmp.exif.GPSSpeed"); 0522 removeXmpTag("Xmp.exif.GPSTrackRef"); 0523 removeXmpTag("Xmp.exif.GPSTrack"); 0524 removeXmpTag("Xmp.exif.GPSImgDirectionRef"); 0525 removeXmpTag("Xmp.exif.GPSImgDirection"); 0526 removeXmpTag("Xmp.exif.GPSMapDatum"); 0527 removeXmpTag("Xmp.exif.GPSDestLatitude"); 0528 removeXmpTag("Xmp.exif.GPSDestLongitude"); 0529 removeXmpTag("Xmp.exif.GPSDestBearingRef"); 0530 removeXmpTag("Xmp.exif.GPSDestBearing"); 0531 removeXmpTag("Xmp.exif.GPSDestDistanceRef"); 0532 removeXmpTag("Xmp.exif.GPSDestDistance"); 0533 removeXmpTag("Xmp.exif.GPSProcessingMethod"); 0534 removeXmpTag("Xmp.exif.GPSAreaInformation"); 0535 removeXmpTag("Xmp.exif.GPSDifferential"); 0536 0537 #endif // _XMP_SUPPORT_ 0538 0539 return true; 0540 } 0541 catch (Exiv2::AnyError& e) 0542 { 0543 d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif GPS tag with Exiv2:"), e); 0544 } 0545 catch (...) 0546 { 0547 qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2"; 0548 } 0549 0550 return false; 0551 } 0552 0553 void MetaEngine::convertToRational(const double number, long int* const numerator, 0554 long int* const denominator, const int rounding) 0555 { 0556 // This function converts the given decimal number 0557 // to a rational (fractional) number. 0558 // 0559 // Examples in comments use Number as 25.12345, Rounding as 4. 0560 0561 // Split up the number. 0562 0563 double whole = trunc(number); 0564 double fractional = number - whole; 0565 0566 // Calculate the "number" used for rounding. 0567 // This is 10^Digits - ie, 4 places gives us 10000. 0568 0569 double rounder = pow(10.0, rounding); 0570 0571 // Round the fractional part, and leave the number 0572 // as greater than 1. 0573 // To do this we: (for example) 0574 // 0.12345 * 10000 = 1234.5 0575 // floor(1234.5) = 1234 - now bigger than 1 - ready... 0576 0577 fractional = round(fractional * rounder); 0578 0579 // Convert the whole thing to a fraction. 0580 // Fraction is: 0581 // (25 * 10000) + 1234 251234 0582 // ------------------- = ------ = 25.1234 0583 // 10000 10000 0584 0585 double numTemp = (whole * rounder) + fractional; 0586 double denTemp = rounder; 0587 0588 // Now we should reduce until we can reduce no more. 0589 0590 // Try simple reduction... 0591 // if Num 0592 // ----- = integer out then.... 0593 // Den 0594 0595 if (trunc(numTemp / denTemp) == (numTemp / denTemp)) 0596 { 0597 // Divide both by Denominator. 0598 0599 numTemp /= denTemp; 0600 0601 // cppcheck-suppress duplicateExpression 0602 denTemp /= denTemp; 0603 } 0604 0605 // And, if that fails, brute force it. 0606 0607 while (1) 0608 { 0609 // Jump out if we can't integer divide one. 0610 0611 if ((numTemp / 2) != trunc(numTemp / 2)) break; 0612 if ((denTemp / 2) != trunc(denTemp / 2)) break; 0613 0614 // Otherwise, divide away. 0615 0616 numTemp /= 2; 0617 denTemp /= 2; 0618 } 0619 0620 // Copy out the numbers. 0621 0622 *numerator = (int)numTemp; 0623 *denominator = (int)denTemp; 0624 } 0625 0626 void MetaEngine::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator) 0627 { 0628 // This function converts the given decimal number 0629 // to a rational (fractional) number. 0630 // 0631 // This method, in contrast to the method above, will retrieve the smallest possible 0632 // denominator. It is tested to retrieve the correct value for 1/x, with 0 < x <= 1000000. 0633 // Note: This requires double precision, storing in float breaks some numbers (49, 59, 86,...) 0634 0635 // Split up the number. 0636 0637 double whole = trunc(number); 0638 double fractional = number - whole; 0639 0640 /* 0641 * Find best rational approximation to a double 0642 * by C.B. Falconer, 2006-09-07. Released to public domain. 0643 * 0644 * Newsgroups: comp.lang.c, comp.programming 0645 * From: CBFalconer <cbfalconer@yahoo.com> 0646 * Date: Thu, 07 Sep 2006 17:35:30 -0400 0647 * Subject: Rational approximations 0648 */ 0649 0650 int lastnum = 500; // this is _not_ the largest possible denominator 0651 long int num, approx, bestnum = 0, bestdenom = 1; 0652 double value, error, leasterr, criterion; 0653 0654 value = fractional; 0655 0656 if (value == 0.0) 0657 { 0658 *numerator = (long int)whole; 0659 *denominator = 1; 0660 return; 0661 } 0662 0663 criterion = 2 * value * DBL_EPSILON; 0664 0665 for (leasterr = value, num = 1 ; num < lastnum ; ++num) 0666 { 0667 approx = (int)(num / value + 0.5); 0668 error = fabs((double)num / approx - value); 0669 0670 if (error < leasterr) 0671 { 0672 bestnum = num; 0673 bestdenom = approx; 0674 leasterr = error; 0675 0676 if (leasterr <= criterion) 0677 { 0678 break; 0679 } 0680 } 0681 } 0682 0683 // add whole number part 0684 0685 if (bestdenom * whole > (double)INT_MAX) 0686 { 0687 // In some cases, we would generate an integer overflow. 0688 // Fall back to Gilles's code which is better suited for such numbers. 0689 0690 convertToRational(number, numerator, denominator, 5); 0691 } 0692 else 0693 { 0694 bestnum += bestdenom * (long int)whole; 0695 *numerator = bestnum; 0696 *denominator = bestdenom; 0697 } 0698 } 0699 0700 double MetaEngine::convertDegreeAngleToDouble(double degrees, double minutes, double seconds) 0701 { 0702 if (degrees > 0.0) 0703 { 0704 return (degrees + (minutes / 60.0) + (seconds / 3600.0)); 0705 } 0706 0707 return (degrees - (minutes / 60.0) - (seconds / 3600.0)); 0708 } 0709 0710 QString MetaEngine::convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees, 0711 const long int numeratorMinutes, const long int denominatorMinutes, 0712 const long int numeratorSeconds, const long int denominatorSeconds, 0713 const char directionReference) 0714 { 0715 /** 0716 * Precision: 0717 * A second at sea level measures 30m for our purposes, a minute 1800m. 0718 * (for more details, see https://en.wikipedia.org/wiki/Geographic_coordinate_system) 0719 * This means with a decimal precision of 8 for minutes we get +/-0,018mm. 0720 * (if I calculated correctly) 0721 */ 0722 0723 QString coordinate; 0724 long int denSecs = denominatorSeconds; 0725 0726 // be relaxed with seconds of 0/0 0727 0728 if ((denSecs == 0) && 0729 (numeratorSeconds == 0)) 0730 { 0731 denSecs = 1; 0732 } 0733 0734 if ((denominatorDegrees == 1) && 0735 (denominatorMinutes == 1) && 0736 (denSecs == 1)) 0737 { 0738 // use form DDD,MM,SSk 0739 0740 coordinate = QLatin1String("%1,%2,%3%4"); 0741 coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference); 0742 } 0743 else if ((denominatorDegrees == 1) && 0744 (denominatorMinutes == 100) && 0745 (denSecs == 1)) 0746 { 0747 // use form DDD,MM.mmk 0748 0749 coordinate = QLatin1String("%1,%2%3"); 0750 double minutes = (double)numeratorMinutes / (double)denominatorMinutes; 0751 minutes += (double)numeratorSeconds / 60.0; 0752 QString minutesString = QString::number(minutes, 'f', 8); 0753 0754 while (minutesString.endsWith(QLatin1String("0")) && !minutesString.endsWith(QLatin1String(".0"))) 0755 { 0756 minutesString.chop(1); 0757 } 0758 0759 coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference); 0760 } 0761 else if ((denominatorDegrees == 0) || 0762 (denominatorMinutes == 0) || 0763 (denSecs == 0)) 0764 { 0765 // Invalid. 1/0 is everything but 0. As is 0/0. 0766 0767 return QString(); 0768 } 0769 else 0770 { 0771 // use form DDD,MM.mmk 0772 0773 coordinate = QLatin1String("%1,%2%3"); 0774 double degrees = (double)numeratorDegrees / (double)denominatorDegrees; 0775 double wholeDegrees = trunc(degrees); 0776 double minutes = (double)numeratorMinutes / (double)denominatorMinutes; 0777 minutes += (degrees - wholeDegrees) * 60.0; 0778 minutes += ((double)numeratorSeconds / (double)denSecs) / 60.0; 0779 QString minutesString = QString::number(minutes, 'f', 8); 0780 0781 while (minutesString.endsWith(QLatin1String("0")) && !minutesString.endsWith(QLatin1String(".0"))) 0782 { 0783 minutesString.chop(1); 0784 } 0785 0786 coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference); 0787 } 0788 0789 return coordinate; 0790 } 0791 0792 QString MetaEngine::convertToGPSCoordinateString(const bool isLatitude, double coordinate) 0793 { 0794 if ((coordinate < -360.0) || (coordinate > 360.0)) 0795 { 0796 return QString(); 0797 } 0798 0799 QString coordinateString; 0800 0801 char directionReference; 0802 0803 if (isLatitude) 0804 { 0805 if (coordinate < 0) 0806 { 0807 directionReference = 'S'; 0808 } 0809 else 0810 { 0811 directionReference = 'N'; 0812 } 0813 } 0814 else 0815 { 0816 if (coordinate < 0) 0817 { 0818 directionReference = 'W'; 0819 } 0820 else 0821 { 0822 directionReference = 'E'; 0823 } 0824 } 0825 0826 // remove sign 0827 0828 coordinate = fabs(coordinate); 0829 0830 int degrees = (int)floor(coordinate); 0831 0832 // get fractional part 0833 0834 coordinate = coordinate - (double)(degrees); 0835 0836 // To minutes 0837 0838 double minutes = coordinate * 60.0; 0839 0840 // use form DDD,MM.mmk 0841 0842 coordinateString = QLatin1String("%1,%2%3"); 0843 coordinateString = coordinateString.arg(degrees); 0844 coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference); 0845 0846 return coordinateString; 0847 } 0848 0849 bool MetaEngine::convertFromGPSCoordinateString(const QString& gpsString, 0850 long int* const numeratorDegrees, long int* const denominatorDegrees, 0851 long int* const numeratorMinutes, long int* const denominatorMinutes, 0852 long int* const numeratorSeconds, long int* const denominatorSeconds, 0853 char* const directionReference) 0854 { 0855 if (gpsString.isEmpty()) 0856 { 0857 return false; 0858 } 0859 0860 *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1(); 0861 QString coordinate = gpsString.left(gpsString.length() - 1); 0862 QStringList parts = coordinate.split(QLatin1String(",")); 0863 0864 if (parts.size() == 2) 0865 { 0866 // form DDD,MM.mmk 0867 0868 *denominatorDegrees = 1; 0869 *denominatorMinutes = 1000000; 0870 *denominatorSeconds = 1; 0871 0872 *numeratorDegrees = parts[0].toLong(); 0873 0874 double minutes = parts[1].toDouble(); 0875 minutes *= 1000000; 0876 0877 *numeratorMinutes = (long)round(minutes); 0878 *numeratorSeconds = 0; 0879 0880 return true; 0881 } 0882 else if (parts.size() == 3) 0883 { 0884 // use form DDD,MM,SSk 0885 0886 *denominatorDegrees = 1; 0887 *denominatorMinutes = 1; 0888 *denominatorSeconds = 1000000; 0889 0890 *numeratorDegrees = parts[0].toLong(); 0891 *numeratorMinutes = parts[1].toLong(); 0892 0893 double seconds = parts[2].toDouble(); 0894 seconds *= 1000000; 0895 0896 *numeratorSeconds = (long)round(seconds); 0897 0898 return true; 0899 } 0900 else 0901 { 0902 return false; 0903 } 0904 } 0905 0906 bool MetaEngine::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees) 0907 { 0908 if (gpsString.isEmpty()) 0909 { 0910 return false; 0911 } 0912 0913 char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1(); 0914 QString coordinate = gpsString.left(gpsString.length() - 1); 0915 QStringList parts = coordinate.split(QLatin1String(",")); 0916 0917 if (parts.size() == 2) 0918 { 0919 // form DDD,MM.mmk 0920 0921 *degrees = parts[0].toLong(); 0922 *degrees += parts[1].toDouble() / 60.0; 0923 0924 if ((directionReference == 'W') || (directionReference == 'S')) 0925 { 0926 *degrees *= -1.0; 0927 } 0928 0929 return true; 0930 } 0931 else if (parts.size() == 3) 0932 { 0933 // use form DDD,MM,SSk 0934 0935 *degrees = parts[0].toLong(); 0936 *degrees += parts[1].toLong() / 60.0; 0937 *degrees += parts[2].toDouble() / 3600.0; 0938 0939 if ((directionReference == 'W') || (directionReference == 'S')) 0940 { 0941 *degrees *= -1.0; 0942 } 0943 0944 return true; 0945 } 0946 else 0947 { 0948 return false; 0949 } 0950 } 0951 0952 bool MetaEngine::convertToUserPresentableNumbers(const QString& gpsString, 0953 int* const degrees, int* const minutes, 0954 double* const seconds, char* const directionReference) 0955 { 0956 if (gpsString.isEmpty()) 0957 { 0958 return false; 0959 } 0960 0961 *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1(); 0962 QString coordinate = gpsString.left(gpsString.length() - 1); 0963 QStringList parts = coordinate.split(QLatin1String(",")); 0964 0965 if (parts.size() == 2) 0966 { 0967 // form DDD,MM.mmk 0968 0969 *degrees = parts[0].toInt(); 0970 double fractionalMinutes = parts[1].toDouble(); 0971 *minutes = (int)trunc(fractionalMinutes); 0972 *seconds = (fractionalMinutes - (double)(*minutes)) * 60.0; 0973 0974 return true; 0975 } 0976 else if (parts.size() == 3) 0977 { 0978 // use form DDD,MM,SSk 0979 0980 *degrees = parts[0].toInt(); 0981 *minutes = parts[1].toInt(); 0982 *seconds = parts[2].toDouble(); 0983 0984 return true; 0985 } 0986 else 0987 { 0988 return false; 0989 } 0990 } 0991 0992 void MetaEngine::convertToUserPresentableNumbers(const bool isLatitude, double coordinate, 0993 int* const degrees, int* const minutes, 0994 double* const seconds, char* const directionReference) 0995 { 0996 if (isLatitude) 0997 { 0998 if (coordinate < 0) 0999 { 1000 *directionReference = 'S'; 1001 } 1002 else 1003 { 1004 *directionReference = 'N'; 1005 } 1006 } 1007 else 1008 { 1009 if (coordinate < 0) 1010 { 1011 *directionReference = 'W'; 1012 } 1013 else 1014 { 1015 *directionReference = 'E'; 1016 } 1017 } 1018 1019 // remove sign 1020 1021 coordinate = fabs(coordinate); 1022 *degrees = (int)floor(coordinate); 1023 1024 // get fractional part 1025 1026 coordinate = coordinate - (double)(*degrees); 1027 1028 // To minutes 1029 1030 coordinate *= 60.0; 1031 *minutes = (int)floor(coordinate); 1032 1033 // get fractional part 1034 1035 coordinate = coordinate - (double)(*minutes); 1036 1037 // To seconds 1038 1039 coordinate *= 60.0; 1040 *seconds = coordinate; 1041 } 1042 1043 } // namespace Digikam 1044 1045 #if defined(Q_CC_CLANG) 1046 # pragma clang diagnostic pop 1047 #endif