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