24 QString QgsImportPhotosAlgorithm::name()
const 26 return QStringLiteral(
"importphotos" );
29 QString QgsImportPhotosAlgorithm::displayName()
const 31 return QObject::tr(
"Import geotagged photos" );
34 QStringList QgsImportPhotosAlgorithm::tags()
const 36 return QObject::tr(
"exif,metadata,gps,jpeg,jpg" ).split(
',' );
39 QString QgsImportPhotosAlgorithm::group()
const 41 return QObject::tr(
"Vector creation" );
44 QString QgsImportPhotosAlgorithm::groupId()
const 46 return QStringLiteral(
"vectorcreation" );
49 void QgsImportPhotosAlgorithm::initAlgorithm(
const QVariantMap & )
54 std::unique_ptr< QgsProcessingParameterFeatureSink > output = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"OUTPUT" ), QObject::tr(
"Photos" ),
QgsProcessing::TypeVectorPoint, QVariant(), true );
55 output->setCreateByDefault(
true );
56 addParameter( output.release() );
58 std::unique_ptr< QgsProcessingParameterFeatureSink > invalid = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"INVALID" ), QObject::tr(
"Invalid photos table" ),
QgsProcessing::TypeVector, QVariant(), true );
59 invalid->setCreateByDefault(
false );
60 addParameter( invalid.release() );
63 QString QgsImportPhotosAlgorithm::shortHelpString()
const 65 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" 66 "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 " 67 "to set the point's Z value.\n\n" 68 "Optionally, a table of unreadable or non-geotagged photos can also be created." );
71 QgsImportPhotosAlgorithm *QgsImportPhotosAlgorithm::createInstance()
const 73 return new QgsImportPhotosAlgorithm();
76 QVariant QgsImportPhotosAlgorithm::parseMetadataValue(
const QString &value )
78 QRegularExpression numRx( QStringLiteral(
"^\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*$" ) );
79 QRegularExpressionMatch numMatch = numRx.match( value );
80 if ( numMatch.hasMatch() )
82 return numMatch.captured( 1 ).toDouble();
87 bool QgsImportPhotosAlgorithm::extractGeoTagFromMetadata(
const QVariantMap &metadata,
QgsPointXY &tag )
90 if ( metadata.contains( QStringLiteral(
"EXIF_GPSLongitude" ) ) )
93 x = metadata.value( QStringLiteral(
"EXIF_GPSLongitude" ) ).toDouble( &ok );
97 if ( metadata.value( QStringLiteral(
"EXIF_GPSLongitudeRef" ) ).toString().rightRef( 1 ).compare( QLatin1String(
"W" ), Qt::CaseInsensitive ) == 0
98 || metadata.value( QStringLiteral(
"EXIF_GPSLongitudeRef" ) ).toDouble() < 0 )
107 if ( metadata.contains( QStringLiteral(
"EXIF_GPSLatitude" ) ) )
110 y = metadata.value( QStringLiteral(
"EXIF_GPSLatitude" ) ).toDouble( &ok );
114 if ( metadata.value( QStringLiteral(
"EXIF_GPSLatitudeRef" ) ).toString().rightRef( 1 ).compare( QLatin1String(
"S" ), Qt::CaseInsensitive ) == 0
115 || metadata.value( QStringLiteral(
"EXIF_GPSLatitudeRef" ) ).toDouble() < 0 )
127 QVariant QgsImportPhotosAlgorithm::extractAltitudeFromMetadata(
const QVariantMap &metadata )
130 if ( metadata.contains( QStringLiteral(
"EXIF_GPSAltitude" ) ) )
132 double alt = metadata.value( QStringLiteral(
"EXIF_GPSAltitude" ) ).toDouble();
133 if ( metadata.contains( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ) &&
134 ( ( metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).type() == QVariant::String && metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).toString().right( 1 ) == QLatin1String(
"1" ) )
135 || metadata.value( QStringLiteral(
"EXIF_GPSAltitudeRef" ) ).toDouble() < 0 ) )
142 QVariant QgsImportPhotosAlgorithm::extractDirectionFromMetadata(
const QVariantMap &metadata )
145 if ( metadata.contains( QStringLiteral(
"EXIF_GPSImgDirection" ) ) )
147 direction = metadata.value( QStringLiteral(
"EXIF_GPSImgDirection" ) ).toDouble();
152 QVariant QgsImportPhotosAlgorithm::extractTimestampFromMetadata(
const QVariantMap &metadata )
155 if ( metadata.contains( QStringLiteral(
"EXIF_DateTimeOriginal" ) ) )
157 ts = metadata.value( QStringLiteral(
"EXIF_DateTimeOriginal" ) );
159 else if ( metadata.contains( QStringLiteral(
"EXIF_DateTimeDigitized" ) ) )
161 ts = metadata.value( QStringLiteral(
"EXIF_DateTimeDigitized" ) );
163 else if ( metadata.contains( QStringLiteral(
"EXIF_DateTime" ) ) )
165 ts = metadata.value( QStringLiteral(
"EXIF_DateTime" ) );
171 QRegularExpression dsRegEx( QStringLiteral(
"(\\d+):(\\d+):(\\d+)\\s+(\\d+):(\\d+):(\\d+)" ) );
172 QRegularExpressionMatch dsMatch = dsRegEx.match( ts.toString() );
173 if ( dsMatch.hasMatch() )
175 int year = dsMatch.captured( 1 ).toInt();
176 int month = dsMatch.captured( 2 ).toInt();
177 int day = dsMatch.captured( 3 ).toInt();
178 int hour = dsMatch.captured( 4 ).toInt();
179 int min = dsMatch.captured( 5 ).toInt();
180 int sec = dsMatch.captured( 6 ).toInt();
181 return QDateTime( QDate( year, month, day ), QTime( hour, min, sec ) );
189 QVariant QgsImportPhotosAlgorithm::parseCoord(
const QString &
string )
191 QRegularExpression coordRx( QStringLiteral(
"^\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*\\(\\s*([-\\.\\d]+)\\s*\\)\\s*$" ) );
192 QRegularExpressionMatch coordMatch = coordRx.match(
string );
193 if ( coordMatch.hasMatch() )
195 double hours = coordMatch.captured( 1 ).toDouble();
196 double minutes = coordMatch.captured( 2 ).toDouble();
197 double seconds = coordMatch.captured( 3 ).toDouble();
198 return hours + minutes / 60.0 + seconds / 3600.0;
206 QVariantMap QgsImportPhotosAlgorithm::parseMetadataList(
const QStringList &input )
209 QRegularExpression splitRx( QStringLiteral(
"(.*?)=(.*)" ) );
210 for (
const QString &item : input )
212 QRegularExpressionMatch match = splitRx.match( item );
213 if ( !match.hasMatch() )
216 QString tag = match.captured( 1 );
217 QVariant value = parseMetadataValue( match.captured( 2 ) );
219 if ( tag == QLatin1String(
"EXIF_GPSLatitude" ) || tag == QLatin1String(
"EXIF_GPSLongitude" ) )
220 value = parseCoord( value.toString() );
221 results.insert( tag, value );
233 if (
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
237 config.insert( QStringLiteral(
"DocumentViewer" ), 1 );
238 config.insert( QStringLiteral(
"FileWidget" ),
true );
239 vl->setEditorWidgetSetup( 0,
QgsEditorWidgetSetup( QStringLiteral(
"ExternalResource" ), config ) );
243 config.insert( QStringLiteral(
"FileWidgetButton" ),
true );
244 config.insert( QStringLiteral(
"StorageMode" ), 1 );
245 vl->setEditorWidgetSetup( 2,
QgsEditorWidgetSetup( QStringLiteral(
"ExternalResource" ), config ) );
252 QString folder = parameterAsFile( parameters, QStringLiteral(
"FOLDER" ), context );
254 QDir importDir( folder );
255 if ( !importDir.exists() )
260 bool recurse = parameterAsBoolean( parameters, QStringLiteral(
"RECURSIVE" ), context );
263 outFields.
append(
QgsField( QStringLiteral(
"photo" ), QVariant::String ) );
264 outFields.
append(
QgsField( QStringLiteral(
"filename" ), QVariant::String ) );
265 outFields.
append(
QgsField( QStringLiteral(
"directory" ), QVariant::String ) );
266 outFields.
append(
QgsField( QStringLiteral(
"altitude" ), QVariant::Double ) );
267 outFields.
append(
QgsField( QStringLiteral(
"direction" ), QVariant::Double ) );
268 outFields.
append(
QgsField( QStringLiteral(
"longitude" ), QVariant::String ) );
269 outFields.
append(
QgsField( QStringLiteral(
"latitude" ), QVariant::String ) );
270 outFields.
append(
QgsField( QStringLiteral(
"timestamp" ), QVariant::DateTime ) );
272 std::unique_ptr< QgsFeatureSink > outputSink( parameterAsSink( parameters, QStringLiteral(
"OUTPUT" ), context, outputDest, outFields,
276 invalidFields.
append(
QgsField( QStringLiteral(
"photo" ), QVariant::String ) );
277 invalidFields.
append(
QgsField( QStringLiteral(
"filename" ), QVariant::String ) );
278 invalidFields.
append(
QgsField( QStringLiteral(
"directory" ), QVariant::String ) );
279 invalidFields.
append(
QgsField( QStringLiteral(
"readable" ), QVariant::Bool ) );
281 std::unique_ptr< QgsFeatureSink > invalidSink( parameterAsSink( parameters, QStringLiteral(
"INVALID" ), context, invalidDest, invalidFields ) );
283 QStringList nameFilters {
"*.jpeg",
"*.jpg" };
288 QFileInfoList fileInfoList = importDir.entryInfoList( nameFilters, QDir::NoDotAndDotDot | QDir::Files );
289 for (
auto infoIt = fileInfoList.constBegin(); infoIt != fileInfoList.constEnd(); ++infoIt )
291 files.append( infoIt->absoluteFilePath() );
296 QDirIterator it( folder, nameFilters, QDir::NoDotAndDotDot | QDir::Files, QDirIterator::Subdirectories );
297 while ( it.hasNext() )
300 files.append( it.filePath() );
304 auto saveInvalidFile = [&invalidSink](
QgsAttributes & attributes,
bool readable )
309 attributes.append( readable );
315 double step = files.count() > 0 ? 100.0 / files.count() : 1;
317 for (
const QString &file : files )
327 QFileInfo fi( file );
329 attributes << QDir::toNativeSeparators( file )
330 << fi.completeBaseName()
331 << QDir::toNativeSeparators( fi.absolutePath() );
336 feedback->
reportError( QObject::tr(
"Could not open %1" ).arg( QDir::toNativeSeparators( file ) ) );
337 saveInvalidFile( attributes,
false );
341 if (
char **GDALmetadata = GDALGetMetadata( hDS.get(), nullptr ) )
350 if ( !extractGeoTagFromMetadata( metadata, tag ) )
353 feedback->
reportError( QObject::tr(
"Could not retrieve geotag for %1" ).arg( QDir::toNativeSeparators( file ) ) );
354 saveInvalidFile( attributes,
true );
358 QVariant altitude = extractAltitudeFromMetadata( metadata );
364 << extractDirectionFromMetadata( metadata )
367 << extractTimestampFromMetadata( metadata );
373 feedback->
reportError( QObject::tr(
"No metadata found in %1" ).arg( QDir::toNativeSeparators( file ) ) );
374 saveInvalidFile( attributes,
true );
381 outputs.insert( QStringLiteral(
"OUTPUT" ), outputDest );
390 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)
Appends 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.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
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.