QGIS API Documentation  3.16.0-Hannover (43b64b13f3)
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 bool QgsExifTools::hasGeoTag( const QString &imagePath )
104 {
105  bool ok = false;
106  QgsExifTools::getGeoTag( imagePath, ok );
107  return ok;
108 }
109 
110 QgsPoint QgsExifTools::getGeoTag( const QString &imagePath, bool &ok )
111 {
112  ok = false;
113  if ( !QFileInfo::exists( imagePath ) )
114  return QgsPoint();
115  try
116  {
117  std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
118  if ( !image )
119  return QgsPoint();
120 
121  image->readMetadata();
122  Exiv2::ExifData &exifData = image->exifData();
123 
124  if ( exifData.empty() )
125  return QgsPoint();
126 
127  Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitudeRef" ) );
128  Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitude" ) );
129  Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitudeRef" ) );
130  Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitude" ) );
131 
132  if ( itLatRef == exifData.end() || itLatVal == exifData.end() ||
133  itLonRef == exifData.end() || itLonVal == exifData.end() )
134  return QgsPoint();
135 
136  auto readCoord = []( const QString & coord )->double
137  {
138  double res = 0;
139  double div = 1;
140  const QStringList parts = coord.split( QRegularExpression( QStringLiteral( "\\s+" ) ) );
141  for ( const QString &rational : parts )
142  {
143  const QStringList pair = rational.split( '/' );
144  if ( pair.size() != 2 )
145  break;
146  res += ( pair[0].toDouble() / pair[1].toDouble() ) / div;
147  div *= 60;
148  }
149  return res;
150  };
151 
152  auto readRationale = []( const QString & rational )->double
153  {
154  const QStringList pair = rational.split( '/' );
155  if ( pair.size() != 2 )
156  return std::numeric_limits< double >::quiet_NaN();
157  return pair[0].toDouble() / pair[1].toDouble();
158  };
159 
160  double lat = readCoord( QString::fromStdString( itLatVal->value().toString() ) );
161  double lon = readCoord( QString::fromStdString( itLonVal->value().toString() ) );
162 
163  const QString latRef = QString::fromStdString( itLatRef->value().toString() );
164  const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
165  if ( latRef.compare( QLatin1String( "S" ), Qt::CaseInsensitive ) == 0 )
166  {
167  lat *= -1;
168  }
169  if ( lonRef.compare( QLatin1String( "W" ), Qt::CaseInsensitive ) == 0 )
170  {
171  lon *= -1;
172  }
173 
174  ok = true;
175 
176  Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitude" ) );
177  Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitudeRef" ) );
178  if ( itElevVal != exifData.end() )
179  {
180  double elev = readRationale( QString::fromStdString( itElevVal->value().toString() ) );
181  if ( itElevRefVal != exifData.end() )
182  {
183  const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
184  if ( elevRef.compare( QLatin1String( "1" ), Qt::CaseInsensitive ) == 0 )
185  {
186  elev *= -1;
187  }
188  }
189  return QgsPoint( lon, lat, elev );
190  }
191  else
192  {
193  return QgsPoint( lon, lat );
194  }
195  }
196  catch ( ... )
197  {
198  return QgsPoint();
199  }
200 }
201 
202 bool QgsExifTools::geoTagImage( const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details )
203 {
204  try
205  {
206  std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
207  if ( !image )
208  return false;
209 
210  image->readMetadata();
211  Exiv2::ExifData &exifData = image->exifData();
212 
213  exifData["Exif.GPSInfo.GPSVersionID"] = "2 0 0 0";
214  exifData["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
215  exifData["Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinate( location.y() ).toStdString();
216  exifData["Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinate( location.x() ).toStdString();
217  if ( !std::isnan( details.elevation ) )
218  {
219  const QString elevationString = QStringLiteral( "%1/1000" ).arg( static_cast< int>( std::floor( std::abs( details.elevation ) * 1000 ) ) );
220  exifData["Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
221  exifData["Exif.GPSInfo.GPSAltitudeRef"] = details.elevation < 0.0 ? "1" : "0";
222  }
223  exifData["Exif.GPSInfo.GPSLatitudeRef"] = location.y() > 0 ? "N" : "S";
224  exifData["Exif.GPSInfo.GPSLongitudeRef"] = location.x() > 0 ? "E" : "W";
225  exifData["Exif.Image.GPSTag"] = 4908;
226  image->writeMetadata();
227  }
228  catch ( ... )
229  {
230  return false;
231  }
232  return true;
233 }
QgsExifTools::getGeoTag
static QgsPoint getGeoTag(const QString &imagePath, bool &ok)
Returns the geotagged coordinate stored in the image at imagePath.
Definition: qgsexiftools.cpp:110
QgsPointXY::y
double y
Definition: qgspointxy.h:48
QgsPoint
Point geometry type, with support for z-dimension and m-values.
Definition: qgspoint.h:38
QgsPointXY::x
Q_GADGET double x
Definition: qgspointxy.h:47
qgspoint.h
qgsexiftools.h
QgsExifTools::geoTagImage
static bool geoTagImage(const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details=QgsExifTools::GeoTagDetails())
Writes geotags to the image at imagePath.
Definition: qgsexiftools.cpp:202
doubleToExifCoordinate
QString doubleToExifCoordinate(const double val)
Definition: qgsexiftools.cpp:92
QgsPointXY
A class to represent a 2D point.
Definition: qgspointxy.h:44
QgsExifTools::hasGeoTag
static Q_INVOKABLE bool hasGeoTag(const QString &imagePath)
Returns true if the image at imagePath contains a valid geotag.
Definition: qgsexiftools.cpp:103
QgsExifTools::GeoTagDetails::elevation
double elevation
GPS elevation, or NaN if elevation is not available.
Definition: qgsexiftools.h:78
QgsExifTools::GeoTagDetails
Extended image geotag details.
Definition: qgsexiftools.h:67