23 QString QgsImportPhotosAlgorithm::name()
const 25 return QStringLiteral(
"importphotos" );
28 QString QgsImportPhotosAlgorithm::displayName()
const 30 return QObject::tr(
"Import geotagged photos" );
33 QStringList QgsImportPhotosAlgorithm::tags()
const 35 return QObject::tr(
"exif,metadata,gps,jpeg,jpg" ).split(
',' );
38 QString QgsImportPhotosAlgorithm::group()
const 40 return QObject::tr(
"Vector creation" );
43 QString QgsImportPhotosAlgorithm::groupId()
const 45 return QStringLiteral(
"vectorcreation" );
48 void QgsImportPhotosAlgorithm::initAlgorithm(
const QVariantMap & )
53 std::unique_ptr< QgsProcessingParameterFeatureSink > output = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"OUTPUT" ), QObject::tr(
"Photos" ),
QgsProcessing::TypeVectorPoint, QVariant(), true );
54 output->setCreateByDefault(
true );
55 addParameter( output.release() );
57 std::unique_ptr< QgsProcessingParameterFeatureSink > invalid = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"INVALID" ), QObject::tr(
"Invalid photos table" ),
QgsProcessing::TypeVector, QVariant(), true );
58 invalid->setCreateByDefault(
false );
59 addParameter( invalid.release() );
62 QString QgsImportPhotosAlgorithm::shortHelpString()
const 64 return QObject::tr(
"Creates a point layer corresponding to the geotagged locations from JPEG images from a source folder. Optionally the folder can be recursively scanned.\n\n" 65 "The point layer will contain a single PointZ feature per input file from which the geotags could be read. Any altitude information from the geotags will be used " 66 "to set the point's Z value.\n\n" 67 "Optionally, a table of unreadable or non-geotagged photos can also be created." );
70 QgsImportPhotosAlgorithm *QgsImportPhotosAlgorithm::createInstance()
const 72 return new QgsImportPhotosAlgorithm();
75 QVariant QgsImportPhotosAlgorithm::parseMetadataValue(
const QString &value )
77 QRegularExpression numRx( QStringLiteral(
"^\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*$" ) );
78 QRegularExpressionMatch numMatch = numRx.match( value );
79 if ( numMatch.hasMatch() )
81 return numMatch.captured( 1 ).toDouble();
86 bool QgsImportPhotosAlgorithm::extractGeoTagFromMetadata(
const QVariantMap &metadata,
QgsPointXY &tag )
89 if ( metadata.contains( QStringLiteral(
"EXIF_GPSLongitude" ) ) )
92 x = metadata.value( QStringLiteral(
"EXIF_GPSLongitude" ) ).toDouble( &ok );
96 if ( metadata.value( QStringLiteral(
"EXIF_GPSLongitudeRef" ) ).toString().right( 1 ).compare(
"W", Qt::CaseInsensitive ) == 0
97 || metadata.value( QStringLiteral(
"EXIF_GPSLongitudeRef" ) ).toDouble() < 0 )
106 if ( metadata.contains( QStringLiteral(
"EXIF_GPSLatitude" ) ) )
109 y = metadata.value( QStringLiteral(
"EXIF_GPSLatitude" ) ).toDouble( &ok );
113 if ( metadata.value( QStringLiteral(
"EXIF_GPSLatitudeRef" ) ).toString().right( 1 ).compare(
"S", Qt::CaseInsensitive ) == 0
114 || metadata.value( QStringLiteral(
"EXIF_GPSLatitudeRef" ) ).toDouble() < 0 )
126 QVariant QgsImportPhotosAlgorithm::extractAltitudeFromMetadata(
const QVariantMap &metadata )
129 if ( metadata.contains( QStringLiteral(
"EXIF_GPSAltitude" ) ) )
131 double alt = metadata.value( QStringLiteral(
"EXIF_GPSAltitude" ) ).toDouble();
132 if ( metadata.contains( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ) &&
133 ( ( metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).type() == QVariant::String && metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).toString().right( 1 ) == QLatin1String(
"1" ) )
134 || metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).toDouble() < 0 ) )
141 QVariant QgsImportPhotosAlgorithm::extractDirectionFromMetadata(
const QVariantMap &metadata )
144 if ( metadata.contains( QStringLiteral(
"EXIF_GPSImgDirection" ) ) )
146 direction = metadata.value( QStringLiteral(
"EXIF_GPSImgDirection" ) ).toDouble();
151 QVariant QgsImportPhotosAlgorithm::extractTimestampFromMetadata(
const QVariantMap &metadata )
154 if ( metadata.contains( QStringLiteral(
"EXIF_DateTimeOriginal" ) ) )
156 ts = metadata.value( QStringLiteral(
"EXIF_DateTimeOriginal" ) );
158 else if ( metadata.contains( QStringLiteral(
"EXIF_DateTimeDigitized" ) ) )
160 ts = metadata.value( QStringLiteral(
"EXIF_DateTimeDigitized" ) );
162 else if ( metadata.contains( QStringLiteral(
"EXIF_DateTime" ) ) )
164 ts = metadata.value( QStringLiteral(
"EXIF_DateTime" ) );
170 QRegularExpression dsRegEx( QStringLiteral(
"(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)" ) );
171 QRegularExpressionMatch dsMatch = dsRegEx.match( ts.toString() );
172 if ( dsMatch.hasMatch() )
174 int year = dsMatch.captured( 1 ).toInt();
175 int month = dsMatch.captured( 2 ).toInt();
176 int day = dsMatch.captured( 3 ).toInt();
177 int hour = dsMatch.captured( 4 ).toInt();
178 int min = dsMatch.captured( 5 ).toInt();
179 int sec = dsMatch.captured( 6 ).toInt();
180 return QDateTime( QDate( year, month, day ), QTime( hour, min, sec ) );
188 QVariant QgsImportPhotosAlgorithm::parseCoord(
const QString &
string )
190 QRegularExpression coordRx( QStringLiteral(
"^\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*$" ) );
191 QRegularExpressionMatch coordMatch = coordRx.match(
string );
192 if ( coordMatch.hasMatch() )
194 double hours = coordMatch.captured( 1 ).toDouble();
195 double minutes = coordMatch.captured( 2 ).toDouble();
196 double seconds = coordMatch.captured( 3 ).toDouble();
197 return hours + minutes / 60.0 + seconds / 3600.0;
205 QVariantMap QgsImportPhotosAlgorithm::parseMetadataList(
const QStringList &input )
208 QRegularExpression splitRx( QStringLiteral(
"(.*?)=(.*)" ) );
209 for (
const QString &item : input )
211 QRegularExpressionMatch match = splitRx.match( item );
212 if ( !match.hasMatch() )
215 QString tag = match.captured( 1 );
216 QVariant value = parseMetadataValue( match.captured( 2 ) );
218 if ( tag == QLatin1String(
"EXIF_GPSLatitude" ) || tag == QLatin1String(
"EXIF_GPSLongitude" ) )
219 value = parseCoord( value.toString() );
220 results.insert( tag, value );
232 if (
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
236 config.insert( QStringLiteral(
"DocumentViewer" ), 1 );
237 config.insert( QStringLiteral(
"FileWidget" ),
true );
238 vl->setEditorWidgetSetup( 0,
QgsEditorWidgetSetup( QStringLiteral(
"ExternalResource" ), config ) );
242 config.insert( QStringLiteral(
"FileWidgetButton" ),
true );
243 config.insert( QStringLiteral(
"StorageMode" ), 1 );
244 vl->setEditorWidgetSetup( 2,
QgsEditorWidgetSetup( QStringLiteral(
"ExternalResource" ), config ) );
251 QString folder = parameterAsFile( parameters, QStringLiteral(
"FOLDER" ), context );
253 QDir importDir( folder );
254 if ( !importDir.exists() )
259 bool recurse = parameterAsBool( parameters, QStringLiteral(
"RECURSIVE" ), context );
262 outFields.
append(
QgsField( QStringLiteral(
"photo" ), QVariant::String ) );
263 outFields.
append(
QgsField( QStringLiteral(
"filename" ), QVariant::String ) );
264 outFields.
append(
QgsField( QStringLiteral(
"directory" ), QVariant::String ) );
265 outFields.
append(
QgsField( QStringLiteral(
"altitude" ), QVariant::Double ) );
266 outFields.
append(
QgsField( QStringLiteral(
"direction" ), QVariant::Double ) );
267 outFields.
append(
QgsField( QStringLiteral(
"longitude" ), QVariant::String ) );
268 outFields.
append(
QgsField( QStringLiteral(
"latitude" ), QVariant::String ) );
269 outFields.
append(
QgsField( QStringLiteral(
"timestamp" ), QVariant::DateTime ) );
271 std::unique_ptr< QgsFeatureSink > outputSink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, outputDest, outFields,
275 invalidFields.
append(
QgsField( QStringLiteral(
"photo" ), QVariant::String ) );
276 invalidFields.
append(
QgsField( QStringLiteral(
"filename" ), QVariant::String ) );
277 invalidFields.
append(
QgsField( QStringLiteral(
"directory" ), QVariant::String ) );
278 invalidFields.
append(
QgsField( QStringLiteral(
"readable" ), QVariant::Bool ) );
280 std::unique_ptr< QgsFeatureSink > invalidSink( parameterAsSink( parameters, QStringLiteral(
"INVALID" ), context, invalidDest, invalidFields ) );
282 QStringList nameFilters {
"*.jpeg",
"*.jpg" };
287 QFileInfoList fileInfoList = importDir.entryInfoList( nameFilters, QDir::NoDotAndDotDot | QDir::Files );
288 for (
auto infoIt = fileInfoList.constBegin(); infoIt != fileInfoList.constEnd(); ++infoIt )
290 files.append( infoIt->absoluteFilePath() );
295 QDirIterator it( folder, nameFilters, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories );
296 while ( it.hasNext() )
299 files.append( it.filePath() );
303 auto saveInvalidFile = [&invalidSink](
QgsAttributes & attributes,
bool readable )
308 attributes.append( readable );
314 double step = files.count() > 0 ? 100.0 / files.count() : 1;
316 for (
const QString &file : files )
326 QFileInfo fi( file );
328 attributes << QDir::toNativeSeparators( file )
329 << fi.completeBaseName()
330 << QDir::toNativeSeparators( fi.absolutePath() );
335 feedback->
reportError( QObject::tr(
"Could not open %1" ).arg( QDir::toNativeSeparators( file ) ) );
336 saveInvalidFile( attributes,
false );
340 if (
char **GDALmetadata = GDALGetMetadata( hDS.get(), nullptr ) )
349 if ( !extractGeoTagFromMetadata( metadata, tag ) )
352 feedback->
reportError( QObject::tr(
"Could not retrieve geotag for %1" ).arg( QDir::toNativeSeparators( file ) ) );
353 saveInvalidFile( attributes,
true );
357 QVariant altitude = extractAltitudeFromMetadata( metadata );
363 << extractDirectionFromMetadata( metadata )
366 << extractTimestampFromMetadata( metadata );
372 feedback->
reportError( QObject::tr(
"No metadata found in %1" ).arg( QDir::toNativeSeparators( file ) ) );
373 saveInvalidFile( attributes,
true );
380 outputs.insert( QStringLiteral(
"OUTPUT" ), outputDest );
389 outputs.insert( QStringLiteral(
"INVALID" ), invalidDest );
A boolean parameter for processing algorithms.
An input file or folder parameter for processing algorithms.
bool willLoadLayerOnCompletion(const QString &layer) const
Returns true if the given layer (by ID or datasource) will be loaded into the current project upon co...
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for all map layer types.
Base class for providing feedback from a processing algorithm.
An interface for layer post-processing handlers for execution following a processing algorithm operat...
A class to represent a 2D point.
void setProgress(double progress)
Sets the current progress for the feedback object.
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Container of fields for a vector layer.
A geometry is the spatial representation of a feature.
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Custom exception class for processing related exceptions.
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Append a field. The field must have unique name, otherwise it is rejected (returns false) ...
Encapsulate a field in an attribute table or data source.
Point geometry type, with support for z-dimension and m-values.
bool isCanceled() const
Tells whether the operation has been canceled already.
QgsProcessingContext::LayerDetails & layerToLoadOnCompletionDetails(const QString &layer)
Returns a reference to the details for a given layer which is loaded on completion of the algorithm o...
This class represents a coordinate reference system (CRS).
void setGeometry(const QgsGeometry &geometry)
Set the feature's geometry.
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm...
std::unique_ptr< std::remove_pointer< GDALDatasetH >::type, GDALDatasetCloser > dataset_unique_ptr
Scoped GDAL dataset.
static QStringList cStringListToQStringList(char **stringList)
Converts a c string list to a QStringList.
Represents a vector layer which manages a vector based data sets.
Contains information about the context in which a processing algorithm is executed.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.