KExiv2

kexiv2gps.cpp
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
25namespace KExiv2Iface
26{
27
28bool KExiv2::getGPSInfo(double& altitude, double& latitude, double& longitude) const
29{
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 getGPSAltitude(&altitude);
33
34 if (!getGPSLatitudeNumber(&latitude))
35 return false;
36
37 if (!getGPSLongitudeNumber(&longitude))
38 return false;
39
40 return true;
41}
42
43bool KExiv2::getGPSLatitudeNumber(double* const latitude) const
44{
45 try
46 {
47 *latitude=0.0;
48
49 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
50 if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLatitude"), latitude) )
51 return true;
52
53 // Now try to get the reference from Exif.
54 const QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
55
56 if (!latRef.isEmpty())
57 {
58 Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
59 Exiv2::ExifData exifData(d->exifMetadata());
60 Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
61
62 if (it != exifData.end() && (*it).count() == 3)
63 {
64 // Latitude decoding from Exif.
65 double num, den, min, sec;
66
67 num = (double)((*it).toRational(0).first);
68 den = (double)((*it).toRational(0).second);
69
70 if (den == 0)
71 return false;
72
73 *latitude = num/den;
74
75 num = (double)((*it).toRational(1).first);
76 den = (double)((*it).toRational(1).second);
77
78 if (den == 0)
79 return false;
80
81 min = num/den;
82
83 if (min != -1.0)
84 *latitude = *latitude + min/60.0;
85
86 num = (double)((*it).toRational(2).first);
87 den = (double)((*it).toRational(2).second);
88
89 if (den == 0)
90 {
91 // be relaxed and accept 0/0 seconds. See #246077.
92 if (num == 0)
93 den = 1;
94 else
95 return false;
96 }
97
98 sec = num/den;
99
100 if (sec != -1.0)
101 *latitude = *latitude + sec/3600.0;
102 }
103 else
104 {
105 return false;
106 }
107
108 if (latRef[0] == 'S')
109 *latitude *= -1.0;
110
111 return true;
112 }
113 }
114 catch( Exiv2::Error& e )
115 {
116 d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
117 }
118 catch(...)
119 {
120 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
121 }
122
123 return false;
124}
125
126bool KExiv2::getGPSLongitudeNumber(double* const longitude) const
127{
128 try
129 {
130 *longitude=0.0;
131
132 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
133 if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLongitude"), longitude) )
134 return true;
135
136 // Now try to get the reference from Exif.
137 const QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
138
139 if (!lngRef.isEmpty())
140 {
141 // Longitude decoding from Exif.
142
143 Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
144 Exiv2::ExifData exifData(d->exifMetadata());
145 Exiv2::ExifData::iterator it = exifData.findKey(exifKey2);
146
147 if (it != exifData.end() && (*it).count() == 3)
148 {
149 /// @todo Decoding of latitude and longitude works in the same way,
150 /// code here can be put in a separate function
151 double num, den;
152
153 num = (double)((*it).toRational(0).first);
154 den = (double)((*it).toRational(0).second);
155
156 if (den == 0)
157 {
158 return false;
159 }
160
161 *longitude = num/den;
162
163 num = (double)((*it).toRational(1).first);
164 den = (double)((*it).toRational(1).second);
165
166 if (den == 0)
167 {
168 return false;
169 }
170
171 const double min = num/den;
172
173 if (min != -1.0)
174 {
175 *longitude = *longitude + min/60.0;
176 }
177
178 num = (double)((*it).toRational(2).first);
179 den = (double)((*it).toRational(2).second);
180
181 if (den == 0)
182 {
183 // be relaxed and accept 0/0 seconds. See #246077.
184 if (num == 0)
185 den = 1;
186 else
187 return false;
188 }
189
190 const double sec = num/den;
191
192 if (sec != -1.0)
193 {
194 *longitude = *longitude + sec/3600.0;
195 }
196 }
197 else
198 {
199 return false;
200 }
201
202 if (lngRef[0] == 'W')
203 {
204 *longitude *= -1.0;
205 }
206
207 return true;
208 }
209 }
210 catch( Exiv2::Error& e )
211 {
212 d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
213 }
214 catch(...)
215 {
216 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
217 }
218
219 return false;
220}
221
222bool KExiv2::getGPSAltitude(double* const altitude) const
223{
224 try
225 {
226 double num, den;
227 *altitude=0.0;
228
229 // Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
230 const QString altRefXmp = getXmpTagString("Xmp.exif.GPSAltitudeRef");
231
232 if (!altRefXmp.isEmpty())
233 {
234 const QString altXmp = getXmpTagString("Xmp.exif.GPSAltitude");
235
236 if (!altXmp.isEmpty())
237 {
238 num = altXmp.section(QString::fromLatin1("/"), 0, 0).toDouble();
239 den = altXmp.section(QString::fromLatin1("/"), 1, 1).toDouble();
240
241 if (den == 0)
242 return false;
243
244 *altitude = num/den;
245
246 if (altRefXmp == QString::fromLatin1("1"))
247 *altitude *= -1.0;
248
249 return true;
250 }
251 }
252
253 // Get the reference from Exif (above/below sea level)
254 const QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");
255
256 if (!altRef.isEmpty())
257 {
258 // Altitude decoding from Exif.
259
260 Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
261 Exiv2::ExifData exifData(d->exifMetadata());
262 Exiv2::ExifData::iterator it = exifData.findKey(exifKey3);
263 if (it != exifData.end() && (*it).count())
264 {
265 num = (double)((*it).toRational(0).first);
266 den = (double)((*it).toRational(0).second);
267
268 if (den == 0)
269 return false;
270
271 *altitude = num/den;
272 }
273 else
274 {
275 return false;
276 }
277
278 if (altRef[0] == '1')
279 *altitude *= -1.0;
280
281 return true;
282 }
283 }
284 catch( Exiv2::Error& e )
285 {
286 d->printExiv2ExceptionError(QString::fromLatin1("Cannot get GPS tag using Exiv2 "), e);
287 }
288 catch(...)
289 {
290 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
291 }
292
293 return false;
294}
295
297{
298 double latitude;
299
300 if (!getGPSLatitudeNumber(&latitude))
301 return QString();
302
303 return convertToGPSCoordinateString(true, latitude);
304}
305
306QString KExiv2::getGPSLongitudeString() const
307{
308 double longitude;
309
310 if (!getGPSLongitudeNumber(&longitude))
311 return QString();
312
313 return convertToGPSCoordinateString(false, longitude);
314}
315
316bool KExiv2::initializeGPSInfo(const bool setProgramName)
317{
318 if (!setProgramId(setProgramName))
319 return false;
320
321 try
322 {
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#if EXIV2_TEST_VERSION(0,28,0)
329 Exiv2::Value::UniquePtr value = Exiv2::Value::create(Exiv2::unsignedByte);
330#else
331 Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
332#endif
333 value->read("2 0 0 0");
334 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 d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
338
339#ifdef _XMP_SUPPORT_
340 setXmpTagString("Xmp.exif.GPSVersionID", QString::fromLatin1("2.0.0.0"), false);
341 setXmpTagString("Xmp.exif.GPSMapDatum", QString::fromLatin1("WGS-84"), false);
342#endif // _XMP_SUPPORT_
343
344 return true;
345 }
346 catch( Exiv2::Error& e )
347 {
348 d->printExiv2ExceptionError(QString::fromLatin1("Cannot initialize GPS data using Exiv2 "), e);
349 }
350 catch(...)
351 {
352 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
353 }
354
355 return false;
356}
357
358bool KExiv2::setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName)
359{
360 return setGPSInfo(&altitude, latitude, longitude, setProgramName);
361}
362
363bool KExiv2::setGPSInfo(const double* const altitude, const double latitude, const double longitude, const bool setProgramName)
364{
365 if (!setProgramId(setProgramName))
366 return false;
367
368 try
369 {
370 // In first, we need to clean up all existing GPS info.
372
373 // now re-initialize the GPS info:
374 if (!initializeGPSInfo(setProgramName))
375 return false;
376
377 char scratchBuf[100];
378 long int nom, denom;
379 long int deg, min;
380
381 // Now start adding data.
382
383 // ALTITUDE.
384 if (altitude)
385 {
386 // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
387#if EXIV2_TEST_VERSION(0,28,0)
388 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 if ((*altitude) >= 0) value->read("0");
394 else value->read("1");
395
396 d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
397
398 // And the actual altitude, as absolute value..
399 convertToRational(fabs(*altitude), &nom, &denom, 4);
400 snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
401 d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
402
403#ifdef _XMP_SUPPORT_
404 setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QString::fromLatin1("0") : QString::fromLatin1("1"), false);
405 setXmpTagString("Xmp.exif.GPSAltitude", QString::fromLatin1(scratchBuf), false);
406#endif // _XMP_SUPPORT_
407 }
408
409 // LATITUDE
410 // Latitude reference:
411 // latitude < 0 : "S"
412 // latitude > 0 : "N"
413 //
414 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 deg = (int)floor(fabs(latitude)); // Slice off after decimal.
432 min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
433 snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
434 d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
435
436#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 setXmpTagString("Xmp.exif.GPSLatitudeRef", (latitude < 0) ? QString::fromLatin1("S") : QString::fromLatin1("N"), false);
442 setXmpTagString("Xmp.exif.GPSLatitude", convertToGPSCoordinateString(true, latitude), false);
443#endif // _XMP_SUPPORT_
444
445 // LONGITUDE
446 // Longitude reference:
447 // longitude < 0 : "W"
448 // longitude > 0 : "E"
449 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 deg = (int)floor(fabs(longitude)); // Slice off after decimal.
467 min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
468 snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
469 d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
470
471#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 setXmpTagString("Xmp.exif.GPSLongitudeRef", (longitude < 0) ? QString::fromLatin1("W") : QString::fromLatin1("E"), false);
477 setXmpTagString("Xmp.exif.GPSLongitude", convertToGPSCoordinateString(false, longitude), false);
478#endif // _XMP_SUPPORT_
479
480 return true;
481 }
482 catch( Exiv2::Error& e )
483 {
484 d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif GPS tag using Exiv2 "), e);
485 }
486 catch(...)
487 {
488 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
489 }
490
491 return false;
492}
493
494bool KExiv2::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude, const bool setProgramName)
495{
496 double longitudeValue, latitudeValue;
497
498 if (!convertFromGPSCoordinateString(latitude, &latitudeValue))
499 return false;
500
501 if (!convertFromGPSCoordinateString(longitude, &longitudeValue))
502 return false;
503
504 return setGPSInfo(&altitude, latitudeValue, longitudeValue, setProgramName);
505}
506
507bool KExiv2::removeGPSInfo(const bool setProgramName)
508{
509 if (!setProgramId(setProgramName))
510 return false;
511
512 try
513 {
514 QStringList gpsTagsKeys;
515
516 for (Exiv2::ExifData::iterator it = d->exifMetadata().begin();
517 it != d->exifMetadata().end(); ++it)
518 {
519 QString key = QString::fromLocal8Bit(it->key().c_str());
520
521 if (key.section(QString::fromLatin1("."), 1, 1) == QString::fromLatin1("GPSInfo"))
522 gpsTagsKeys.append(key);
523 }
524
525 for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
526 {
527 Exiv2::ExifKey gpsKey((*it2).toLatin1().constData());
528 Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey);
529
530 if (it3 != d->exifMetadata().end())
531 d->exifMetadata().erase(it3);
532 }
533
534#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 removeXmpTag("Xmp.exif.GPSLatitudeRef", false);
540 removeXmpTag("Xmp.exif.GPSLongitudeRef", false);
541 removeXmpTag("Xmp.exif.GPSVersionID", false);
542 removeXmpTag("Xmp.exif.GPSLatitude", false);
543 removeXmpTag("Xmp.exif.GPSLongitude", false);
544 removeXmpTag("Xmp.exif.GPSAltitudeRef", false);
545 removeXmpTag("Xmp.exif.GPSAltitude", false);
546 removeXmpTag("Xmp.exif.GPSTimeStamp", false);
547 removeXmpTag("Xmp.exif.GPSSatellites", false);
548 removeXmpTag("Xmp.exif.GPSStatus", false);
549 removeXmpTag("Xmp.exif.GPSMeasureMode", false);
550 removeXmpTag("Xmp.exif.GPSDOP", false);
551 removeXmpTag("Xmp.exif.GPSSpeedRef", false);
552 removeXmpTag("Xmp.exif.GPSSpeed", false);
553 removeXmpTag("Xmp.exif.GPSTrackRef", false);
554 removeXmpTag("Xmp.exif.GPSTrack", false);
555 removeXmpTag("Xmp.exif.GPSImgDirectionRef", false);
556 removeXmpTag("Xmp.exif.GPSImgDirection", false);
557 removeXmpTag("Xmp.exif.GPSMapDatum", false);
558 removeXmpTag("Xmp.exif.GPSDestLatitude", false);
559 removeXmpTag("Xmp.exif.GPSDestLongitude", false);
560 removeXmpTag("Xmp.exif.GPSDestBearingRef", false);
561 removeXmpTag("Xmp.exif.GPSDestBearing", false);
562 removeXmpTag("Xmp.exif.GPSDestDistanceRef", false);
563 removeXmpTag("Xmp.exif.GPSDestDistance", false);
564 removeXmpTag("Xmp.exif.GPSProcessingMethod", false);
565 removeXmpTag("Xmp.exif.GPSAreaInformation", false);
566 removeXmpTag("Xmp.exif.GPSDifferential", false);
567#endif // _XMP_SUPPORT_
568
569 return true;
570 }
571 catch( Exiv2::Error& e )
572 {
573 d->printExiv2ExceptionError(QString::fromLatin1("Cannot remove Exif GPS tag using Exiv2 "), e);
574 }
575 catch(...)
576 {
577 qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
578 }
579
580 return false;
581}
582
583void KExiv2::convertToRational(const double number, long int* const numerator,
584 long int* const denominator, const int rounding)
585{
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 double whole = trunc(number);
593 double fractional = number - whole;
594
595 // Calculate the "number" used for rounding.
596 // This is 10^Digits - ie, 4 places gives us 10000.
597 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 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 double numTemp = (whole * rounder) + fractional;
612 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 if (trunc(numTemp / denTemp) == (numTemp / denTemp))
621 {
622 // Divide both by Denominator.
623 numTemp /= denTemp;
624 denTemp /= denTemp;
625 }
626
627 // And, if that fails, brute force it.
628 while (1)
629 {
630 // Jump out if we can't integer divide one.
631 if ((numTemp / 2) != trunc(numTemp / 2)) break;
632 if ((denTemp / 2) != trunc(denTemp / 2)) break;
633 // Otherwise, divide away.
634 numTemp /= 2;
635 denTemp /= 2;
636 }
637
638 // Copy out the numbers.
639 *numerator = (int)numTemp;
640 *denominator = (int)denTemp;
641}
642
643void KExiv2::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator)
644{
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 double whole = trunc(number);
654 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 int lastnum = 500; // this is _not_ the largest possible denominator
666 long int num, approx, bestnum=0, bestdenom=1;
667 double value, error, leasterr, criterion;
668
669 value = fractional;
670
671 if (value == 0.0)
672 {
673 *numerator = (long int)whole;
674 *denominator = 1;
675 return;
676 }
677
678 criterion = 2 * value * DBL_EPSILON;
679
680 for (leasterr = value, num = 1; num < lastnum; ++num)
681 {
682 approx = (int)(num / value + 0.5);
683 error = fabs((double)num / approx - value);
684
685 if (error < leasterr)
686 {
687 bestnum = num;
688 bestdenom = approx;
689 leasterr = error;
690
691 if (leasterr <= criterion) break;
692 }
693 }
694
695 // add whole number part
696 if (bestdenom * whole > (double)INT_MAX)
697 {
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 convertToRational(number, numerator, denominator, 5);
701 }
702 else
703 {
704 bestnum += bestdenom * (long int)whole;
705 *numerator = bestnum;
706 *denominator = bestdenom;
707 }
708}
709
710QString 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{
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 QString coordinate;
724
725 // be relaxed with seconds of 0/0
726 if (denominatorSeconds == 0 && numeratorSeconds == 0)
727 denominatorSeconds = 1;
728
729 if (denominatorDegrees == 1 &&
730 denominatorMinutes == 1 &&
731 denominatorSeconds == 1)
732 {
733 // use form DDD,MM,SSk
734 coordinate = QString::fromLatin1("%1,%2,%3%4");
735 coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference);
736 }
737 else if (denominatorDegrees == 1 &&
738 denominatorMinutes == 100 &&
739 denominatorSeconds == 1)
740 {
741 // use form DDD,MM.mmk
742 coordinate = QString::fromLatin1("%1,%2%3");
743 double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
744 minutes += (double)numeratorSeconds / 60.0;
745 QString minutesString = QString::number(minutes, 'f', 8);
746
747 while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
748 {
749 minutesString.chop(1);
750 }
751
752 coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference);
753 }
754 else if (denominatorDegrees == 0 ||
755 denominatorMinutes == 0 ||
756 denominatorSeconds == 0)
757 {
758 // Invalid. 1/0 is everything but 0. As is 0/0.
759 return QString();
760 }
761 else
762 {
763 // use form DDD,MM.mmk
764 coordinate = QString::fromLatin1("%1,%2%3");
765 double degrees = (double)numeratorDegrees / (double)denominatorDegrees;
766 double wholeDegrees = trunc(degrees);
767 double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
768 minutes += (degrees - wholeDegrees) * 60.0;
769 minutes += ((double)numeratorSeconds / (double)denominatorSeconds) / 60.0;
770 QString minutesString = QString::number(minutes, 'f', 8);
771
772 while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
773 {
774 minutesString.chop(1);
775 }
776
777 coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference);
778 }
779
780 return coordinate;
781}
782
783QString KExiv2::convertToGPSCoordinateString(const bool isLatitude, double coordinate)
784{
785 if (coordinate < -360.0 || coordinate > 360.0)
786 return QString();
787
788 QString coordinateString;
789
790 char directionReference;
791
792 if (isLatitude)
793 {
794 if (coordinate < 0)
795 directionReference = 'S';
796 else
797 directionReference = 'N';
798 }
799 else
800 {
801 if (coordinate < 0)
802 directionReference = 'W';
803 else
804 directionReference = 'E';
805 }
806
807 // remove sign
808 coordinate = fabs(coordinate);
809
810 int degrees = (int)floor(coordinate);
811 // get fractional part
812 coordinate = coordinate - (double)(degrees);
813 // To minutes
814 double minutes = coordinate * 60.0;
815
816 // use form DDD,MM.mmk
817 coordinateString = QString::fromLatin1("%1,%2%3");
818 coordinateString = coordinateString.arg(degrees);
819 coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference);
820
821 return coordinateString;
822}
823
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{
830 if (gpsString.isEmpty())
831 return false;
832
833 *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
834 QString coordinate = gpsString.left(gpsString.length() - 1);
835 QStringList parts = coordinate.split(QString::fromLatin1(","));
836
837 if (parts.size() == 2)
838 {
839 // form DDD,MM.mmk
840 *denominatorDegrees = 1;
841 *denominatorMinutes = 1000000;
842 *denominatorSeconds = 1;
843
844 *numeratorDegrees = parts[0].toLong();
845
846 double minutes = parts[1].toDouble();
847 minutes *= 1000000;
848
849 *numeratorMinutes = (long)round(minutes);
850 *numeratorSeconds = 0;
851
852 return true;
853 }
854 else if (parts.size() == 3)
855 {
856 // use form DDD,MM,SSk
857 *denominatorDegrees = 1;
858 *denominatorMinutes = 1;
859 *denominatorSeconds = 1;
860
861 *numeratorDegrees = parts[0].toLong();
862 *numeratorMinutes = parts[1].toLong();
863 *numeratorSeconds = parts[2].toLong();
864
865 return true;
866 }
867 else
868 {
869 return false;
870 }
871}
872
873bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees)
874{
875 if (gpsString.isEmpty())
876 return false;
877
878 char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
879 QString coordinate = gpsString.left(gpsString.length() - 1);
880 QStringList parts = coordinate.split(QString::fromLatin1(","));
881
882 if (parts.size() == 2)
883 {
884 // form DDD,MM.mmk
885 *degrees = parts[0].toLong();
886 *degrees += parts[1].toDouble() / 60.0;
887
888 if (directionReference == 'W' || directionReference == 'S')
889 *degrees *= -1.0;
890
891 return true;
892 }
893 else if (parts.size() == 3)
894 {
895 // use form DDD,MM,SSk
896
897 *degrees = parts[0].toLong();
898 *degrees += parts[1].toLong() / 60.0;
899 *degrees += parts[2].toLong() / 3600.0;
900
901 if (directionReference == 'W' || directionReference == 'S')
902 *degrees *= -1.0;
903
904 return true;
905 }
906 else
907 {
908 return false;
909 }
910}
911
913 int* const degrees, int* const minutes,
914 double* const seconds, char* const directionReference)
915{
916 if (gpsString.isEmpty())
917 return false;
918
919 *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
920 QString coordinate = gpsString.left(gpsString.length() - 1);
921 QStringList parts = coordinate.split(QString::fromLatin1(","));
922
923 if (parts.size() == 2)
924 {
925 // form DDD,MM.mmk
926 *degrees = parts[0].toInt();
927 double fractionalMinutes = parts[1].toDouble();
928 *minutes = (int)trunc(fractionalMinutes);
929 *seconds = (fractionalMinutes - (double)(*minutes)) * 60.0;
930
931 return true;
932 }
933 else if (parts.size() == 3)
934 {
935 // use form DDD,MM,SSk
936 *degrees = parts[0].toInt();
937 *minutes = parts[1].toInt();
938 *seconds = (double)parts[2].toInt();
939
940 return true;
941 }
942 else
943 {
944 return false;
945 }
946}
947
948void KExiv2::convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
949 int* const degrees, int* const minutes,
950 double* const seconds, char* const directionReference)
951{
952 if (isLatitude)
953 {
954 if (coordinate < 0)
955 *directionReference = 'S';
956 else
957 *directionReference = 'N';
958 }
959 else
960 {
961 if (coordinate < 0)
962 *directionReference = 'W';
963 else
964 *directionReference = 'E';
965 }
966
967 // remove sign
968 coordinate = fabs(coordinate);
969 *degrees = (int)floor(coordinate);
970 // get fractional part
971 coordinate = coordinate - (double)(*degrees);
972 // To minutes
973 coordinate *= 60.0;
974 *minutes = (int)floor(coordinate);
975 // get fractional part
976 coordinate = coordinate - (double)(*minutes);
977 // To seconds
978 coordinate *= 60.0;
979 *seconds = coordinate;
980}
981
982} // NameSpace KExiv2Iface
QString getXmpTagString(const char *xmpTagName, bool escapeCR=true) const
Get a Xmp tag content like a string.
static bool convertFromGPSCoordinateString(const QString &coordinate, long int *const numeratorDegrees, long int *const denominatorDegrees, long int *const numeratorMinutes, long int *const denominatorMinutes, long int *const numeratorSeconds, long int *const denominatorSeconds, char *const directionReference)
Converts a GPSCoordinate string as defined by XMP to three rationals and the direction reference.
static void convertToRationalSmallDenominator(const double number, long int *const numerator, long int *const denominator)
This method convert a 'number' to a rational value, returned in 'numerator' and 'denominator' paramet...
virtual bool setProgramId(bool on=true) const
Re-implement this method to set automatically the Program Name and Program Version information in Exi...
Definition kexiv2.cpp:526
QString getGPSLatitudeString() const
Get GPS location information set in the image, in the GPSCoordinate format as described in the XMP sp...
static QString convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees, const long int numeratorMinutes, const long int denominatorMinutes, const long int numeratorSeconds, long int denominatorSeconds, const char directionReference)
Converts a GPS position stored as rationals in Exif to the form described as GPSCoordinate in the XMP...
static void convertToRational(const double number, long int *const numerator, long int *const denominator, const int rounding)
This method converts 'number' to a rational value, returned in the 'numerator' and 'denominator' para...
bool initializeGPSInfo(const bool setProgramName)
Make sure all static required GPS EXIF and XMP tags exist.
static bool convertToUserPresentableNumbers(const QString &coordinate, int *const degrees, int *const minutes, double *const seconds, char *const directionReference)
Converts a GPSCoordinate string to user presentable numbers, integer degrees and minutes and double f...
bool getGPSInfo(double &altitude, double &latitude, double &longitude) const
Get all GPS location information set in image.
Definition kexiv2gps.cpp:28
bool setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName=true)
Set all GPS location information into image.
bool getGPSAltitude(double *const altitude) const
Get GPS altitude information, in meters, relative to sea level (positive sign above sea level)
bool removeGPSInfo(const bool setProgramName=true)
Remove all Exif tags relevant of GPS location information.
bool getGPSLatitudeNumber(double *const latitude) const
Get GPS location information set in the image, as a double floating point number as in degrees where ...
Definition kexiv2gps.cpp:43
QByteArray getExifTagData(const char *exifTagName) const
Get an Exif tag content like a bytes array.
bool removeXmpTag(const char *xmpTagName, bool setProgramName=true) const
Remove the Xmp tag 'xmpTagName' from Xmp metadata.
bool getGPSLongitudeNumber(double *const longitude) const
bool setXmpTagString(const char *xmpTagName, const QString &value, bool setProgramName=true) const
Set a Xmp tag content using a string.
KExiv2Iface - Exiv2 library interface.
Definition kexiv2.cpp:17
bool isEmpty() const const
char32_t toUpper(char32_t ucs4)
void append(QList< T > &&value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype size() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
QString left(qsizetype n) const const
qsizetype length() const const
QString number(double n, char format, int precision)
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
double toDouble(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:16:02 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.