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