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