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