QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
Loading...
Searching...
No Matches
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
18#include "qgslogger.h"
19#include "qgspoint.h"
20
21#include <QString>
22
23#include "moc_qgsexiftools.cpp"
24
25using namespace Qt::StringLiterals;
26
27#ifdef HAVE_EXIV2
28#include <exiv2/exiv2.hpp>
29#endif
30
31#include <QDate>
32#include <QRegularExpression>
33#include <QFileInfo>
34#include <QTime>
35
36#ifdef HAVE_EXIV2
37double readRational( const Exiv2::Value &value, long n = 0 )
38{
39 const Exiv2::Rational rational = value.toRational( n );
40 const auto numerator = rational.first;
41 const auto denominator = rational.second;
42 double res = 0;
43 if ( value.typeId() == Exiv2::unsignedRational )
44 {
45 res = static_cast< double >( static_cast<uint32_t>( numerator ) ) / static_cast<uint32_t>( denominator );
46 }
47 else
48 {
49 res = static_cast< double >( numerator ) / denominator;
50 }
51 return res;
52};
53
54double readCoordinate( const Exiv2::Value &value )
55{
56 double res = 0;
57 double div = 1;
58 for ( int i = 0; i < 3; i++ )
59 {
60 res += readRational( value, i ) / div;
61 div *= 60;
62 }
63 return res;
64}
65
66QVariant decodeXmpData( const QString &key, Exiv2::XmpData::const_iterator &it )
67{
68 QVariant val;
69 if ( key == "Xmp.xmp.MetadataDate"_L1 || key == "Xmp.xmp.CreateDate"_L1 || key == "Xmp.xmp.ModifyDate"_L1 )
70 {
71 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), Qt::ISODate ) );
72 }
73 else
74 {
75 switch ( it->typeId() )
76 {
77 case Exiv2::asciiString:
78 case Exiv2::string:
79 case Exiv2::comment:
80 case Exiv2::directory:
81 case Exiv2::xmpText:
82 val = QString::fromStdString( it->toString() );
83 break;
84
85 case Exiv2::unsignedLong:
86 case Exiv2::signedLong:
87 case Exiv2::unsignedLongLong:
88 case Exiv2::signedLongLong:
89#if EXIV2_TEST_VERSION( 0, 28, 0 )
90 val = QVariant::fromValue( it->toUint32() );
91#else
92 val = QVariant::fromValue( it->toLong() );
93#endif
94 break;
95
96 case Exiv2::tiffDouble:
97 case Exiv2::tiffFloat:
98 val = QVariant::fromValue( it->toFloat() );
99 break;
100
101 case Exiv2::unsignedShort:
102 case Exiv2::signedShort:
103 case Exiv2::unsignedByte:
104 case Exiv2::signedByte:
105 case Exiv2::tiffIfd:
106 case Exiv2::tiffIfd8:
107#if EXIV2_TEST_VERSION( 0, 28, 0 )
108 val = QVariant::fromValue( static_cast< int >( it->toUint32() ) );
109#else
110 val = QVariant::fromValue( static_cast< int >( it->toLong() ) );
111#endif
112 break;
113
114 case Exiv2::date:
115 {
116 const Exiv2::DateValue::Date date = static_cast< const Exiv2::DateValue *>( &it->value() )->getDate();
117 val = QVariant::fromValue(
118 QDate::fromString( u"%1-%2-%3"_s.arg( date.year ).arg( QString::number( date.month ).rightJustified( 2, '0' ) ).arg( QString::number( date.day ).rightJustified( 2, '0' ) ), "yyyy-MM-dd"_L1 )
119 );
120 break;
121 }
122
123 case Exiv2::time:
124 {
125 const Exiv2::TimeValue::Time time = static_cast< const Exiv2::TimeValue *>( &it->value() )->getTime();
126 val = QVariant::fromValue(
127 QTime::
128 fromString( u"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2, '0' ) ).arg( QString::number( time.minute ).rightJustified( 2, '0' ) ).arg( QString::number( time.second ).rightJustified( 2, '0' ) ), "hh:mm:ss"_L1 )
129 );
130 break;
131 }
132
133 case Exiv2::unsignedRational:
134 case Exiv2::signedRational:
135 {
136 if ( it->count() == 1 )
137 {
138 val = QVariant::fromValue( readRational( it->value() ) );
139 }
140 else
141 {
142 val = QString::fromStdString( it->toString() );
143 }
144 break;
145 }
146
147 case Exiv2::undefined:
148 case Exiv2::xmpAlt:
149 case Exiv2::xmpBag:
150 case Exiv2::xmpSeq:
151 case Exiv2::langAlt:
152 case Exiv2::invalidTypeId:
153 case Exiv2::lastTypeId:
154 val = QString::fromStdString( it->toString() );
155 break;
156 }
157 }
158 return val;
159}
160
161QVariant decodeExifData( const QString &key, Exiv2::ExifData::const_iterator &it )
162{
163 QVariant val;
164
165 if ( key == "Exif.GPSInfo.GPSLatitude"_L1 || key == "Exif.GPSInfo.GPSLongitude"_L1 || key == "Exif.GPSInfo.GPSDestLatitude"_L1 || key == "Exif.GPSInfo.GPSDestLongitude"_L1 )
166 {
167 val = readCoordinate( it->value() );
168 }
169 else if ( key == "Exif.GPSInfo.GPSTimeStamp"_L1 )
170 {
171 const QStringList parts = QString::fromStdString( it->toString() ).split( QRegularExpression( u"\\s+"_s ) );
172 if ( parts.size() == 3 )
173 {
174 const int hour = std::max( 0, std::min( 23, static_cast< int >( readRational( it->value(), 0 ) ) ) );
175 const int minute = std::max( 0, std::min( 59, static_cast< int >( readRational( it->value(), 1 ) ) ) );
176 const int second = std::max( 0, std::min( 59, static_cast< int >( readRational( it->value(), 2 ) ) ) );
177
178 val = QVariant::fromValue(
179 QTime::fromString( u"%1:%2:%3"_s.arg( QString::number( hour ).rightJustified( 2, '0' ) ).arg( QString::number( minute ).rightJustified( 2, '0' ) ).arg( QString::number( second ).rightJustified( 2, '0' ) ), "hh:mm:ss"_L1 )
180 );
181 }
182 }
183 else if ( key == "Exif.GPSInfo.GPSDateStamp"_L1 )
184 {
185 val = QVariant::fromValue( QDate::fromString( QString::fromStdString( it->toString() ), "yyyy:MM:dd"_L1 ) );
186 }
187 else if ( key == "Exif.Image.DateTime"_L1 || key == "Exif.Image.DateTime"_L1 || key == "Exif.Photo.DateTimeDigitized"_L1 || key == "Exif.Photo.DateTimeOriginal"_L1 )
188 {
189 val = QVariant::fromValue( QDateTime::fromString( QString::fromStdString( it->toString() ), "yyyy:MM:dd hh:mm:ss"_L1 ) );
190 }
191 else
192 {
193 switch ( it->typeId() )
194 {
195 case Exiv2::asciiString:
196 case Exiv2::string:
197 case Exiv2::comment:
198 case Exiv2::directory:
199 case Exiv2::xmpText:
200 val = QString::fromStdString( it->toString() );
201 break;
202
203 case Exiv2::unsignedLong:
204 case Exiv2::signedLong:
205 case Exiv2::unsignedLongLong:
206 case Exiv2::signedLongLong:
207#if EXIV2_TEST_VERSION( 0, 28, 0 )
208 val = QVariant::fromValue( it->toUint32() );
209#else
210 val = QVariant::fromValue( it->toLong() );
211#endif
212 break;
213
214 case Exiv2::tiffDouble:
215 case Exiv2::tiffFloat:
216 val = QVariant::fromValue( it->toFloat() );
217 break;
218
219 case Exiv2::unsignedShort:
220 case Exiv2::signedShort:
221 case Exiv2::unsignedByte:
222 case Exiv2::signedByte:
223 case Exiv2::tiffIfd:
224 case Exiv2::tiffIfd8:
225#if EXIV2_TEST_VERSION( 0, 28, 0 )
226 val = QVariant::fromValue( static_cast< int >( it->toUint32() ) );
227#else
228 val = QVariant::fromValue( static_cast< int >( it->toLong() ) );
229#endif
230 break;
231
232 case Exiv2::date:
233 {
234 const Exiv2::DateValue::Date date = static_cast< const Exiv2::DateValue *>( &it->value() )->getDate();
235 val = QVariant::fromValue(
236 QDate::fromString( u"%1-%2-%3"_s.arg( date.year ).arg( QString::number( date.month ).rightJustified( 2, '0' ) ).arg( QString::number( date.day ).rightJustified( 2, '0' ) ), "yyyy-MM-dd"_L1 )
237 );
238 break;
239 }
240
241 case Exiv2::time:
242 {
243 const Exiv2::TimeValue::Time time = static_cast< const Exiv2::TimeValue *>( &it->value() )->getTime();
244 val = QVariant::fromValue(
245 QTime::
246 fromString( u"%1:%2:%3"_s.arg( QString::number( time.hour ).rightJustified( 2, '0' ) ).arg( QString::number( time.minute ).rightJustified( 2, '0' ) ).arg( QString::number( time.second ).rightJustified( 2, '0' ) ), "hh:mm:ss"_L1 )
247 );
248 break;
249 }
250
251 case Exiv2::unsignedRational:
252 case Exiv2::signedRational:
253 {
254 if ( it->count() == 1 )
255 {
256 val = QVariant::fromValue( readRational( it->value() ) );
257 }
258 else
259 {
260 val = QString::fromStdString( it->toString() );
261 }
262 break;
263 }
264
265 case Exiv2::undefined:
266 case Exiv2::xmpAlt:
267 case Exiv2::xmpBag:
268 case Exiv2::xmpSeq:
269 case Exiv2::langAlt:
270 case Exiv2::invalidTypeId:
271 case Exiv2::lastTypeId:
272 val = QString::fromStdString( it->toString() );
273 break;
274 }
275 }
276 return val;
277}
278
279QString doubleToExifCoordinateString( const double val )
280{
281 const double d = std::abs( val );
282 const int degrees = static_cast< int >( std::floor( d ) );
283 const double m = 60 * ( d - degrees );
284 const int minutes = static_cast< int >( std::floor( m ) );
285 const double s = 60 * ( m - minutes );
286 const int seconds = static_cast< int >( std::floor( s * 1000 ) );
287 return u"%1/1 %2/1 %3/1000"_s.arg( degrees ).arg( minutes ).arg( seconds );
288}
289#endif // HAVE_EXIV2
290
291QVariant QgsExifTools::readTag( const QString &imagePath, const QString &key )
292{
293#ifdef HAVE_EXIV2
294 if ( !QFileInfo::exists( imagePath ) )
295 return QVariant();
296
297 try
298 {
299 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
300 if ( !image || key.isEmpty() )
301 return QVariant();
302
303 image->readMetadata();
304
305 if ( key.startsWith( "Xmp."_L1 ) )
306 {
307 Exiv2::XmpData &xmpData = image->xmpData();
308 if ( xmpData.empty() )
309 {
310 return QVariant();
311 }
312 Exiv2::XmpData::const_iterator i = xmpData.findKey( Exiv2::XmpKey( key.toUtf8().constData() ) );
313 return i != xmpData.end() ? decodeXmpData( key, i ) : QVariant();
314 }
315 else
316 {
317 Exiv2::ExifData &exifData = image->exifData();
318 if ( exifData.empty() )
319 {
320 return QVariant();
321 }
322 Exiv2::ExifData::const_iterator i = exifData.findKey( Exiv2::ExifKey( key.toUtf8().constData() ) );
323 return i != exifData.end() ? decodeExifData( key, i ) : QVariant();
324 }
325 }
326 catch ( ... )
327 {
328 return QVariant();
329 }
330#else
331 Q_UNUSED( imagePath )
332 Q_UNUSED( key )
333 QgsDebugError( u"QGIS is built without exiv2 support"_s );
334 return QVariant();
335#endif
336}
337
338QVariantMap QgsExifTools::readTags( const QString &imagePath )
339{
340#ifdef HAVE_EXIV2
341 if ( !QFileInfo::exists( imagePath ) )
342 return QVariantMap();
343
344 try
345 {
346 QVariantMap res;
347 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
348 if ( !image )
349 return QVariantMap();
350 image->readMetadata();
351
352 Exiv2::ExifData &exifData = image->exifData();
353 if ( !exifData.empty() )
354 {
355 const Exiv2::ExifData::const_iterator end = exifData.end();
356 for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i )
357 {
358 const QString key = QString::fromStdString( i->key() );
359 res.insert( key, decodeExifData( key, i ) );
360 }
361 }
362
363 Exiv2::XmpData &xmpData = image->xmpData();
364 if ( !xmpData.empty() )
365 {
366 const Exiv2::XmpData::const_iterator end = xmpData.end();
367 for ( Exiv2::XmpData::const_iterator i = xmpData.begin(); i != end; ++i )
368 {
369 const QString key = QString::fromStdString( i->key() );
370 res.insert( key, decodeXmpData( key, i ) );
371 }
372 }
373
374 return res;
375 }
376 catch ( ... )
377 {
378 return QVariantMap();
379 }
380#else
381 Q_UNUSED( imagePath )
382 QgsDebugError( u"QGIS is built without exiv2 support"_s );
383 return QVariantMap();
384#endif
385}
386
387bool QgsExifTools::hasGeoTag( const QString &imagePath )
388{
389#ifdef HAVE_EXIV2
390 bool ok = false;
391 QgsExifTools::getGeoTag( imagePath, ok );
392 return ok;
393#else
394 Q_UNUSED( imagePath )
395 QgsDebugError( u"QGIS is built without exiv2 support"_s );
396 return false;
397#endif
398}
399
400QgsPoint QgsExifTools::getGeoTag( const QString &imagePath, bool &ok )
401{
402#ifdef HAVE_EXIV2
403 ok = false;
404 if ( !QFileInfo::exists( imagePath ) )
405 return QgsPoint();
406 try
407 {
408 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
409 if ( !image )
410 return QgsPoint();
411
412 image->readMetadata();
413 Exiv2::ExifData &exifData = image->exifData();
414
415 if ( exifData.empty() )
416 return QgsPoint();
417
418 const Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitudeRef" ) );
419 const Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitude" ) );
420 const Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitudeRef" ) );
421 const Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitude" ) );
422
423 if ( itLatRef == exifData.end() || itLatVal == exifData.end() || itLonRef == exifData.end() || itLonVal == exifData.end() )
424 return QgsPoint();
425
426 double lat = readCoordinate( itLatVal->value() );
427 double lon = readCoordinate( itLonVal->value() );
428
429 const QString latRef = QString::fromStdString( itLatRef->value().toString() );
430 const QString lonRef = QString::fromStdString( itLonRef->value().toString() );
431 if ( latRef.compare( 'S'_L1, Qt::CaseInsensitive ) == 0 )
432 {
433 lat *= -1;
434 }
435 if ( lonRef.compare( 'W'_L1, Qt::CaseInsensitive ) == 0 )
436 {
437 lon *= -1;
438 }
439
440 ok = true;
441
442 const Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitude" ) );
443 const Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitudeRef" ) );
444 if ( itElevVal != exifData.end() )
445 {
446 double elev = readRational( itElevVal->value() );
447 if ( itElevRefVal != exifData.end() )
448 {
449 const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() );
450 if ( elevRef.compare( '1'_L1, Qt::CaseInsensitive ) == 0 )
451 {
452 elev *= -1;
453 }
454 }
455 return QgsPoint( lon, lat, elev );
456 }
457 else
458 {
459 return QgsPoint( lon, lat );
460 }
461 }
462 catch ( ... )
463 {
464 return QgsPoint();
465 }
466#else
467 Q_UNUSED( imagePath )
468 ok = false;
469 QgsDebugError( u"QGIS is built without exiv2 support"_s );
470 return QgsPoint();
471#endif
472}
473
474bool QgsExifTools::geoTagImage( const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details )
475{
476#ifdef HAVE_EXIV2
477 try
478 {
479 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
480 if ( !image )
481 return false;
482
483 image->readMetadata();
484 Exiv2::ExifData &exifData = image->exifData();
485
486 exifData["Exif.GPSInfo.GPSVersionID"] = "2 0 0 0";
487 exifData["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
488 exifData["Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinateString( location.y() ).toStdString();
489 exifData["Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinateString( location.x() ).toStdString();
490 if ( !std::isnan( details.elevation ) )
491 {
492 const QString elevationString = u"%1/1000"_s.arg( static_cast< int>( std::floor( std::abs( details.elevation ) * 1000 ) ) );
493 exifData["Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString();
494 exifData["Exif.GPSInfo.GPSAltitudeRef"] = details.elevation < 0.0 ? "1" : "0";
495 }
496 exifData["Exif.GPSInfo.GPSLatitudeRef"] = location.y() > 0 ? "N" : "S";
497 exifData["Exif.GPSInfo.GPSLongitudeRef"] = location.x() > 0 ? "E" : "W";
498 exifData["Exif.Image.GPSTag"] = 4908;
499 image->writeMetadata();
500 }
501 catch ( ... )
502 {
503 return false;
504 }
505 return true;
506#else
507 Q_UNUSED( imagePath )
508 Q_UNUSED( location )
509 Q_UNUSED( details )
510 QgsDebugError( u"QGIS is built without exiv2 support"_s );
511 return false;
512#endif
513}
514
515bool QgsExifTools::tagImage( const QString &imagePath, const QString &tag, const QVariant &value )
516{
517#ifdef HAVE_EXIV2
518 try
519 {
520 std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) );
521 if ( !image )
522 return false;
523
524 QVariant actualValue;
525 bool actualValueIsUShort = false;
526 if ( tag == "Exif.GPSInfo.GPSLatitude"_L1 || tag == "Exif.GPSInfo.GPSLongitude"_L1 || tag == "Exif.GPSInfo.GPSDestLatitude"_L1 || tag == "Exif.GPSInfo.GPSDestLongitude"_L1 )
527 {
528 actualValue = doubleToExifCoordinateString( value.toDouble() );
529 }
530 else if ( tag == "Exif.GPSInfo.GPSAltitude"_L1 )
531 {
532 actualValue = u"%1/1000"_s.arg( static_cast< int>( std::floor( std::abs( value.toDouble() ) * 1000 ) ) );
533 }
534 else if ( tag == "Exif.Image.Orientation"_L1 )
535 {
536 actualValueIsUShort = true;
537 actualValue = value;
538 }
539 else if ( value.userType() == QMetaType::Type::QDateTime )
540 {
541 const QDateTime dateTime = value.toDateTime();
542 if ( tag == "Exif.Image.DateTime"_L1 || tag == "Exif.Image.DateTime"_L1 || tag == "Exif.Photo.DateTimeDigitized"_L1 || tag == "Exif.Photo.DateTimeOriginal"_L1 )
543 {
544 actualValue = dateTime.toString( u"yyyy:MM:dd hh:mm:ss"_s );
545 }
546 else
547 {
548 actualValue = dateTime.toString( Qt::ISODate );
549 }
550 }
551 else if ( value.userType() == QMetaType::Type::QDate )
552 {
553 const QDate date = value.toDate();
554 if ( tag == "Exif.GPSInfo.GPSDateStamp"_L1 )
555 {
556 actualValue = date.toString( u"yyyy:MM:dd"_s );
557 }
558 else
559 {
560 actualValue = date.toString( u"yyyy-MM-dd"_s );
561 }
562 }
563 else if ( value.userType() == QMetaType::Type::QTime )
564 {
565 const QTime time = value.toTime();
566 if ( tag == "Exif.GPSInfo.GPSTimeStamp"_L1 )
567 {
568 actualValue = u"%1/1 %2/1 %3/1"_s.arg( time.hour() ).arg( time.minute() ).arg( time.second() );
569 }
570 else
571 {
572 actualValue = time.toString( u"HH:mm:ss"_s );
573 }
574 }
575 else
576 {
577 actualValue = value;
578 }
579
580 const bool isXmp = tag.startsWith( "Xmp."_L1 );
581 image->readMetadata();
582 if ( actualValueIsUShort )
583 {
584 if ( isXmp )
585 {
586 Exiv2::XmpData &xmpData = image->xmpData();
587 xmpData[tag.toStdString()] = static_cast<ushort>( actualValue.toLongLong() );
588 }
589 else
590 {
591 Exiv2::ExifData &exifData = image->exifData();
592 exifData[tag.toStdString()] = static_cast<ushort>( actualValue.toLongLong() );
593 }
594 }
595 else if ( actualValue.userType() == QMetaType::Type::Int || actualValue.userType() == QMetaType::Type::LongLong )
596 {
597 if ( isXmp )
598 {
599 Exiv2::XmpData &xmpData = image->xmpData();
600 xmpData[tag.toStdString()] = static_cast<uint32_t>( actualValue.toLongLong() );
601 }
602 else
603 {
604 Exiv2::ExifData &exifData = image->exifData();
605 exifData[tag.toStdString()] = static_cast<uint32_t>( actualValue.toLongLong() );
606 }
607 }
608 else if ( actualValue.userType() == QMetaType::Type::UInt || actualValue.userType() == QMetaType::Type::ULongLong )
609 {
610 if ( isXmp )
611 {
612 Exiv2::XmpData &xmpData = image->xmpData();
613 xmpData[tag.toStdString()] = static_cast<int32_t>( actualValue.toULongLong() );
614 }
615 else
616 {
617 Exiv2::ExifData &exifData = image->exifData();
618 exifData[tag.toStdString()] = static_cast<int32_t>( actualValue.toULongLong() );
619 }
620 }
621 else if ( actualValue.userType() == QMetaType::Type::Double )
622 {
623 if ( isXmp )
624 {
625 Exiv2::XmpData &xmpData = image->xmpData();
626 xmpData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
627 }
628 else
629 {
630 Exiv2::ExifData &exifData = image->exifData();
631 exifData[tag.toStdString()] = Exiv2::floatToRationalCast( actualValue.toFloat() );
632 }
633 }
634 else
635 {
636 if ( isXmp )
637 {
638 Exiv2::XmpData &xmpData = image->xmpData();
639 xmpData[tag.toStdString()] = actualValue.toString().toStdString();
640 }
641 else
642 {
643 Exiv2::ExifData &exifData = image->exifData();
644 exifData[tag.toStdString()] = actualValue.toString().toStdString();
645 }
646 }
647 image->writeMetadata();
648 }
649 catch ( ... )
650 {
651 return false;
652 }
653 return true;
654#else
655 Q_UNUSED( imagePath )
656 Q_UNUSED( tag )
657 Q_UNUSED( value )
658 QgsDebugError( u"QGIS is built without exiv2 support"_s );
659 return false;
660#endif
661}
Extended image geotag details.
double elevation
GPS elevation, or NaN if elevation is not available.
static QVariantMap readTags(const QString &imagePath)
Returns a map object containing all exif tags stored in the image at imagePath.
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.
static bool tagImage(const QString &imagePath, const QString &tag, const QVariant &value)
Writes a tag to the image at imagePath.
static QVariant readTag(const QString &imagePath, const QString &key)
Returns the value of of an exif tag key stored in the image at imagePath.
Represents a 2D point.
Definition qgspointxy.h:62
double y
Definition qgspointxy.h:66
double x
Definition qgspointxy.h:65
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
#define QgsDebugError(str)
Definition qgslogger.h:59