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 
25 namespace KExiv2Iface
26 {
27 
28 bool 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 
43 bool 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 
126 bool 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 
222 bool 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 
306 QString KExiv2::getGPSLongitudeString() const
307 {
308  double longitude;
309 
310  if (!getGPSLongitudeNumber(&longitude))
311  return QString();
312 
313  return convertToGPSCoordinateString(false, longitude);
314 }
315 
316 bool 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  Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
329  value->read("2 0 0 0");
330  d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());
331 
332  // Datum: the datum of the measured data. If not given, we insert WGS-84.
333  d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
334 
335 #ifdef _XMP_SUPPORT_
336  setXmpTagString("Xmp.exif.GPSVersionID", QString::fromLatin1("2.0.0.0"), false);
337  setXmpTagString("Xmp.exif.GPSMapDatum", QString::fromLatin1("WGS-84"), false);
338 #endif // _XMP_SUPPORT_
339 
340  return true;
341  }
342  catch( Exiv2::Error& e )
343  {
344  d->printExiv2ExceptionError(QString::fromLatin1("Cannot initialize GPS data using Exiv2 "), e);
345  }
346  catch(...)
347  {
348  qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
349  }
350 
351  return false;
352 }
353 
354 bool KExiv2::setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName)
355 {
356  return setGPSInfo(&altitude, latitude, longitude, setProgramName);
357 }
358 
359 bool KExiv2::setGPSInfo(const double* const altitude, const double latitude, const double longitude, const bool setProgramName)
360 {
361  if (!setProgramId(setProgramName))
362  return false;
363 
364  try
365  {
366  // In first, we need to clean up all existing GPS info.
367  removeGPSInfo();
368 
369  // now re-initialize the GPS info:
370  if (!initializeGPSInfo(setProgramName))
371  return false;
372 
373  char scratchBuf[100];
374  long int nom, denom;
375  long int deg, min;
376 
377  // Now start adding data.
378 
379  // ALTITUDE.
380  if (altitude)
381  {
382  // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
383  Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
384 
385  if ((*altitude) >= 0) value->read("0");
386  else value->read("1");
387 
388  d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
389 
390  // And the actual altitude, as absolute value..
391  convertToRational(fabs(*altitude), &nom, &denom, 4);
392  snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
393  d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
394 
395 #ifdef _XMP_SUPPORT_
396  setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QString::fromLatin1("0") : QString::fromLatin1("1"), false);
397  setXmpTagString("Xmp.exif.GPSAltitude", QString::fromLatin1(scratchBuf), false);
398 #endif // _XMP_SUPPORT_
399  }
400 
401  // LATITUDE
402  // Latitude reference:
403  // latitude < 0 : "S"
404  // latitude > 0 : "N"
405  //
406  d->exifMetadata()["Exif.GPSInfo.GPSLatitudeRef"] = (latitude < 0 ) ? "S" : "N";
407 
408  // Now the actual latitude itself.
409  // This is done as three rationals.
410  // I choose to do it as:
411  // dd/1 - degrees.
412  // mmmm/100 - minutes
413  // 0/1 - seconds
414  // Exif standard says you can do it with minutes
415  // as mm/1 and then seconds as ss/1, but its
416  // (slightly) more accurate to do it as
417  // mmmm/100 than to split it.
418  // We also absolute the value (with fabs())
419  // as the sign is encoded in LatRef.
420  // Further note: original code did not translate between
421  // dd.dddddd to dd mm.mm - that's why we now multiply
422  // by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
423  deg = (int)floor(fabs(latitude)); // Slice off after decimal.
424  min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
425  snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
426  d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
427 
428 #ifdef _XMP_SUPPORT_
429  /** @todo The XMP spec does not mention Xmp.exif.GPSLatitudeRef,
430  * because the reference is included in Xmp.exif.GPSLatitude.
431  * Is there a historic reason for writing it anyway?
432  */
433  setXmpTagString("Xmp.exif.GPSLatitudeRef", (latitude < 0) ? QString::fromLatin1("S") : QString::fromLatin1("N"), false);
434  setXmpTagString("Xmp.exif.GPSLatitude", convertToGPSCoordinateString(true, latitude), false);
435 #endif // _XMP_SUPPORT_
436 
437  // LONGITUDE
438  // Longitude reference:
439  // longitude < 0 : "W"
440  // longitude > 0 : "E"
441  d->exifMetadata()["Exif.GPSInfo.GPSLongitudeRef"] = (longitude < 0 ) ? "W" : "E";
442 
443  // Now the actual longitude itself.
444  // This is done as three rationals.
445  // I choose to do it as:
446  // dd/1 - degrees.
447  // mmmm/100 - minutes
448  // 0/1 - seconds
449  // Exif standard says you can do it with minutes
450  // as mm/1 and then seconds as ss/1, but its
451  // (slightly) more accurate to do it as
452  // mmmm/100 than to split it.
453  // We also absolute the value (with fabs())
454  // as the sign is encoded in LongRef.
455  // Further note: original code did not translate between
456  // dd.dddddd to dd mm.mm - that's why we now multiply
457  // by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
458  deg = (int)floor(fabs(longitude)); // Slice off after decimal.
459  min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
460  snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
461  d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
462 
463 #ifdef _XMP_SUPPORT_
464  /** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
465  * because the reference is included in Xmp.exif.GPSLongitude.
466  * Is there a historic reason for writing it anyway?
467  */
468  setXmpTagString("Xmp.exif.GPSLongitudeRef", (longitude < 0) ? QString::fromLatin1("W") : QString::fromLatin1("E"), false);
469  setXmpTagString("Xmp.exif.GPSLongitude", convertToGPSCoordinateString(false, longitude), false);
470 #endif // _XMP_SUPPORT_
471 
472  return true;
473  }
474  catch( Exiv2::Error& e )
475  {
476  d->printExiv2ExceptionError(QString::fromLatin1("Cannot set Exif GPS tag using Exiv2 "), e);
477  }
478  catch(...)
479  {
480  qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
481  }
482 
483  return false;
484 }
485 
486 bool KExiv2::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude, const bool setProgramName)
487 {
488  double longitudeValue, latitudeValue;
489 
490  if (!convertFromGPSCoordinateString(latitude, &latitudeValue))
491  return false;
492 
493  if (!convertFromGPSCoordinateString(longitude, &longitudeValue))
494  return false;
495 
496  return setGPSInfo(&altitude, latitudeValue, longitudeValue, setProgramName);
497 }
498 
499 bool KExiv2::removeGPSInfo(const bool setProgramName)
500 {
501  if (!setProgramId(setProgramName))
502  return false;
503 
504  try
505  {
506  QStringList gpsTagsKeys;
507 
508  for (Exiv2::ExifData::iterator it = d->exifMetadata().begin();
509  it != d->exifMetadata().end(); ++it)
510  {
511  QString key = QString::fromLocal8Bit(it->key().c_str());
512 
513  if (key.section(QString::fromLatin1("."), 1, 1) == QString::fromLatin1("GPSInfo"))
514  gpsTagsKeys.append(key);
515  }
516 
517  for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
518  {
519  Exiv2::ExifKey gpsKey((*it2).toLatin1().constData());
520  Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey);
521 
522  if (it3 != d->exifMetadata().end())
523  d->exifMetadata().erase(it3);
524  }
525 
526 #ifdef _XMP_SUPPORT_
527  /** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
528  * and Xmp.exif.GPSLatitudeRef. But because we write them in setGPSInfo(),
529  * we should also remove them here.
530  */
531  removeXmpTag("Xmp.exif.GPSLatitudeRef", false);
532  removeXmpTag("Xmp.exif.GPSLongitudeRef", false);
533  removeXmpTag("Xmp.exif.GPSVersionID", false);
534  removeXmpTag("Xmp.exif.GPSLatitude", false);
535  removeXmpTag("Xmp.exif.GPSLongitude", false);
536  removeXmpTag("Xmp.exif.GPSAltitudeRef", false);
537  removeXmpTag("Xmp.exif.GPSAltitude", false);
538  removeXmpTag("Xmp.exif.GPSTimeStamp", false);
539  removeXmpTag("Xmp.exif.GPSSatellites", false);
540  removeXmpTag("Xmp.exif.GPSStatus", false);
541  removeXmpTag("Xmp.exif.GPSMeasureMode", false);
542  removeXmpTag("Xmp.exif.GPSDOP", false);
543  removeXmpTag("Xmp.exif.GPSSpeedRef", false);
544  removeXmpTag("Xmp.exif.GPSSpeed", false);
545  removeXmpTag("Xmp.exif.GPSTrackRef", false);
546  removeXmpTag("Xmp.exif.GPSTrack", false);
547  removeXmpTag("Xmp.exif.GPSImgDirectionRef", false);
548  removeXmpTag("Xmp.exif.GPSImgDirection", false);
549  removeXmpTag("Xmp.exif.GPSMapDatum", false);
550  removeXmpTag("Xmp.exif.GPSDestLatitude", false);
551  removeXmpTag("Xmp.exif.GPSDestLongitude", false);
552  removeXmpTag("Xmp.exif.GPSDestBearingRef", false);
553  removeXmpTag("Xmp.exif.GPSDestBearing", false);
554  removeXmpTag("Xmp.exif.GPSDestDistanceRef", false);
555  removeXmpTag("Xmp.exif.GPSDestDistance", false);
556  removeXmpTag("Xmp.exif.GPSProcessingMethod", false);
557  removeXmpTag("Xmp.exif.GPSAreaInformation", false);
558  removeXmpTag("Xmp.exif.GPSDifferential", false);
559 #endif // _XMP_SUPPORT_
560 
561  return true;
562  }
563  catch( Exiv2::Error& e )
564  {
565  d->printExiv2ExceptionError(QString::fromLatin1("Cannot remove Exif GPS tag using Exiv2 "), e);
566  }
567  catch(...)
568  {
569  qCCritical(LIBKEXIV2_LOG) << "Default exception from Exiv2";
570  }
571 
572  return false;
573 }
574 
575 void KExiv2::convertToRational(const double number, long int* const numerator,
576  long int* const denominator, const int rounding)
577 {
578  // This function converts the given decimal number
579  // to a rational (fractional) number.
580  //
581  // Examples in comments use Number as 25.12345, Rounding as 4.
582 
583  // Split up the number.
584  double whole = trunc(number);
585  double fractional = number - whole;
586 
587  // Calculate the "number" used for rounding.
588  // This is 10^Digits - ie, 4 places gives us 10000.
589  double rounder = pow(10.0, rounding);
590 
591  // Round the fractional part, and leave the number
592  // as greater than 1.
593  // To do this we: (for example)
594  // 0.12345 * 10000 = 1234.5
595  // floor(1234.5) = 1234 - now bigger than 1 - ready...
596  fractional = round(fractional * rounder);
597 
598  // Convert the whole thing to a fraction.
599  // Fraction is:
600  // (25 * 10000) + 1234 251234
601  // ------------------- = ------ = 25.1234
602  // 10000 10000
603  double numTemp = (whole * rounder) + fractional;
604  double denTemp = rounder;
605 
606  // Now we should reduce until we can reduce no more.
607 
608  // Try simple reduction...
609  // if Num
610  // ----- = integer out then....
611  // Den
612  if (trunc(numTemp / denTemp) == (numTemp / denTemp))
613  {
614  // Divide both by Denominator.
615  numTemp /= denTemp;
616  denTemp /= denTemp;
617  }
618 
619  // And, if that fails, brute force it.
620  while (1)
621  {
622  // Jump out if we can't integer divide one.
623  if ((numTemp / 2) != trunc(numTemp / 2)) break;
624  if ((denTemp / 2) != trunc(denTemp / 2)) break;
625  // Otherwise, divide away.
626  numTemp /= 2;
627  denTemp /= 2;
628  }
629 
630  // Copy out the numbers.
631  *numerator = (int)numTemp;
632  *denominator = (int)denTemp;
633 }
634 
635 void KExiv2::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator)
636 {
637  // This function converts the given decimal number
638  // to a rational (fractional) number.
639  //
640  // This method, in contrast to the method above, will retrieve the smallest possible
641  // denominator. It is tested to retrieve the correct value for 1/x, with 0 < x <= 1000000.
642  // Note: This requires double precision, storing in float breaks some numbers (49, 59, 86,...)
643 
644  // Split up the number.
645  double whole = trunc(number);
646  double fractional = number - whole;
647 
648  /*
649  * Find best rational approximation to a double
650  * by C.B. Falconer, 2006-09-07. Released to public domain.
651  *
652  * Newsgroups: comp.lang.c, comp.programming
653  * From: CBFalconer <[email protected]>
654  * Date: Thu, 07 Sep 2006 17:35:30 -0400
655  * Subject: Rational approximations
656  */
657  int lastnum = 500; // this is _not_ the largest possible denominator
658  long int num, approx, bestnum=0, bestdenom=1;
659  double value, error, leasterr, criterion;
660 
661  value = fractional;
662 
663  if (value == 0.0)
664  {
665  *numerator = (long int)whole;
666  *denominator = 1;
667  return;
668  }
669 
670  criterion = 2 * value * DBL_EPSILON;
671 
672  for (leasterr = value, num = 1; num < lastnum; ++num)
673  {
674  approx = (int)(num / value + 0.5);
675  error = fabs((double)num / approx - value);
676 
677  if (error < leasterr)
678  {
679  bestnum = num;
680  bestdenom = approx;
681  leasterr = error;
682 
683  if (leasterr <= criterion) break;
684  }
685  }
686 
687  // add whole number part
688  if (bestdenom * whole > (double)INT_MAX)
689  {
690  // In some cases, we would generate an integer overflow.
691  // Fall back to Gilles's code which is better suited for such numbers.
692  convertToRational(number, numerator, denominator, 5);
693  }
694  else
695  {
696  bestnum += bestdenom * (long int)whole;
697  *numerator = bestnum;
698  *denominator = bestdenom;
699  }
700 }
701 
702 QString KExiv2::convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees,
703  const long int numeratorMinutes, const long int denominatorMinutes,
704  const long int numeratorSeconds, long int denominatorSeconds,
705  const char directionReference)
706 {
707  /**
708  * Precision:
709  * A second at sea level measures 30m for our purposes, a minute 1800m.
710  * (for more details, see https://en.wikipedia.org/wiki/Geographic_coordinate_system)
711  * This means with a decimal precision of 8 for minutes we get +/-0,018mm.
712  * (if I calculated correctly)
713  */
714 
715  QString coordinate;
716 
717  // be relaxed with seconds of 0/0
718  if (denominatorSeconds == 0 && numeratorSeconds == 0)
719  denominatorSeconds = 1;
720 
721  if (denominatorDegrees == 1 &&
722  denominatorMinutes == 1 &&
723  denominatorSeconds == 1)
724  {
725  // use form DDD,MM,SSk
726  coordinate = QString::fromLatin1("%1,%2,%3%4");
727  coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference);
728  }
729  else if (denominatorDegrees == 1 &&
730  denominatorMinutes == 100 &&
731  denominatorSeconds == 1)
732  {
733  // use form DDD,MM.mmk
734  coordinate = QString::fromLatin1("%1,%2%3");
735  double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
736  minutes += (double)numeratorSeconds / 60.0;
737  QString minutesString = QString::number(minutes, 'f', 8);
738 
739  while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
740  {
741  minutesString.chop(1);
742  }
743 
744  coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference);
745  }
746  else if (denominatorDegrees == 0 ||
747  denominatorMinutes == 0 ||
748  denominatorSeconds == 0)
749  {
750  // Invalid. 1/0 is everything but 0. As is 0/0.
751  return QString();
752  }
753  else
754  {
755  // use form DDD,MM.mmk
756  coordinate = QString::fromLatin1("%1,%2%3");
757  double degrees = (double)numeratorDegrees / (double)denominatorDegrees;
758  double wholeDegrees = trunc(degrees);
759  double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
760  minutes += (degrees - wholeDegrees) * 60.0;
761  minutes += ((double)numeratorSeconds / (double)denominatorSeconds) / 60.0;
762  QString minutesString = QString::number(minutes, 'f', 8);
763 
764  while (minutesString.endsWith(QString::fromLatin1("0")) && !minutesString.endsWith(QString::fromLatin1(".0")))
765  {
766  minutesString.chop(1);
767  }
768 
769  coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference);
770  }
771 
772  return coordinate;
773 }
774 
775 QString KExiv2::convertToGPSCoordinateString(const bool isLatitude, double coordinate)
776 {
777  if (coordinate < -360.0 || coordinate > 360.0)
778  return QString();
779 
780  QString coordinateString;
781 
782  char directionReference;
783 
784  if (isLatitude)
785  {
786  if (coordinate < 0)
787  directionReference = 'S';
788  else
789  directionReference = 'N';
790  }
791  else
792  {
793  if (coordinate < 0)
794  directionReference = 'W';
795  else
796  directionReference = 'E';
797  }
798 
799  // remove sign
800  coordinate = fabs(coordinate);
801 
802  int degrees = (int)floor(coordinate);
803  // get fractional part
804  coordinate = coordinate - (double)(degrees);
805  // To minutes
806  double minutes = coordinate * 60.0;
807 
808  // use form DDD,MM.mmk
809  coordinateString = QString::fromLatin1("%1,%2%3");
810  coordinateString = coordinateString.arg(degrees);
811  coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference);
812 
813  return coordinateString;
814 }
815 
817  long int* const numeratorDegrees, long int* const denominatorDegrees,
818  long int* const numeratorMinutes, long int* const denominatorMinutes,
819  long int* const numeratorSeconds, long int* const denominatorSeconds,
820  char* const directionReference)
821 {
822  if (gpsString.isEmpty())
823  return false;
824 
825  *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
826  QString coordinate = gpsString.left(gpsString.length() - 1);
827  QStringList parts = coordinate.split(QString::fromLatin1(","));
828 
829  if (parts.size() == 2)
830  {
831  // form DDD,MM.mmk
832  *denominatorDegrees = 1;
833  *denominatorMinutes = 1000000;
834  *denominatorSeconds = 1;
835 
836  *numeratorDegrees = parts[0].toLong();
837 
838  double minutes = parts[1].toDouble();
839  minutes *= 1000000;
840 
841  *numeratorMinutes = (long)round(minutes);
842  *numeratorSeconds = 0;
843 
844  return true;
845  }
846  else if (parts.size() == 3)
847  {
848  // use form DDD,MM,SSk
849  *denominatorDegrees = 1;
850  *denominatorMinutes = 1;
851  *denominatorSeconds = 1;
852 
853  *numeratorDegrees = parts[0].toLong();
854  *numeratorMinutes = parts[1].toLong();
855  *numeratorSeconds = parts[2].toLong();
856 
857  return true;
858  }
859  else
860  {
861  return false;
862  }
863 }
864 
865 bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees)
866 {
867  if (gpsString.isEmpty())
868  return false;
869 
870  char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
871  QString coordinate = gpsString.left(gpsString.length() - 1);
872  QStringList parts = coordinate.split(QString::fromLatin1(","));
873 
874  if (parts.size() == 2)
875  {
876  // form DDD,MM.mmk
877  *degrees = parts[0].toLong();
878  *degrees += parts[1].toDouble() / 60.0;
879 
880  if (directionReference == 'W' || directionReference == 'S')
881  *degrees *= -1.0;
882 
883  return true;
884  }
885  else if (parts.size() == 3)
886  {
887  // use form DDD,MM,SSk
888 
889  *degrees = parts[0].toLong();
890  *degrees += parts[1].toLong() / 60.0;
891  *degrees += parts[2].toLong() / 3600.0;
892 
893  if (directionReference == 'W' || directionReference == 'S')
894  *degrees *= -1.0;
895 
896  return true;
897  }
898  else
899  {
900  return false;
901  }
902 }
903 
905  int* const degrees, int* const minutes,
906  double* const seconds, char* const directionReference)
907 {
908  if (gpsString.isEmpty())
909  return false;
910 
911  *directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
912  QString coordinate = gpsString.left(gpsString.length() - 1);
913  QStringList parts = coordinate.split(QString::fromLatin1(","));
914 
915  if (parts.size() == 2)
916  {
917  // form DDD,MM.mmk
918  *degrees = parts[0].toInt();
919  double fractionalMinutes = parts[1].toDouble();
920  *minutes = (int)trunc(fractionalMinutes);
921  *seconds = (fractionalMinutes - (double)(*minutes)) * 60.0;
922 
923  return true;
924  }
925  else if (parts.size() == 3)
926  {
927  // use form DDD,MM,SSk
928  *degrees = parts[0].toInt();
929  *minutes = parts[1].toInt();
930  *seconds = (double)parts[2].toInt();
931 
932  return true;
933  }
934  else
935  {
936  return false;
937  }
938 }
939 
940 void KExiv2::convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
941  int* const degrees, int* const minutes,
942  double* const seconds, char* const directionReference)
943 {
944  if (isLatitude)
945  {
946  if (coordinate < 0)
947  *directionReference = 'S';
948  else
949  *directionReference = 'N';
950  }
951  else
952  {
953  if (coordinate < 0)
954  *directionReference = 'W';
955  else
956  *directionReference = 'E';
957  }
958 
959  // remove sign
960  coordinate = fabs(coordinate);
961  *degrees = (int)floor(coordinate);
962  // get fractional part
963  coordinate = coordinate - (double)(*degrees);
964  // To minutes
965  coordinate *= 60.0;
966  *minutes = (int)floor(coordinate);
967  // get fractional part
968  coordinate = coordinate - (double)(*minutes);
969  // To seconds
970  coordinate *= 60.0;
971  *seconds = coordinate;
972 }
973 
974 } // NameSpace KExiv2Iface
KExiv2Iface - Exiv2 library interface.
Definition: kexiv2.cpp:16
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
bool setXmpTagString(const char *xmpTagName, const QString &value, bool setProgramName=true) const
Set a Xmp tag content using a string.
Definition: kexiv2xmp.cpp:367
static void convertToRationalSmallDenominator(const double number, long int *const numerator, long int *const denominator)
This method convert a &#39;number&#39; to a rational value, returned in &#39;numerator&#39; and &#39;denominator&#39; paramet...
Definition: kexiv2gps.cpp:635
bool removeGPSInfo(const bool setProgramName=true)
Remove all Exif tags relevant of GPS location information.
Definition: kexiv2gps.cpp:499
bool isEmpty() const const
void chop(int n)
double toDouble(bool *ok) const const
int size() const const
QString number(int n, int base)
QString fromLocal8Bit(const char *str, int size)
void append(const T &value)
bool getGPSInfo(double &altitude, double &latitude, double &longitude) const
Get all GPS location information set in image.
Definition: kexiv2gps.cpp:28
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...
Definition: kexiv2gps.cpp:702
bool isEmpty() const const
static void convertToRational(const double number, long int *const numerator, long int *const denominator, const int rounding)
This method converts &#39;number&#39; to a rational value, returned in the &#39;numerator&#39; and &#39;denominator&#39; para...
Definition: kexiv2gps.cpp:575
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool removeXmpTag(const char *xmpTagName, bool setProgramName=true) const
Remove the Xmp tag &#39;xmpTagName&#39; from Xmp metadata.
Definition: kexiv2xmp.cpp:1101
QString getGPSLatitudeString() const
Get GPS location information set in the image, in the GPSCoordinate format as described in the XMP sp...
Definition: kexiv2gps.cpp:296
bool getGPSAltitude(double *const altitude) const
Get GPS altitude information, in meters, relative to sea level (positive sign above sea level) ...
Definition: kexiv2gps.cpp:222
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:506
char toLatin1() const const
bool setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName=true)
Set all GPS location information into image.
Definition: kexiv2gps.cpp:354
QString getXmpTagString(const char *xmpTagName, bool escapeCR=true) const
Get a Xmp tag content like a string.
Definition: kexiv2xmp.cpp:326
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...
Definition: kexiv2gps.cpp:816
QByteArray getExifTagData(const char *exifTagName) const
Get an Exif tag content like a bytes array.
Definition: kexiv2exif.cpp:694
bool getGPSLongitudeNumber(double *const longitude) const
Definition: kexiv2gps.cpp:126
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
int length() const const
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...
Definition: kexiv2gps.cpp:904
QString section(QChar sep, int start, int end, QString::SectionFlags flags) const const
QString left(int n) const const
QString fromLatin1(const char *str, int size)
bool initializeGPSInfo(const bool setProgramName)
Make sure all static required GPS EXIF and XMP tags exist.
Definition: kexiv2gps.cpp:316
QList::const_iterator constEnd() const const
QList::const_iterator constBegin() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Tue Dec 7 2021 22:32:53 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.