Branch data Line data Source code
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 <QRegularExpression> 22 : : #include <QFileInfo> 23 : : 24 : : #if 0 // needs further work on the correct casting of tag values to QVariant values! 25 : : QVariantMap QgsExifTools::readTags( const QString &imagePath ) 26 : : { 27 : : std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) ); 28 : : if ( !image ) 29 : : return QVariantMap(); 30 : : 31 : : image->readMetadata(); 32 : : Exiv2::ExifData &exifData = image->exifData(); 33 : : if ( exifData.empty() ) 34 : : { 35 : : return QVariantMap(); 36 : : } 37 : : 38 : : QVariantMap res; 39 : : Exiv2::ExifData::const_iterator end = exifData.end(); 40 : : for ( Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i ) 41 : : { 42 : : const QString key = QString::fromStdString( i->key() ); 43 : : QVariant val; 44 : : switch ( i->typeId() ) 45 : : { 46 : : case Exiv2::asciiString: 47 : : case Exiv2::string: 48 : : case Exiv2::comment: 49 : : case Exiv2::directory: 50 : : case Exiv2::xmpText: 51 : : val = QString::fromStdString( i->toString() ); 52 : : break; 53 : : 54 : : case Exiv2::unsignedLong: 55 : : case Exiv2::signedLong: 56 : : val = QVariant::fromValue( i->toLong() ); 57 : : break; 58 : : 59 : : case Exiv2::tiffDouble: 60 : : case Exiv2::tiffFloat: 61 : : val = QVariant::fromValue( i->toFloat() ); 62 : : break; 63 : : 64 : : case Exiv2::unsignedShort: 65 : : case Exiv2::signedShort: 66 : : val = QVariant::fromValue( static_cast< int >( i->toLong() ) ); 67 : : break; 68 : : 69 : : case Exiv2::unsignedRational: 70 : : case Exiv2::signedRational: 71 : : case Exiv2::unsignedByte: 72 : : case Exiv2::signedByte: 73 : : case Exiv2::undefined: 74 : : case Exiv2::tiffIfd: 75 : : case Exiv2::date: 76 : : case Exiv2::time: 77 : : case Exiv2::xmpAlt: 78 : : case Exiv2::xmpBag: 79 : : case Exiv2::xmpSeq: 80 : : case Exiv2::langAlt: 81 : : case Exiv2::invalidTypeId: 82 : : case Exiv2::lastTypeId: 83 : : val = QString::fromStdString( i->toString() ); 84 : : break; 85 : : 86 : : } 87 : : 88 : : res.insert( key, val ); 89 : : } 90 : : return res; 91 : : } 92 : : #endif 93 : : 94 : 0 : QString doubleToExifCoordinate( const double val ) 95 : : { 96 : 0 : double d = std::abs( val ); 97 : 0 : int degrees = static_cast< int >( std::floor( d ) ); 98 : 0 : double m = 60 * ( d - degrees ); 99 : 0 : int minutes = static_cast< int >( std::floor( m ) ); 100 : 0 : double s = 60 * ( m - minutes ); 101 : 0 : int seconds = static_cast< int >( std::floor( s * 1000 ) ); 102 : 0 : return QStringLiteral( "%1/1 %2/1 %3/1000" ).arg( degrees ).arg( minutes ).arg( seconds ); 103 : 0 : } 104 : : 105 : 0 : bool QgsExifTools::hasGeoTag( const QString &imagePath ) 106 : : { 107 : 0 : bool ok = false; 108 : 0 : QgsExifTools::getGeoTag( imagePath, ok ); 109 : 0 : return ok; 110 : : } 111 : : 112 : 0 : QgsPoint QgsExifTools::getGeoTag( const QString &imagePath, bool &ok ) 113 : : { 114 : 0 : ok = false; 115 : 0 : if ( !QFileInfo::exists( imagePath ) ) 116 : 0 : return QgsPoint(); 117 : : try 118 : : { 119 : 0 : std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) ); 120 : 0 : if ( !image ) 121 : 0 : return QgsPoint(); 122 : : 123 : 0 : image->readMetadata(); 124 : 0 : Exiv2::ExifData &exifData = image->exifData(); 125 : : 126 : 0 : if ( exifData.empty() ) 127 : 0 : return QgsPoint(); 128 : : 129 : 0 : Exiv2::ExifData::iterator itLatRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitudeRef" ) ); 130 : 0 : Exiv2::ExifData::iterator itLatVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLatitude" ) ); 131 : 0 : Exiv2::ExifData::iterator itLonRef = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitudeRef" ) ); 132 : 0 : Exiv2::ExifData::iterator itLonVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSLongitude" ) ); 133 : : 134 : 0 : if ( itLatRef == exifData.end() || itLatVal == exifData.end() || 135 : 0 : itLonRef == exifData.end() || itLonVal == exifData.end() ) 136 : 0 : return QgsPoint(); 137 : : 138 : 0 : auto readCoord = []( const QString & coord )->double 139 : : { 140 : 0 : double res = 0; 141 : 0 : double div = 1; 142 : 0 : const QStringList parts = coord.split( QRegularExpression( QStringLiteral( "\\s+" ) ) ); 143 : 0 : for ( const QString &rational : parts ) 144 : : { 145 : 0 : const QStringList pair = rational.split( '/' ); 146 : 0 : if ( pair.size() != 2 ) 147 : 0 : break; 148 : 0 : res += ( pair[0].toDouble() / pair[1].toDouble() ) / div; 149 : 0 : div *= 60; 150 : 0 : } 151 : 0 : return res; 152 : 0 : }; 153 : : 154 : 0 : auto readRationale = []( const QString & rational )->double 155 : : { 156 : 0 : const QStringList pair = rational.split( '/' ); 157 : 0 : if ( pair.size() != 2 ) 158 : 0 : return std::numeric_limits< double >::quiet_NaN(); 159 : 0 : return pair[0].toDouble() / pair[1].toDouble(); 160 : 0 : }; 161 : : 162 : 0 : double lat = readCoord( QString::fromStdString( itLatVal->value().toString() ) ); 163 : 0 : double lon = readCoord( QString::fromStdString( itLonVal->value().toString() ) ); 164 : : 165 : 0 : const QString latRef = QString::fromStdString( itLatRef->value().toString() ); 166 : 0 : const QString lonRef = QString::fromStdString( itLonRef->value().toString() ); 167 : 0 : if ( latRef.compare( QLatin1String( "S" ), Qt::CaseInsensitive ) == 0 ) 168 : : { 169 : 0 : lat *= -1; 170 : 0 : } 171 : 0 : if ( lonRef.compare( QLatin1String( "W" ), Qt::CaseInsensitive ) == 0 ) 172 : : { 173 : 0 : lon *= -1; 174 : 0 : } 175 : : 176 : 0 : ok = true; 177 : : 178 : 0 : Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitude" ) ); 179 : 0 : Exiv2::ExifData::iterator itElevRefVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitudeRef" ) ); 180 : 0 : if ( itElevVal != exifData.end() ) 181 : : { 182 : 0 : double elev = readRationale( QString::fromStdString( itElevVal->value().toString() ) ); 183 : 0 : if ( itElevRefVal != exifData.end() ) 184 : : { 185 : 0 : const QString elevRef = QString::fromStdString( itElevRefVal->value().toString() ); 186 : 0 : if ( elevRef.compare( QLatin1String( "1" ), Qt::CaseInsensitive ) == 0 ) 187 : : { 188 : 0 : elev *= -1; 189 : 0 : } 190 : 0 : } 191 : 0 : return QgsPoint( lon, lat, elev ); 192 : : } 193 : : else 194 : : { 195 : 0 : return QgsPoint( lon, lat ); 196 : : } 197 : 0 : } 198 : : catch ( ... ) 199 : : { 200 : 0 : return QgsPoint(); 201 : 0 : } 202 : 0 : } 203 : : 204 : 0 : bool QgsExifTools::geoTagImage( const QString &imagePath, const QgsPointXY &location, const GeoTagDetails &details ) 205 : : { 206 : : try 207 : : { 208 : 0 : std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( imagePath.toStdString() ) ); 209 : 0 : if ( !image ) 210 : 0 : return false; 211 : : 212 : 0 : image->readMetadata(); 213 : 0 : Exiv2::ExifData &exifData = image->exifData(); 214 : : 215 : 0 : exifData["Exif.GPSInfo.GPSVersionID"] = "2 0 0 0"; 216 : 0 : exifData["Exif.GPSInfo.GPSMapDatum"] = "WGS-84"; 217 : 0 : exifData["Exif.GPSInfo.GPSLatitude"] = doubleToExifCoordinate( location.y() ).toStdString(); 218 : 0 : exifData["Exif.GPSInfo.GPSLongitude"] = doubleToExifCoordinate( location.x() ).toStdString(); 219 : 0 : if ( !std::isnan( details.elevation ) ) 220 : : { 221 : 0 : const QString elevationString = QStringLiteral( "%1/1000" ).arg( static_cast< int>( std::floor( std::abs( details.elevation ) * 1000 ) ) ); 222 : 0 : exifData["Exif.GPSInfo.GPSAltitude"] = elevationString.toStdString(); 223 : 0 : exifData["Exif.GPSInfo.GPSAltitudeRef"] = details.elevation < 0.0 ? "1" : "0"; 224 : 0 : } 225 : 0 : exifData["Exif.GPSInfo.GPSLatitudeRef"] = location.y() > 0 ? "N" : "S"; 226 : 0 : exifData["Exif.GPSInfo.GPSLongitudeRef"] = location.x() > 0 ? "E" : "W"; 227 : 0 : exifData["Exif.Image.GPSTag"] = 4908; 228 : 0 : image->writeMetadata(); 229 : 0 : } 230 : : catch ( ... ) 231 : : { 232 : 0 : return false; 233 : 0 : } 234 : 0 : return true; 235 : 0 : }