Coverage Report

Created: 2026-02-26 07:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libkexiv2/src/kexiv2gps.cpp
Line
Count
Source
1
/*
2
    SPDX-FileCopyrightText: 2006-2015 Gilles Caulier <caulier dot gilles at gmail dot com>
3
    SPDX-FileCopyrightText: 2006-2012 Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
4
    SPDX-FileCopyrightText: 2010-2012 Michael G. Hansen <mike at mghansen dot de>
5
6
    SPDX-License-Identifier: GPL-2.0-or-later
7
*/
8
9
#include "kexiv2.h"
10
#include "kexiv2_p.h"
11
12
// C ANSI includes
13
14
#include <math.h>
15
16
// C++ includes
17
18
#include <climits>
19
#include <cmath>
20
21
// Local includes
22
23
#include "libkexiv2_debug.h"
24
25
namespace KExiv2Iface
26
{
27
28
bool KExiv2::getGPSInfo(double& altitude, double& latitude, double& longitude) const
29
0
{
30
    // Some GPS device do not set Altitude. So a valid GPS position can be with a zero value.
31
    // No need to check return value.
32
0
    getGPSAltitude(&altitude);
33
34
0
    if (!getGPSLatitudeNumber(&latitude))
35
0
         return false;
36
37
0
    if (!getGPSLongitudeNumber(&longitude))
38
0
         return false;
39
40
0
    return true;
41
0
}
42
43
bool KExiv2::getGPSLatitudeNumber(double* const latitude) const
44
0
{
45
0
    try
46
0
    {
47
0
        *latitude=0.0;
48
49
        // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
50
0
        if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLatitude"), latitude) )
51
0
            return true;
52
53
        // Now try to get the reference from Exif.
54
0
        const QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
55
56
0
        if (!latRef.isEmpty())
57
0
        {
58
0
            Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
59
0
            Exiv2::ExifData exifData(d->exifMetadata());
60
0
            Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
61
62
0
            if (it != exifData.end() && (*it).count() == 3)
63
0
            {
64
                // Latitude decoding from Exif.
65
0
                double num, den, min, sec;
66
67
0
                num = (double)((*it).toRational(0).first);
68
0
                den = (double)((*it).toRational(0).second);
69
70
0
                if (den == 0)
71
0
                    return false;
72
73
0
                *latitude = num/den;
74
75
0
                num = (double)((*it).toRational(1).first);
76
0
                den = (double)((*it).toRational(1).second);
77
78
0
                if (den == 0)
79
0
                    return false;
80
81
0
                min = num/den;
82
83
0
                if (min != -1.0)
84
0
                    *latitude = *latitude + min/60.0;
85
86
0
                num = (double)((*it).toRational(2).first);
87
0
                den = (double)((*it).toRational(2).second);
88
89
0
                if (den == 0)
90
0
                {
91
                    // be relaxed and accept 0/0 seconds. See #246077.
92
0
                    if (num == 0)
93
0
                        den = 1;
94
0
                    else
95
0
                        return false;
96
0
                }
97
98
0
                sec = num/den;
99
100
0
                if (sec != -1.0)
101
0
                    *latitude = *latitude + sec/3600.0;
102
0
            }
103
0
            else
104
0
            {
105
0
                return false;
106
0
            }
107
108
0
            if (latRef[0] == 'S')
109
0
                *latitude *= -1.0;
110
111
0
            return true;
112
0
        }
113
0
    }
114
0
    catch( Exiv2::Error& e )
115
0
    {
116
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
117
0
    }
118
0
    catch(...)
119
0
    {
120
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
121
0
    }
122
123
0
    return false;
124
0
}
125
126
bool KExiv2::getGPSLongitudeNumber(double* const longitude) const
127
0
{
128
0
    try
129
0
    {
130
0
        *longitude=0.0;
131
132
        // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
133
0
        if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLongitude"), longitude) )
134
0
            return true;
135
136
        // Now try to get the reference from Exif.
137
0
        const QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
138
139
0
        if (!lngRef.isEmpty())
140
0
        {
141
            // Longitude decoding from Exif.
142
143
0
            Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
144
0
            Exiv2::ExifData exifData(d->exifMetadata());
145
0
            Exiv2::ExifData::iterator it = exifData.findKey(exifKey2);
146
147
0
            if (it != exifData.end() && (*it).count() == 3)
148
0
            {
149
                /// @todo Decoding of latitude and longitude works in the same way,
150
                ///       code here can be put in a separate function
151
0
                double num, den;
152
153
0
                num = (double)((*it).toRational(0).first);
154
0
                den = (double)((*it).toRational(0).second);
155
156
0
                if (den == 0)
157
0
                {
158
0
                    return false;
159
0
                }
160
161
0
                *longitude = num/den;
162
163
0
                num = (double)((*it).toRational(1).first);
164
0
                den = (double)((*it).toRational(1).second);
165
166
0
                if (den == 0)
167
0
                {
168
0
                    return false;
169
0
                }
170
171
0
                const double min = num/den;
172
173
0
                if (min != -1.0)
174
0
                {
175
0
                    *longitude = *longitude + min/60.0;
176
0
                }
177
178
0
                num = (double)((*it).toRational(2).first);
179
0
                den = (double)((*it).toRational(2).second);
180
181
0
                if (den == 0)
182
0
                {
183
                    // be relaxed and accept 0/0 seconds. See #246077.
184
0
                    if (num == 0)
185
0
                        den = 1;
186
0
                    else
187
0
                        return false;
188
0
                }
189
190
0
                const double sec = num/den;
191
192
0
                if (sec != -1.0)
193
0
                {
194
0
                    *longitude = *longitude + sec/3600.0;
195
0
                }
196
0
            }
197
0
            else
198
0
            {
199
0
                return false;
200
0
            }
201
202
0
            if (lngRef[0] == 'W')
203
0
            {
204
0
                *longitude *= -1.0;
205
0
            }
206
207
0
            return true;
208
0
        }
209
0
    }
210
0
    catch( Exiv2::Error& e )
211
0
    {
212
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
213
0
    }
214
0
    catch(...)
215
0
    {
216
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
217
0
    }
218
219
0
    return false;
220
0
}
221
222
bool KExiv2::getGPSAltitude(double* const altitude) const
223
0
{
224
0
    try
225
0
    {
226
0
        double num, den;
227
0
        *altitude=0.0;
228
229
        // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
230
0
        const QString altRefXmp = getXmpTagString("Xmp.exif.GPSAltitudeRef");
231
232
0
        if (!altRefXmp.isEmpty())
233
0
        {
234
0
            const QString altXmp = getXmpTagString("Xmp.exif.GPSAltitude");
235
236
0
            if (!altXmp.isEmpty())
237
0
            {
238
0
                num = altXmp.section(QString::fromLatin1("/"), 0, 0).toDouble();
239
0
                den = altXmp.section(QString::fromLatin1("/"), 1, 1).toDouble();
240
241
0
                if (den == 0)
242
0
                    return false;
243
244
0
                *altitude = num/den;
245
246
0
                if (altRefXmp == QString::fromLatin1("1"))
247
0
                    *altitude *= -1.0;
248
249
0
                return true;
250
0
            }
251
0
        }
252
253
        // Get the reference from Exif (above/below sea level)
254
0
        const QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");
255
256
0
        if (!altRef.isEmpty())
257
0
        {
258
            // Altitude decoding from Exif.
259
260
0
            Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
261
0
            Exiv2::ExifData exifData(d->exifMetadata());
262
0
            Exiv2::ExifData::iterator it = exifData.findKey(exifKey3);
263
0
            if (it != exifData.end() && (*it).count())
264
0
            {
265
0
                num = (double)((*it).toRational(0).first);
266
0
                den = (double)((*it).toRational(0).second);
267
268
0
                if (den == 0)
269
0
                    return false;
270
271
0
                *altitude = num/den;
272
0
            }
273
0
            else
274
0
            {
275
0
                return false;
276
0
            }
277
278
0
            if (altRef[0] == '1')
279
0
                *altitude *= -1.0;
280
281
0
            return true;
282
0
        }
283
0
    }
284
0
    catch( Exiv2::Error& e )
285
0
    {
286
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
287
0
    }
288
0
    catch(...)
289
0
    {
290
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
291
0
    }
292
293
0
    return false;
294
0
}
295
296
QString KExiv2::getGPSLatitudeString() const
297
0
{
298
0
    double latitude;
299
300
0
    if (!getGPSLatitudeNumber(&latitude))
301
0
        return QString();
302
303
0
    return convertToGPSCoordinateString(true, latitude);
304
0
}
305
306
QString KExiv2::getGPSLongitudeString() const
307
0
{
308
0
    double longitude;
309
310
0
    if (!getGPSLongitudeNumber(&longitude))
311
0
        return QString();
312
313
0
    return convertToGPSCoordinateString(false, longitude);
314
0
}
315
316
bool KExiv2::initializeGPSInfo(const bool setProgramName)
317
0
{
318
0
    if (!setProgramId(setProgramName))
319
0
        return false;
320
321
0
    try
322
0
    {
323
        // TODO: what happens if these already exist?
324
325
        // Do all the easy constant ones first.
326
        // GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
327
        // (and, must be present).
328
0
#if EXIV2_TEST_VERSION(0,28,0)
329
0
        Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::unsignedByte);
330
#else
331
        Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
332
#endif
333
0
        value->read("2 0 0 0");
334
0
        d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());
335
336
        // Datum: the datum of the measured data. If not given, we insert WGS-84.
337
0
        d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
338
339
0
#ifdef _XMP_SUPPORT_
340
0
        setXmpTagString("Xmp.exif.GPSVersionID", QString::fromLatin1("2.0.0.0"), false);
341
0
        setXmpTagString("Xmp.exif.GPSMapDatum",  QString::fromLatin1("WGS-84"),  false);
342
0
#endif // _XMP_SUPPORT_
343
344
0
        return true;
345
0
    }
346
0
    catch( Exiv2::Error& e )
347
0
    {
348
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot initialize GPS data using Exiv2 "), e);
349
0
    }
350
0
    catch(...)
351
0
    {
352
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
353
0
    }
354
355
0
    return false;
356
0
}
357
358
bool KExiv2::setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName)
359
0
{
360
0
    return setGPSInfo(&altitude, latitude, longitude, setProgramName);
361
0
}
362
363
bool KExiv2::setGPSInfo(const double* const altitude, const double latitude, const double longitude, const bool setProgramName)
364
0
{
365
0
    if (!setProgramId(setProgramName))
366
0
        return false;
367
368
0
    try
369
0
    {
370
        // In first, we need to clean up all existing GPS info.
371
0
        removeGPSInfo();
372
373
        // now re-initialize the GPS info:
374
0
        if (!initializeGPSInfo(setProgramName))
375
0
            return false;
376
377
0
        char scratchBuf[100];
378
0
        long int nom, denom;
379
0
        long int deg, min;
380
381
        // Now start adding data.
382
383
        // ALTITUDE.
384
0
        if (altitude)
385
0
        {
386
            // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
387
0
#if EXIV2_TEST_VERSION(0,28,0)
388
0
            Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::unsignedByte);
389
#else
390
            Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
391
#endif
392
393
0
            if ((*altitude) >= 0) value->read("0");
394
0
            else               value->read("1");
395
396
0
            d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
397
398
            // And the actual altitude, as absolute value..
399
0
            convertToRational(fabs(*altitude), &nom, &denom, 4);
400
0
            snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
401
0
            d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
402
403
0
#ifdef _XMP_SUPPORT_
404
0
            setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QString::fromLatin1("0") : QString::fromLatin1("1"), false);
405
0
            setXmpTagString("Xmp.exif.GPSAltitude",    QString::fromLatin1(scratchBuf),                                          false);
406
0
#endif // _XMP_SUPPORT_
407
0
        }
408
409
        // LATITUDE
410
        // Latitude reference:
411
        // latitude < 0 : "S"
412
        // latitude > 0 : "N"
413
        //
414
0
        d->exifMetadata()["Exif.GPSInfo.GPSLatitudeRef"] = (latitude < 0 ) ? "S" : "N";
415
416
        // Now the actual latitude itself.
417
        // This is done as three rationals.
418
        // I choose to do it as:
419
        //   dd/1 - degrees.
420
        //   mmmm/100 - minutes
421
        //   0/1 - seconds
422
        // Exif standard says you can do it with minutes
423
        // as mm/1 and then seconds as ss/1, but its
424
        // (slightly) more accurate to do it as
425
        //  mmmm/100 than to split it.
426
        // We also absolute the value (with fabs())
427
        // as the sign is encoded in LatRef.
428
        // Further note: original code did not translate between
429
        //   dd.dddddd to dd mm.mm - that's why we now multiply
430
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
431
0
        deg   = (int)floor(fabs(latitude)); // Slice off after decimal.
432
0
        min   = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
433
0
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
434
0
        d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
435
436
0
#ifdef _XMP_SUPPORT_
437
        /** @todo The XMP spec does not mention Xmp.exif.GPSLatitudeRef,
438
         * because the reference is included in Xmp.exif.GPSLatitude.
439
         * Is there a historic reason for writing it anyway?
440
         */
441
0
        setXmpTagString("Xmp.exif.GPSLatitudeRef", (latitude < 0) ? QString::fromLatin1("S") : QString::fromLatin1("N"), false);
442
0
        setXmpTagString("Xmp.exif.GPSLatitude",    convertToGPSCoordinateString(true, latitude),                         false);
443
0
#endif // _XMP_SUPPORT_
444
445
        // LONGITUDE
446
        // Longitude reference:
447
        // longitude < 0 : "W"
448
        // longitude > 0 : "E"
449
0
        d->exifMetadata()["Exif.GPSInfo.GPSLongitudeRef"] = (longitude < 0 ) ? "W" : "E";
450
451
        // Now the actual longitude itself.
452
        // This is done as three rationals.
453
        // I choose to do it as:
454
        //   dd/1 - degrees.
455
        //   mmmm/100 - minutes
456
        //   0/1 - seconds
457
        // Exif standard says you can do it with minutes
458
        // as mm/1 and then seconds as ss/1, but its
459
        // (slightly) more accurate to do it as
460
        //  mmmm/100 than to split it.
461
        // We also absolute the value (with fabs())
462
        // as the sign is encoded in LongRef.
463
        // Further note: original code did not translate between
464
        //   dd.dddddd to dd mm.mm - that's why we now multiply
465
        //   by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
466
0
        deg   = (int)floor(fabs(longitude)); // Slice off after decimal.
467
0
        min   = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
468
0
        snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
469
0
        d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
470
471
0
#ifdef _XMP_SUPPORT_
472
        /** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
473
         * because the reference is included in Xmp.exif.GPSLongitude.
474
         * Is there a historic reason for writing it anyway?
475
         */
476
0
        setXmpTagString("Xmp.exif.GPSLongitudeRef", (longitude < 0) ? QString::fromLatin1("W") : QString::fromLatin1("E"), false);
477
0
        setXmpTagString("Xmp.exif.GPSLongitude",    convertToGPSCoordinateString(false, longitude),                        false);
478
0
#endif // _XMP_SUPPORT_
479
480
0
        return true;
481
0
    }
482
0
    catch( Exiv2::Error& e )
483
0
    {
484
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif GPS tag using Exiv2 "), e);
485
0
    }
486
0
    catch(...)
487
0
    {
488
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
489
0
    }
490
491
0
    return false;
492
0
}
493
494
bool KExiv2::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude, const bool setProgramName)
495
0
{
496
0
    double longitudeValue, latitudeValue;
497
498
0
    if (!convertFromGPSCoordinateString(latitude, &latitudeValue))
499
0
        return false;
500
501
0
    if (!convertFromGPSCoordinateString(longitude, &longitudeValue))
502
0
        return false;
503
504
0
    return setGPSInfo(&altitude, latitudeValue, longitudeValue, setProgramName);
505
0
}
506
507
bool KExiv2::removeGPSInfo(const bool setProgramName)
508
0
{
509
0
    if (!setProgramId(setProgramName))
510
0
        return false;
511
512
0
    try
513
0
    {
514
0
        QStringList gpsTagsKeys;
515
516
0
        for (Exiv2::ExifData::iterator it = d->exifMetadata().begin();
517
0
             it != d->exifMetadata().end(); ++it)
518
0
        {
519
0
            QString key = QString::fromLocal8Bit(it->key().c_str());
520
521
0
            if (key.section(QString::fromLatin1("."), 1, 1) == QString::fromLatin1("GPSInfo"))
522
0
                gpsTagsKeys.append(key);
523
0
        }
524
525
0
        for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
526
0
        {
527
0
            Exiv2::ExifKey gpsKey((*it2).toLatin1().constData());
528
0
            Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey);
529
530
0
            if (it3 != d->exifMetadata().end())
531
0
                d->exifMetadata().erase(it3);
532
0
        }
533
534
0
#ifdef _XMP_SUPPORT_
535
        /** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
536
         * and Xmp.exif.GPSLatitudeRef. But because we write them in setGPSInfo(),
537
         * we should also remove them here.
538
         */
539
0
        removeXmpTag("Xmp.exif.GPSLatitudeRef", false);
540
0
        removeXmpTag("Xmp.exif.GPSLongitudeRef", false);
541
0
        removeXmpTag("Xmp.exif.GPSVersionID", false);
542
0
        removeXmpTag("Xmp.exif.GPSLatitude", false);
543
0
        removeXmpTag("Xmp.exif.GPSLongitude", false);
544
0
        removeXmpTag("Xmp.exif.GPSAltitudeRef", false);
545
0
        removeXmpTag("Xmp.exif.GPSAltitude", false);
546
0
        removeXmpTag("Xmp.exif.GPSTimeStamp", false);
547
0
        removeXmpTag("Xmp.exif.GPSSatellites", false);
548
0
        removeXmpTag("Xmp.exif.GPSStatus", false);
549
0
        removeXmpTag("Xmp.exif.GPSMeasureMode", false);
550
0
        removeXmpTag("Xmp.exif.GPSDOP", false);
551
0
        removeXmpTag("Xmp.exif.GPSSpeedRef", false);
552
0
        removeXmpTag("Xmp.exif.GPSSpeed", false);
553
0
        removeXmpTag("Xmp.exif.GPSTrackRef", false);
554
0
        removeXmpTag("Xmp.exif.GPSTrack", false);
555
0
        removeXmpTag("Xmp.exif.GPSImgDirectionRef", false);
556
0
        removeXmpTag("Xmp.exif.GPSImgDirection", false);
557
0
        removeXmpTag("Xmp.exif.GPSMapDatum", false);
558
0
        removeXmpTag("Xmp.exif.GPSDestLatitude", false);
559
0
        removeXmpTag("Xmp.exif.GPSDestLongitude", false);
560
0
        removeXmpTag("Xmp.exif.GPSDestBearingRef", false);
561
0
        removeXmpTag("Xmp.exif.GPSDestBearing", false);
562
0
        removeXmpTag("Xmp.exif.GPSDestDistanceRef", false);
563
0
        removeXmpTag("Xmp.exif.GPSDestDistance", false);
564
0
        removeXmpTag("Xmp.exif.GPSProcessingMethod", false);
565
0
        removeXmpTag("Xmp.exif.GPSAreaInformation", false);
566
0
        removeXmpTag("Xmp.exif.GPSDifferential", false);
567
0
#endif // _XMP_SUPPORT_
568
569
0
        return true;
570
0
    }
571
0
    catch( Exiv2::Error& e )
572
0
    {
573
0
        d->printExiv2ExceptionError(QString::fromLatin1("Cannot remove Exif GPS tag using Exiv2 "), e);
574
0
    }
575
0
    catch(...)
576
0
    {
577
0
        qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
578
0
    }
579
580
0
    return false;
581
0
}
582
583
void KExiv2::convertToRational(const double number, long int* const numerator, 
584
                               long int* const denominator, const int rounding)
585
0
{
586
    // This function converts the given decimal number
587
    // to a rational (fractional) number.
588
    //
589
    // Examples in comments use Number as 25.12345, Rounding as 4.
590
591
    // Split up the number.
592
0
    double whole      = trunc(number);
593
0
    double fractional = number - whole;
594
595
    // Calculate the "number" used for rounding.
596
    // This is 10^Digits - ie, 4 places gives us 10000.
597
0
    double rounder = pow(10.0, rounding);
598
599
    // Round the fractional part, and leave the number
600
    // as greater than 1.
601
    // To do this we: (for example)
602
    //  0.12345 * 10000 = 1234.5
603
    //  floor(1234.5) = 1234 - now bigger than 1 - ready...
604
0
    fractional = round(fractional * rounder);
605
606
    // Convert the whole thing to a fraction.
607
    // Fraction is:
608
    //     (25 * 10000) + 1234   251234
609
    //     ------------------- = ------ = 25.1234
610
    //           10000            10000
611
0
    double numTemp = (whole * rounder) + fractional;
612
0
    double denTemp = rounder;
613
614
    // Now we should reduce until we can reduce no more.
615
616
    // Try simple reduction...
617
    // if   Num
618
    //     ----- = integer out then....
619
    //      Den
620
0
    if (trunc(numTemp / denTemp) == (numTemp / denTemp))
621
0
    {
622
        // Divide both by Denominator.
623
0
        numTemp /= denTemp;
624
0
        denTemp /= denTemp;
625
0
    }
626
627
    // And, if that fails, brute force it.
628
0
    while (1)
629
0
    {
630
        // Jump out if we can't integer divide one.
631
0
        if ((numTemp / 2) != trunc(numTemp / 2)) break;
632
0
        if ((denTemp / 2) != trunc(denTemp / 2)) break;
633
        // Otherwise, divide away.
634
0
        numTemp /= 2;
635
0
        denTemp /= 2;
636
0
    }
637
638
    // Copy out the numbers.
639
0
    *numerator   = (int)numTemp;
640
0
    *denominator = (int)denTemp;
641
0
}
642
643
void KExiv2::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator)
644
0
{
645
    // This function converts the given decimal number
646
    // to a rational (fractional) number.
647
    //
648
    // This method, in contrast to the method above, will retrieve the smallest possible
649
    // denominator. It is tested to retrieve the correct value for 1/x, with 0 < x <= 1000000.
650
    // Note: This requires double precision, storing in float breaks some numbers (49, 59, 86,...)
651
652
    // Split up the number.
653
0
    double whole      = trunc(number);
654
0
    double fractional = number - whole;
655
656
    /*
657
     * Find best rational approximation to a double
658
     * by C.B. Falconer, 2006-09-07. Released to public domain.
659
     *
660
     * Newsgroups: comp.lang.c, comp.programming
661
     * From: CBFalconer <cbfalconer@yahoo.com>
662
     * Date: Thu, 07 Sep 2006 17:35:30 -0400
663
     * Subject: Rational approximations
664
     */
665
0
    int      lastnum = 500; // this is _not_ the largest possible denominator
666
0
    long int num, approx, bestnum=0, bestdenom=1;
667
0
    double   value, error, leasterr, criterion;
668
669
0
    value = fractional;
670
671
0
    if (value == 0.0)
672
0
    {
673
0
        *numerator   = (long int)whole;
674
0
        *denominator = 1;
675
0
        return;
676
0
    }
677
678
0
    criterion = 2 * value * DBL_EPSILON;
679
680
0
    for (leasterr = value, num = 1; num < lastnum; ++num)
681
0
    {
682
0
        approx = (int)(num / value + 0.5);
683
0
        error  = fabs((double)num / approx - value);
684
685
0
        if (error < leasterr)
686
0
        {
687
0
            bestnum   = num;
688
0
            bestdenom = approx;
689
0
            leasterr  = error;
690
691
0
            if (leasterr <= criterion) break;
692
0
        }
693
0
    }
694
695
    // add whole number part
696
0
    if (bestdenom * whole > (double)INT_MAX)
697
0
    {
698
        // In some cases, we would generate an integer overflow.
699
        // Fall back to Gilles's code which is better suited for such numbers.
700
0
        convertToRational(number, numerator, denominator, 5);
701
0
    }
702
0
    else
703
0
    {
704
0
        bestnum      += bestdenom * (long int)whole;
705
0
        *numerator   =  bestnum;
706
0
        *denominator =  bestdenom;
707
0
    }
708
0
}
709
710
QString KExiv2::convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees,
711
                                             const long int numeratorMinutes, const long int denominatorMinutes,
712
                                             const long int numeratorSeconds, long int denominatorSeconds,
713
                                             const char directionReference)
714
0
{
715
    /**
716
     * Precision:
717
     * A second at sea level measures 30m for our purposes, a minute 1800m.
718
     * (for more details, see https://en.wikipedia.org/wiki/Geographic_coordinate_system)
719
     * This means with a decimal precision of 8 for minutes we get +/-0,018mm.
720
     * (if I calculated correctly)
721
     */
722
723
0
    QString coordinate;
724
725
    // be relaxed with seconds of 0/0
726
0
    if (denominatorSeconds == 0 && numeratorSeconds == 0)
727
0
        denominatorSeconds = 1;
728
729
0
    if (denominatorDegrees == 1 &&
730
0
        denominatorMinutes == 1 &&
731
0
        denominatorSeconds == 1)
732
0
    {
733
        // use form DDD,MM,SSk
734
0
        coordinate = QString::fromLatin1("%1,%2,%3%4");
735
0
        coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference);
736
0
    }
737
0
    else if (denominatorDegrees == 1   &&
738
0
             denominatorMinutes == 100 &&
739
0
             denominatorSeconds == 1)
740
0
    {
741
        // use form DDD,MM.mmk
742
0
        coordinate             = QString::fromLatin1("%1,%2%3");
743
0
        double minutes         = (double)numeratorMinutes / (double)denominatorMinutes;
744
0
        minutes               += (double)numeratorSeconds / 60.0;
745
0
        QString minutesString =  QString::number(minutes, 'f', 8);
746
747
0
        while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
748
0
        {
749
0
            minutesString.chop(1);
750
0
        }
751
752
0
        coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference);
753
0
    }
754
0
    else if (denominatorDegrees == 0 ||
755
0
             denominatorMinutes == 0 ||
756
0
             denominatorSeconds == 0)
757
0
    {
758
        // Invalid. 1/0 is everything but 0. As is 0/0.
759
0
        return QString();
760
0
    }
761
0
    else
762
0
    {
763
        // use form DDD,MM.mmk
764
0
        coordinate             = QString::fromLatin1("%1,%2%3");
765
0
        double degrees         = (double)numeratorDegrees / (double)denominatorDegrees;
766
0
        double wholeDegrees    = trunc(degrees);
767
0
        double minutes         = (double)numeratorMinutes / (double)denominatorMinutes;
768
0
        minutes               += (degrees - wholeDegrees) * 60.0;
769
0
        minutes               += ((double)numeratorSeconds / (double)denominatorSeconds) / 60.0;
770
0
        QString minutesString  = QString::number(minutes, 'f', 8);
771
772
0
        while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
773
0
        {
774
0
            minutesString.chop(1);
775
0
        }
776
777
0
        coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference);
778
0
    }
779
780
0
    return coordinate;
781
0
}
782
783
QString KExiv2::convertToGPSCoordinateString(const bool isLatitude, double coordinate)
784
0
{
785
0
    if (coordinate < -360.0 || coordinate > 360.0)
786
0
        return QString();
787
788
0
    QString coordinateString;
789
790
0
    char directionReference;
791
792
0
    if (isLatitude)
793
0
    {
794
0
        if (coordinate < 0)
795
0
            directionReference = 'S';
796
0
        else
797
0
            directionReference = 'N';
798
0
    }
799
0
    else
800
0
    {
801
0
        if (coordinate < 0)
802
0
            directionReference = 'W';
803
0
        else
804
0
            directionReference = 'E';
805
0
    }
806
807
    // remove sign
808
0
    coordinate     = fabs(coordinate);
809
810
0
    int degrees    = (int)floor(coordinate);
811
    // get fractional part
812
0
    coordinate     = coordinate - (double)(degrees);
813
    // To minutes
814
0
    double minutes = coordinate * 60.0;
815
816
    // use form DDD,MM.mmk
817
0
    coordinateString = QString::fromLatin1("%1,%2%3");
818
0
    coordinateString = coordinateString.arg(degrees);
819
0
    coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference);
820
821
0
    return coordinateString;
822
0
}
823
824
bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString,
825
                                            long int* const numeratorDegrees, long int* const denominatorDegrees,
826
                                            long int* const numeratorMinutes, long int* const denominatorMinutes,
827
                                            long int* const numeratorSeconds, long int* const denominatorSeconds,
828
                                            char* const directionReference)
829
0
{
830
0
    if (gpsString.isEmpty())
831
0
        return false;
832
833
0
    *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
834
0
    QString coordinate  = gpsString.left(gpsString.length() - 1);
835
0
    QStringList parts   = coordinate.split(QString::fromLatin1(","));
836
837
0
    if (parts.size() == 2)
838
0
    {
839
        // form DDD,MM.mmk
840
0
        *denominatorDegrees = 1;
841
0
        *denominatorMinutes = 1000000;
842
0
        *denominatorSeconds = 1;
843
844
0
        *numeratorDegrees   = parts[0].toLong();
845
846
0
        double minutes      = parts[1].toDouble();
847
0
        minutes            *= 1000000;
848
849
0
        *numeratorMinutes   = (long)round(minutes);
850
0
        *numeratorSeconds   = 0;
851
852
0
        return true;
853
0
    }
854
0
    else if (parts.size() == 3)
855
0
    {
856
        // use form DDD,MM,SSk
857
0
        *denominatorDegrees = 1;
858
0
        *denominatorMinutes = 1;
859
0
        *denominatorSeconds = 1;
860
861
0
        *numeratorDegrees   = parts[0].toLong();
862
0
        *numeratorMinutes   = parts[1].toLong();
863
0
        *numeratorSeconds   = parts[2].toLong();
864
865
0
        return true;
866
0
    }
867
0
    else
868
0
    {
869
0
        return false;
870
0
    }
871
0
}
872
873
bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees)
874
0
{
875
0
    if (gpsString.isEmpty())
876
0
        return false;
877
878
0
    char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
879
0
    QString coordinate      = gpsString.left(gpsString.length() - 1);
880
0
    QStringList parts       = coordinate.split(QString::fromLatin1(","));
881
882
0
    if (parts.size() == 2)
883
0
    {
884
        // form DDD,MM.mmk
885
0
        *degrees =  parts[0].toLong();
886
0
        *degrees += parts[1].toDouble() / 60.0;
887
888
0
        if (directionReference == 'W' || directionReference == 'S')
889
0
            *degrees *= -1.0;
890
891
0
        return true;
892
0
    }
893
0
    else if (parts.size() == 3)
894
0
    {
895
        // use form DDD,MM,SSk
896
897
0
        *degrees =  parts[0].toLong();
898
0
        *degrees += parts[1].toLong() / 60.0;
899
0
        *degrees += parts[2].toLong() / 3600.0;
900
901
0
        if (directionReference == 'W' || directionReference == 'S')
902
0
            *degrees *= -1.0;
903
904
0
        return true;
905
0
    }
906
0
    else
907
0
    {
908
0
        return false;
909
0
    }
910
0
}
911
912
bool KExiv2::convertToUserPresentableNumbers(const QString& gpsString,
913
                                             int* const degrees, int* const minutes,
914
                                             double* const seconds, char* const directionReference)
915
0
{
916
0
    if (gpsString.isEmpty())
917
0
        return false;
918
919
0
    *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
920
0
    QString coordinate  = gpsString.left(gpsString.length() - 1);
921
0
    QStringList parts   = coordinate.split(QString::fromLatin1(","));
922
923
0
    if (parts.size() == 2)
924
0
    {
925
        // form DDD,MM.mmk
926
0
        *degrees                 = parts[0].toInt();
927
0
        double fractionalMinutes = parts[1].toDouble();
928
0
        *minutes                 = (int)trunc(fractionalMinutes);
929
0
        *seconds                 = (fractionalMinutes - (double)(*minutes)) * 60.0;
930
931
0
        return true;
932
0
    }
933
0
    else if (parts.size() == 3)
934
0
    {
935
        // use form DDD,MM,SSk
936
0
        *degrees = parts[0].toInt();
937
0
        *minutes = parts[1].toInt();
938
0
        *seconds = (double)parts[2].toInt();
939
940
0
        return true;
941
0
    }
942
0
    else
943
0
    {
944
0
        return false;
945
0
    }
946
0
}
947
948
void KExiv2::convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
949
                                             int* const degrees, int* const minutes,
950
                                             double* const seconds, char* const directionReference)
951
0
{
952
0
    if (isLatitude)
953
0
    {
954
0
        if (coordinate < 0)
955
0
            *directionReference = 'S';
956
0
        else
957
0
            *directionReference = 'N';
958
0
    }
959
0
    else
960
0
    {
961
0
        if (coordinate < 0)
962
0
            *directionReference = 'W';
963
0
        else
964
0
            *directionReference = 'E';
965
0
    }
966
967
    // remove sign
968
0
    coordinate  = fabs(coordinate);
969
0
    *degrees    = (int)floor(coordinate);
970
    // get fractional part
971
0
    coordinate  = coordinate - (double)(*degrees);
972
    // To minutes
973
0
    coordinate *= 60.0;
974
0
    *minutes    = (int)floor(coordinate);
975
    // get fractional part
976
0
    coordinate  = coordinate - (double)(*minutes);
977
    // To seconds
978
0
    coordinate *= 60.0;
979
0
    *seconds    = coordinate;
980
0
}
981
982
}  // NameSpace KExiv2Iface