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