QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsexiftools.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgisexiftools.cpp
3  -----------------
4  Date : November 2018
5  Copyright : (C) 2018 by Nyall Dawson
6  Email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsexiftools.h"
17 #include "qgspoint.h"
18 #include <exiv2/exiv2.hpp>
19 #include <QRegularExpression>
20 #include <QFileInfo>
21 
22 #if 0 // needs further work on the correct casting of tag values to QVariant values!
23 QVariantMap QgsExifTools::readTags( const QString &imagePath )
24 {
25  std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
26  if ( !image )
27  return QVariantMap();
28 
29  image->readMetadata();
30  Exiv2::ExifData &exifData = image->exifData();
31  if ( exifData.empty() )
32  {
33  return QVariantMap();
34  }
35 
36  QVariantMap res;
37  Exiv2::ExifData::const_iterator end = exifData.end();
38  for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i )
39  {
40  const QString key = QString::fromStdString( i->key() );
41  QVariant val;
42  switch ( i->typeId() )
43  {
44  case Exiv2::asciiString:
45  case Exiv2::string:
46  case Exiv2::comment:
47  case Exiv2::directory:
48  case Exiv2::xmpText:
49  val = QString::fromStdString( i->toString() );
50  break;
51 
52  case Exiv2::unsignedLong:
53  case Exiv2::signedLong:
54  val = QVariant::fromValue( i->toLong() );
55  break;
56 
57  case Exiv2::tiffDouble:
58  case Exiv2::tiffFloat:
59  val = QVariant::fromValue( i->toFloat() );
60  break;
61 
62  case Exiv2::unsignedShort:
63  case Exiv2::signedShort:
64  val = QVariant::fromValue( static_cast< int >( i->toLong() ) );
65  break;
66 
67  case Exiv2::unsignedRational:
68  case Exiv2::signedRational:
69  case Exiv2::unsignedByte:
70  case Exiv2::signedByte:
71  case Exiv2::undefined:
72  case Exiv2::tiffIfd:
73  case Exiv2::date:
74  case Exiv2::time:
75  case Exiv2::xmpAlt:
76  case Exiv2::xmpBag:
77  case Exiv2::xmpSeq:
78  case Exiv2::langAlt:
79  case Exiv2::invalidTypeId:
80  case Exiv2::lastTypeId:
81  val = QString::fromStdString( i->toString() );
82  break;
83 
84  }
85 
86  res.insert( key, val );
87  }
88  return res;
89 }
90 #endif
91 
92 QString doubleToExifCoordinate( const double val )
93 {
94  double d = std::abs( val );
95  int degrees = static_cast< int >( std::floor( d ) );
96  double m = 60 * ( d - degrees );
97  int minutes = static_cast< int >( std::floor( m ) );
98  double s = 60 * ( m - minutes );
99  int seconds = static_cast< int >( std::floor( s * 1000 ) );
100  return QStringLiteral( "%1/1 %2/1 %3/1000" ).arg( degrees ).arg( minutes ).arg( seconds );
101 }
102 
103 QgsPoint QgsExifTools::getGeoTag( const QString &imagePath, bool &ok )
104 {
105  ok = false;
106  if ( !QFileInfo::exists( imagePath ) )
107  return QgsPoint();
108  try
109  {
110  std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
111  if ( !image )
112  return QgsPoint();
113 
114  image->readMetadata();
115  Exiv2::ExifData &exifData = image->exifData();
116 
117  if ( exifData.empty() )
118  return QgsPoint();
119 
120  Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitudeRef" ) );
121  Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitude" ) );
122  Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitudeRef" ) );
123  Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitude" ) );
124 
125  if ( itLatRef == exifData.end() || itLatVal == exifData.end() ||
126  itLonRef == exifData.end() || itLonVal == exifData.end() )
127  return QgsPoint();
128 
129  auto readCoord = []( const QString & coord )->double
130  {
131  double res = 0;
132  double div = 1;
133  const QStringList parts = coord.split( QRegularExpression( QStringLiteral( "\\s+" ) ) );
134  for ( const QString &rational : parts )
135  {
136  const QStringList pair = rational.split( '/' );
137  if ( pair.size() != 2 )
138  break;
139  res += ( pair[0].toDouble() / pair[1].toDouble() ) / div;
140  div *= 60;
141  }
142  return res;
143  };
144 
145  auto readRationale = []( const QString & rational )->double
146  {
147  const QStringList pair = rational.split( '/' );
148  if ( pair.size() != 2 )
149  return std::numeric_limits< double >::quiet_NaN();
150  return pair[0].toDouble() / pair[1].toDouble();
151  };
152 
153  double lat = readCoord( QString::fromStdString( itLatVal->value().toString() ) );
154  double lon = readCoord( QString::fromStdString( itLonVal->value().toString() ) );
155 
156  const QString latRef = QString::fromStdString( itLatRef->value().toString() );
157  const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
158  if ( latRef.compare( QLatin1String( "S" ), Qt::CaseInsensitive ) == 0 )
159  {
160  lat *= -1;
161  }
162  if ( lonRef.compare( QLatin1String( "W" ), Qt::CaseInsensitive ) == 0 )
163  {
164  lon *= -1;
165  }
166 
167  ok = true;
168 
169  Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitude" ) );
170  Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitudeRef" ) );
171  if ( itElevVal != exifData.end() )
172  {
173  double elev = readRationale( QString::fromStdString( itElevVal->value().toString() ) );
174  if ( itElevRefVal != exifData.end() )
175  {
176  const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
177  if ( elevRef.compare( QLatin1String( "1" ), Qt::CaseInsensitive ) == 0 )
178  {
179  elev *= -1;
180  }
181  }
182  return QgsPoint( lon, lat, elev );
183  }
184  else
185  {
186  return QgsPoint( lon, lat );
187  }
188  }
189  catch ( ... )
190  {
191  return QgsPoint();
192  }
193 }
194 
195 bool QgsExifTools::geoTagImage( const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details )
196 {
197  try
198  {
199  std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
200  if ( !image )
201  return false;
202 
203  image->readMetadata();
204  Exiv2::ExifData &exifData = image->exifData();
205 
206  exifData["Exif.GPSInfo.GPSVersionID"] = "2 0 0 0";
207  exifData["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
208  exifData["Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinate( location.y() ).toStdString();
209  exifData["Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinate( location.x() ).toStdString();
210  if ( !std::isnan( details.elevation ) )
211  {
212  const QString elevationString = QStringLiteral( "%1/1000" ).arg( static_cast< int>( std::floor( std::abs( details.elevation ) * 1000 ) ) );
213  exifData["Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
214  exifData["Exif.GPSInfo.GPSAltitudeRef"] = details.elevation < 0.0 ? "1" : "0";
215  }
216  exifData["Exif.GPSInfo.GPSLatitudeRef"] = location.y() > 0 ? "N" : "S";
217  exifData["Exif.GPSInfo.GPSLongitudeRef"] = location.x() > 0 ? "E" : "W";
218  exifData["Exif.Image.GPSTag"] = 4908;
219  image->writeMetadata();
220  }
221  catch ( ... )
222  {
223  return false;
224  }
225  return true;
226 }
double elevation
GPS elevation, or NaN if elevation is not available.
Definition: qgsexiftools.h:67
QString doubleToExifCoordinate(const double val)
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
static bool geoTagImage(const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details=QgsExifTools::GeoTagDetails())
Writes geotags to the image at imagePath.
Extended image geotag details.
Definition: qgsexiftools.h:55
static QgsPoint getGeoTag(const QString &imagePath, bool &ok)
Returns the geotagged coordinate stored in the image at imagePath.
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:37
double x
Definition: qgspointxy.h:47