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