kdelibs/kexiv2/libkexiv2/kexiv2gps.cpp
Ivailo Monev a0dbbaabdf generic: rename merged libraries directories for consistency
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2015-10-29 13:57:28 +02:00

989 lines
31 KiB
C++

/** ===========================================================
* @file
*
* This file is a part of digiKam project
* <a href="http://www.digikam.org">http://www.digikam.org</a>
*
* @date 2006-09-15
* @brief GPS manipulation methods
*
* @author Copyright (C) 2006-2014 by Gilles Caulier
* <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
* @author Copyright (C) 2006-2012 by Marcel Wiesweg
* <a href="mailto:marcel dot wiesweg at gmx dot de">marcel dot wiesweg at gmx dot de</a>
* @author Copyright (C) 2010-2012 by Michael G. Hansen
* <a href="mailto:mike at mghansen dot de">mike at mghansen dot de</a>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "kexiv2.h"
#include "kexiv2_p.h"
// C++ includes
#include <climits>
#include <cmath>
#include <math.h>
namespace KExiv2Iface
{
bool KExiv2::getGPSInfo(double& altitude, double& latitude, double& longitude) const
{
// Some GPS device do not set Altitude. So a valid GPS position can be with a zero value.
// No need to check return value.
getGPSAltitude(&altitude);
if (!getGPSLatitudeNumber(&latitude))
return false;
if (!getGPSLongitudeNumber(&longitude))
return false;
return true;
}
bool KExiv2::getGPSLatitudeNumber(double* const latitude) const
{
try
{
*latitude=0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLatitude"), latitude) )
return true;
// Now try to get the reference from Exif.
const QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
if (!latRef.isEmpty())
{
Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::iterator it = exifData.findKey(exifKey);
if (it != exifData.end() && (*it).count() == 3)
{
// Latitude decoding from Exif.
double num, den, min, sec;
num = (double)((*it).toRational(0).first);
den = (double)((*it).toRational(0).second);
if (den == 0)
return false;
*latitude = num/den;
num = (double)((*it).toRational(1).first);
den = (double)((*it).toRational(1).second);
if (den == 0)
return false;
min = num/den;
if (min != -1.0)
*latitude = *latitude + min/60.0;
num = (double)((*it).toRational(2).first);
den = (double)((*it).toRational(2).second);
if (den == 0)
{
// be relaxed and accept 0/0 seconds. See #246077.
if (num == 0)
den = 1;
else
return false;
}
sec = num/den;
if (sec != -1.0)
*latitude = *latitude + sec/3600.0;
}
else
{
return false;
}
if (latRef[0] == 'S')
*latitude *= -1.0;
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot get GPS tag using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
bool KExiv2::getGPSLongitudeNumber(double* const longitude) const
{
try
{
*longitude=0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
if ( convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLongitude"), longitude) )
return true;
// Now try to get the reference from Exif.
const QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
if (!lngRef.isEmpty())
{
// Longitude decoding from Exif.
Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::iterator it = exifData.findKey(exifKey2);
if (it != exifData.end() && (*it).count() == 3)
{
/// @todo Decoding of latitude and longitude works in the same way,
/// code here can be put in a separate function
double num, den;
num = (double)((*it).toRational(0).first);
den = (double)((*it).toRational(0).second);
if (den == 0)
{
return false;
}
*longitude = num/den;
num = (double)((*it).toRational(1).first);
den = (double)((*it).toRational(1).second);
if (den == 0)
{
return false;
}
const double min = num/den;
if (min != -1.0)
{
*longitude = *longitude + min/60.0;
}
num = (double)((*it).toRational(2).first);
den = (double)((*it).toRational(2).second);
if (den == 0)
{
// be relaxed and accept 0/0 seconds. See #246077.
if (num == 0)
den = 1;
else
return false;
}
const double sec = num/den;
if (sec != -1.0)
{
*longitude = *longitude + sec/3600.0;
}
}
else
{
return false;
}
if (lngRef[0] == 'W')
{
*longitude *= -1.0;
}
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot get GPS tag using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
bool KExiv2::getGPSAltitude(double* const altitude) const
{
try
{
double num, den;
*altitude=0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
const QString altRefXmp = getXmpTagString("Xmp.exif.GPSAltitudeRef");
if (!altRefXmp.isEmpty())
{
const QString altXmp = getXmpTagString("Xmp.exif.GPSAltitude");
if (!altXmp.isEmpty())
{
num = altXmp.section('/', 0, 0).toDouble();
den = altXmp.section('/', 1, 1).toDouble();
if (den == 0)
return false;
*altitude = num/den;
if (altRefXmp == QString("1"))
*altitude *= -1.0;
return true;
}
}
// Get the reference from Exif (above/below sea level)
const QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");
if (!altRef.isEmpty())
{
// Altitude decoding from Exif.
Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::iterator it = exifData.findKey(exifKey3);
if (it != exifData.end() && (*it).count())
{
num = (double)((*it).toRational(0).first);
den = (double)((*it).toRational(0).second);
if (den == 0)
return false;
*altitude = num/den;
}
else
{
return false;
}
if (altRef[0] == '1')
*altitude *= -1.0;
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot get GPS tag using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
QString KExiv2::getGPSLatitudeString() const
{
double latitude;
if (!getGPSLatitudeNumber(&latitude))
return QString();
return convertToGPSCoordinateString(true, latitude);
}
QString KExiv2::getGPSLongitudeString() const
{
double longitude;
if (!getGPSLongitudeNumber(&longitude))
return QString();
return convertToGPSCoordinateString(false, longitude);
}
bool KExiv2::initializeGPSInfo(const bool setProgramName)
{
if (!setProgramId(setProgramName))
return false;
try
{
// TODO: what happens if these already exist?
// Do all the easy constant ones first.
// GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
// (and, must be present).
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
value->read("2 0 0 0");
d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());
// Datum: the datum of the measured data. If not given, we insert WGS-84.
d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
#ifdef _XMP_SUPPORT_
setXmpTagString("Xmp.exif.GPSVersionID", QString("2.0.0.0"), false);
setXmpTagString("Xmp.exif.GPSMapDatum", QString("WGS-84"), false);
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot initialize GPS data using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
bool KExiv2::setGPSInfo(const double altitude, const double latitude, const double longitude, const bool setProgramName)
{
return setGPSInfo(&altitude, latitude, longitude, setProgramName);
}
bool KExiv2::setGPSInfo(const double* const altitude, const double latitude, const double longitude, const bool setProgramName)
{
if (!setProgramId(setProgramName))
return false;
try
{
// In first, we need to clean up all existing GPS info.
removeGPSInfo();
// now re-initialize the GPS info:
if (!initializeGPSInfo(setProgramName))
return false;
char scratchBuf[100];
long int nom, denom;
long int deg, min;
// Now start adding data.
// ALTITUDE.
if (altitude)
{
// Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
if ((*altitude) >= 0) value->read("0");
else value->read("1");
d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
// And the actual altitude, as absolute value..
convertToRational(fabs(*altitude), &nom, &denom, 4);
snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QString("0") : QString("1"), false);
setXmpTagString("Xmp.exif.GPSAltitude", QString(scratchBuf), false);
#endif // _XMP_SUPPORT_
}
// LATITUDE
// Latitude reference:
// latitude < 0 : "S"
// latitude > 0 : "N"
//
d->exifMetadata()["Exif.GPSInfo.GPSLatitudeRef"] = (latitude < 0 ) ? "S" : "N";
// Now the actual latitude itself.
// This is done as three rationals.
// I choose to do it as:
// dd/1 - degrees.
// mmmm/100 - minutes
// 0/1 - seconds
// Exif standard says you can do it with minutes
// as mm/1 and then seconds as ss/1, but its
// (slightly) more accurate to do it as
// mmmm/100 than to split it.
// We also absolute the value (with fabs())
// as the sign is encoded in LatRef.
// Further note: original code did not translate between
// dd.dddddd to dd mm.mm - that's why we now multiply
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
deg = (int)floor(fabs(latitude)); // Slice off after decimal.
min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLatitudeRef,
* because the reference is included in Xmp.exif.GPSLatitude.
* Is there a historic reason for writing it anyway?
*/
setXmpTagString("Xmp.exif.GPSLatitudeRef", (latitude < 0) ? QString("S") : QString("N"), false);
setXmpTagString("Xmp.exif.GPSLatitude", convertToGPSCoordinateString(true, latitude), false);
#endif // _XMP_SUPPORT_
// LONGITUDE
// Longitude reference:
// longitude < 0 : "W"
// longitude > 0 : "E"
d->exifMetadata()["Exif.GPSInfo.GPSLongitudeRef"] = (longitude < 0 ) ? "W" : "E";
// Now the actual longitude itself.
// This is done as three rationals.
// I choose to do it as:
// dd/1 - degrees.
// mmmm/100 - minutes
// 0/1 - seconds
// Exif standard says you can do it with minutes
// as mm/1 and then seconds as ss/1, but its
// (slightly) more accurate to do it as
// mmmm/100 than to split it.
// We also absolute the value (with fabs())
// as the sign is encoded in LongRef.
// Further note: original code did not translate between
// dd.dddddd to dd mm.mm - that's why we now multiply
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
deg = (int)floor(fabs(longitude)); // Slice off after decimal.
min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
* because the reference is included in Xmp.exif.GPSLongitude.
* Is there a historic reason for writing it anyway?
*/
setXmpTagString("Xmp.exif.GPSLongitudeRef", (longitude < 0) ? QString("W") : QString("E"), false);
setXmpTagString("Xmp.exif.GPSLongitude", convertToGPSCoordinateString(false, longitude), false);
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot set Exif GPS tag using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
bool KExiv2::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude, const bool setProgramName)
{
double longitudeValue, latitudeValue;
if (!convertFromGPSCoordinateString(latitude, &latitudeValue))
return false;
if (!convertFromGPSCoordinateString(longitude, &longitudeValue))
return false;
return setGPSInfo(&altitude, latitudeValue, longitudeValue, setProgramName);
}
bool KExiv2::removeGPSInfo(const bool setProgramName)
{
if (!setProgramId(setProgramName))
return false;
try
{
QStringList gpsTagsKeys;
for (Exiv2::ExifData::iterator it = d->exifMetadata().begin();
it != d->exifMetadata().end(); ++it)
{
QString key = QString::fromLocal8Bit(it->key().c_str());
if (key.section('.', 1, 1) == QString("GPSInfo"))
gpsTagsKeys.append(key);
}
for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
{
Exiv2::ExifKey gpsKey((*it2).toAscii().constData());
Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey);
if (it3 != d->exifMetadata().end())
d->exifMetadata().erase(it3);
}
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
* and Xmp.exif.GPSLatitudeRef. But because we write them in setGPSInfo(),
* we should also remove them here.
*/
removeXmpTag("Xmp.exif.GPSLatitudeRef", false);
removeXmpTag("Xmp.exif.GPSLongitudeRef", false);
removeXmpTag("Xmp.exif.GPSVersionID", false);
removeXmpTag("Xmp.exif.GPSLatitude", false);
removeXmpTag("Xmp.exif.GPSLongitude", false);
removeXmpTag("Xmp.exif.GPSAltitudeRef", false);
removeXmpTag("Xmp.exif.GPSAltitude", false);
removeXmpTag("Xmp.exif.GPSTimeStamp", false);
removeXmpTag("Xmp.exif.GPSSatellites", false);
removeXmpTag("Xmp.exif.GPSStatus", false);
removeXmpTag("Xmp.exif.GPSMeasureMode", false);
removeXmpTag("Xmp.exif.GPSDOP", false);
removeXmpTag("Xmp.exif.GPSSpeedRef", false);
removeXmpTag("Xmp.exif.GPSSpeed", false);
removeXmpTag("Xmp.exif.GPSTrackRef", false);
removeXmpTag("Xmp.exif.GPSTrack", false);
removeXmpTag("Xmp.exif.GPSImgDirectionRef", false);
removeXmpTag("Xmp.exif.GPSImgDirection", false);
removeXmpTag("Xmp.exif.GPSMapDatum", false);
removeXmpTag("Xmp.exif.GPSDestLatitude", false);
removeXmpTag("Xmp.exif.GPSDestLongitude", false);
removeXmpTag("Xmp.exif.GPSDestBearingRef", false);
removeXmpTag("Xmp.exif.GPSDestBearing", false);
removeXmpTag("Xmp.exif.GPSDestDistanceRef", false);
removeXmpTag("Xmp.exif.GPSDestDistance", false);
removeXmpTag("Xmp.exif.GPSProcessingMethod", false);
removeXmpTag("Xmp.exif.GPSAreaInformation", false);
removeXmpTag("Xmp.exif.GPSDifferential", false);
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError("Cannot remove Exif GPS tag using Exiv2 ", e);
}
catch(...)
{
kError() << "Default exception from Exiv2";
}
return false;
}
void KExiv2::convertToRational(const double number, long int* const numerator,
long int* const denominator, const int rounding)
{
// This function converts the given decimal number
// to a rational (fractional) number.
//
// Examples in comments use Number as 25.12345, Rounding as 4.
// Split up the number.
double whole = trunc(number);
double fractional = number - whole;
// Calculate the "number" used for rounding.
// This is 10^Digits - ie, 4 places gives us 10000.
double rounder = pow(10.0, rounding);
// Round the fractional part, and leave the number
// as greater than 1.
// To do this we: (for example)
// 0.12345 * 10000 = 1234.5
// floor(1234.5) = 1234 - now bigger than 1 - ready...
fractional = round(fractional * rounder);
// Convert the whole thing to a fraction.
// Fraction is:
// (25 * 10000) + 1234 251234
// ------------------- = ------ = 25.1234
// 10000 10000
double numTemp = (whole * rounder) + fractional;
double denTemp = rounder;
// Now we should reduce until we can reduce no more.
// Try simple reduction...
// if Num
// ----- = integer out then....
// Den
if (trunc(numTemp / denTemp) == (numTemp / denTemp))
{
// Divide both by Denominator.
numTemp /= denTemp;
denTemp /= denTemp;
}
// And, if that fails, brute force it.
while (1)
{
// Jump out if we can't integer divide one.
if ((numTemp / 2) != trunc(numTemp / 2)) break;
if ((denTemp / 2) != trunc(denTemp / 2)) break;
// Otherwise, divide away.
numTemp /= 2;
denTemp /= 2;
}
// Copy out the numbers.
*numerator = (int)numTemp;
*denominator = (int)denTemp;
}
void KExiv2::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator)
{
// This function converts the given decimal number
// to a rational (fractional) number.
//
// This method, in contrast to the method above, will retrieve the smallest possible
// denominator. It is tested to retrieve the correct value for 1/x, with 0 < x <= 1000000.
// Note: This requires double precision, storing in float breaks some numbers (49, 59, 86,...)
// Split up the number.
double whole = trunc(number);
double fractional = number - whole;
/*
* Find best rational approximation to a double
* by C.B. Falconer, 2006-09-07. Released to public domain.
*
* Newsgroups: comp.lang.c, comp.programming
* From: CBFalconer <cbfalconer@yahoo.com>
* Date: Thu, 07 Sep 2006 17:35:30 -0400
* Subject: Rational approximations
*/
int lastnum = 500; // this is _not_ the largest possible denominator
long int num, approx, bestnum=0, bestdenom=1;
double value, error, leasterr, criterion;
value = fractional;
if (value == 0.0)
{
*numerator = (long int)whole;
*denominator = 1;
return;
}
criterion = 2 * value * DBL_EPSILON;
for (leasterr = value, num = 1; num < lastnum; ++num)
{
approx = (int)(num / value + 0.5);
error = fabs((double)num / approx - value);
if (error < leasterr)
{
bestnum = num;
bestdenom = approx;
leasterr = error;
if (leasterr <= criterion) break;
}
}
// add whole number part
if (bestdenom * whole > (double)INT_MAX)
{
// In some cases, we would generate an integer overflow.
// Fall back to Gilles's code which is better suited for such numbers.
convertToRational(number, numerator, denominator, 5);
}
else
{
bestnum += bestdenom * (long int)whole;
*numerator = bestnum;
*denominator = bestdenom;
}
}
QString KExiv2::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)
{
/**
* Precision:
* A second at sea level measures 30m for our purposes, a minute 1800m.
* (for more details, see http://en.wikipedia.org/wiki/Geographic_coordinate_system)
* This means with a decimal precision of 8 for minutes we get +/-0,018mm.
* (if I calculated correctly)
*/
QString coordinate;
// be relaxed with seconds of 0/0
if (denominatorSeconds == 0 && numeratorSeconds == 0)
denominatorSeconds = 1;
if (denominatorDegrees == 1 &&
denominatorMinutes == 1 &&
denominatorSeconds == 1)
{
// use form DDD,MM,SSk
coordinate = "%1,%2,%3%4";
coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference);
}
else if (denominatorDegrees == 1 &&
denominatorMinutes == 100 &&
denominatorSeconds == 1)
{
// use form DDD,MM.mmk
coordinate = "%1,%2%3";
double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
minutes += (double)numeratorSeconds / 60.0;
QString minutesString = QString::number(minutes, 'f', 8);
while (minutesString.endsWith('0') && !minutesString.endsWith(".0"))
{
minutesString.chop(1);
}
coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference);
}
else if (denominatorDegrees == 0 ||
denominatorMinutes == 0 ||
denominatorSeconds == 0)
{
// Invalid. 1/0 is everything but 0. As is 0/0.
return QString();
}
else
{
// use form DDD,MM.mmk
coordinate = "%1,%2%3";
double degrees = (double)numeratorDegrees / (double)denominatorDegrees;
double wholeDegrees = trunc(degrees);
double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
minutes += (degrees - wholeDegrees) * 60.0;
minutes += ((double)numeratorSeconds / (double)denominatorSeconds) / 60.0;
QString minutesString = QString::number(minutes, 'f', 8);
while (minutesString.endsWith('0') && !minutesString.endsWith(".0"))
{
minutesString.chop(1);
}
coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference);
}
return coordinate;
}
QString KExiv2::convertToGPSCoordinateString(const bool isLatitude, double coordinate)
{
if (coordinate < -360.0 || coordinate > 360.0)
return QString();
QString coordinateString;
char directionReference;
if (isLatitude)
{
if (coordinate < 0)
directionReference = 'S';
else
directionReference = 'N';
}
else
{
if (coordinate < 0)
directionReference = 'W';
else
directionReference = 'E';
}
// remove sign
coordinate = fabs(coordinate);
int degrees = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(degrees);
// To minutes
double minutes = coordinate * 60.0;
// use form DDD,MM.mmk
coordinateString = "%1,%2%3";
coordinateString = coordinateString.arg(degrees);
coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference);
return coordinateString;
}
bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString,
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)
{
if (gpsString.isEmpty())
return false;
*directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(',');
if (parts.size() == 2)
{
// form DDD,MM.mmk
*denominatorDegrees = 1;
*denominatorMinutes = 1000000;
*denominatorSeconds = 1;
*numeratorDegrees = parts[0].toLong();
double minutes = parts[1].toDouble();
minutes *= 1000000;
*numeratorMinutes = (long)round(minutes);
*numeratorSeconds = 0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*denominatorDegrees = 1;
*denominatorMinutes = 1;
*denominatorSeconds = 1;
*numeratorDegrees = parts[0].toLong();
*numeratorMinutes = parts[1].toLong();
*numeratorSeconds = parts[2].toLong();
return true;
}
else
{
return false;
}
}
bool KExiv2::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees)
{
if (gpsString.isEmpty())
return false;
char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(',');
if (parts.size() == 2)
{
// form DDD,MM.mmk
*degrees = parts[0].toLong();
*degrees += parts[1].toDouble() / 60.0;
if (directionReference == 'W' || directionReference == 'S')
*degrees *= -1.0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*degrees = parts[0].toLong();
*degrees += parts[1].toLong() / 60.0;
*degrees += parts[2].toLong() / 3600.0;
if (directionReference == 'W' || directionReference == 'S')
*degrees *= -1.0;
return true;
}
else
{
return false;
}
}
bool KExiv2::convertToUserPresentableNumbers(const QString& gpsString,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference)
{
if (gpsString.isEmpty())
return false;
*directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(',');
if (parts.size() == 2)
{
// form DDD,MM.mmk
*degrees = parts[0].toInt();
double fractionalMinutes = parts[1].toDouble();
*minutes = (int)trunc(fractionalMinutes);
*seconds = (fractionalMinutes - (double)(*minutes)) * 60.0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*degrees = parts[0].toInt();
*minutes = parts[1].toInt();
*seconds = (double)parts[2].toInt();
return true;
}
else
{
return false;
}
}
void KExiv2::convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference)
{
if (isLatitude)
{
if (coordinate < 0)
*directionReference = 'S';
else
*directionReference = 'N';
}
else
{
if (coordinate < 0)
*directionReference = 'W';
else
*directionReference = 'E';
}
// remove sign
coordinate = fabs(coordinate);
*degrees = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(*degrees);
// To minutes
coordinate *= 60.0;
*minutes = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(*minutes);
// To seconds
coordinate *= 60.0;
*seconds = coordinate;
}
} // NameSpace KExiv2Iface