QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
qgsalgorithmpackage.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmpackage.cpp
3  ---------------------
4  begin : November 2017
5  copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsalgorithmpackage.h"
19 #include "qgsgeometryengine.h"
20 #include "qgsogrutils.h"
21 #include "qgsvectorfilewriter.h"
22 #include "qgsvectorlayer.h"
23 #include "qgssettings.h"
24 
26 
27 QString QgsPackageAlgorithm::name() const
28 {
29  return QStringLiteral( "package" );
30 }
31 
32 QString QgsPackageAlgorithm::displayName() const
33 {
34  return QObject::tr( "Package layers" );
35 }
36 
37 QStringList QgsPackageAlgorithm::tags() const
38 {
39  return QObject::tr( "geopackage,collect,merge,combine,styles" ).split( ',' );
40 }
41 
42 QString QgsPackageAlgorithm::group() const
43 {
44  return QObject::tr( "Database" );
45 }
46 
47 QString QgsPackageAlgorithm::groupId() const
48 {
49  return QStringLiteral( "database" );
50 }
51 
52 void QgsPackageAlgorithm::initAlgorithm( const QVariantMap & )
53 {
54  addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
55  QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination GeoPackage" ), QObject::tr( "GeoPackage files (*.gpkg)" ) );
56  outputParameter->setMetadata( QVariantMap( {{QStringLiteral( "widget_wrapper" ), QVariantMap( {{QStringLiteral( "dontconfirmoverwrite" ), true }} ) }} ) );
57  addParameter( outputParameter );
58  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing GeoPackage" ), false ) );
59  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SAVE_STYLES" ), QObject::tr( "Save layer styles into GeoPackage" ), true ) );
60  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SAVE_METADATA" ), QObject::tr( "Save layer metadata into GeoPackage" ), true ) );
61  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SELECTED_FEATURES_ONLY" ), QObject::tr( "Save only selected features" ), false ) );
62  addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) );
63 }
64 
65 QString QgsPackageAlgorithm::shortHelpString() const
66 {
67  return QObject::tr( "This algorithm collects a number of existing layers and packages them together into a single GeoPackage database." );
68 }
69 
70 QgsPackageAlgorithm *QgsPackageAlgorithm::createInstance() const
71 {
72  return new QgsPackageAlgorithm();
73 }
74 
75 bool QgsPackageAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
76 {
77  const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
78  for ( QgsMapLayer *layer : layers )
79  {
80  mLayers.emplace_back( layer->clone() );
81  }
82 
83  if ( mLayers.empty() )
84  feedback->reportError( QObject::tr( "No layers selected, geopackage will be empty" ), false );
85 
86  return true;
87 }
88 
89 QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
90 {
91  const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
92  const bool saveStyles = parameterAsBoolean( parameters, QStringLiteral( "SAVE_STYLES" ), context );
93  const bool saveMetadata = parameterAsBoolean( parameters, QStringLiteral( "SAVE_METADATA" ), context );
94  const bool selectedFeaturesOnly = parameterAsBoolean( parameters, QStringLiteral( "SELECTED_FEATURES_ONLY" ), context );
95  const QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
96  if ( packagePath.isEmpty() )
97  throw QgsProcessingException( QObject::tr( "No output file specified." ) );
98 
99  // delete existing geopackage if it exists
100  if ( overwrite && QFile::exists( packagePath ) )
101  {
102  feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
103  if ( !QFile( packagePath ).remove() )
104  {
105  throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( packagePath ) );
106  }
107  }
108 
109  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
110  if ( !hGpkgDriver )
111  {
112  throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
113  }
114 
116 
117  if ( !QFile::exists( packagePath ) )
118  {
119  hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
120  if ( !hDS )
121  throw QgsProcessingException( QObject::tr( "Creation of database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
122  }
123  else
124  {
125  hDS = gdal::ogr_datasource_unique_ptr( OGROpen( packagePath.toUtf8().constData(), true, nullptr ) );
126  if ( !hDS )
127  throw QgsProcessingException( QObject::tr( "Opening database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
128  }
129 
130 
131  bool errored = false;
132 
133  QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
134 
135  QStringList outputLayers;
136  int i = 0;
137  for ( const auto &layer : mLayers )
138  {
139  if ( feedback->isCanceled() )
140  break;
141 
142  multiStepFeedback.setCurrentStep( i );
143  i++;
144 
145  if ( !layer )
146  {
147  // don't throw immediately - instead do what we can and error out later
148  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
149  errored = true;
150  continue;
151  }
152 
153  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
154 
155  switch ( layer->type() )
156  {
158  {
159  QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer.get() );
160  const bool onlySaveSelected = vectorLayer->selectedFeatureCount() > 0 && selectedFeaturesOnly;
161  if ( !packageVectorLayer( vectorLayer, packagePath, context, &multiStepFeedback, saveStyles, saveMetadata, onlySaveSelected ) )
162  errored = true;
163  else
164  outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
165  break;
166  }
167 
169  {
170  //not supported
171  feedback->pushDebugInfo( QObject::tr( "Packaging raster layers is not supported." ) );
172  errored = true;
173  break;
174  }
175 
177  //not supported
178  feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
179  errored = true;
180  break;
181 
183  //not supported
184  feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
185  errored = true;
186  break;
187 
189  //not supported
190  feedback->pushDebugInfo( QObject::tr( "Packaging point cloud layers is not supported." ) );
191  errored = true;
192  break;
193 
195  //not supported
196  feedback->pushDebugInfo( QObject::tr( "Packaging vector tile layers is not supported." ) );
197  errored = true;
198  break;
199 
201  //not supported
202  feedback->pushDebugInfo( QObject::tr( "Packaging annotation layers is not supported." ) );
203  errored = true;
204  break;
205 
207  //not supported
208  feedback->pushDebugInfo( QObject::tr( "Packaging group layers is not supported." ) );
209  errored = true;
210  break;
211  }
212  }
213 
214  if ( errored )
215  throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
216 
217  QVariantMap outputs;
218  outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
219  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
220  return outputs;
221 }
222 
223 bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
224  QgsProcessingFeedback *feedback, bool saveStyles, bool saveMetadata, bool selectedFeaturesOnly )
225 {
227  options.driverName = QStringLiteral( "GPKG" );
228  options.layerName = layer->name();
230  options.fileEncoding = context.defaultEncoding();
231  options.onlySelectedFeatures = selectedFeaturesOnly;
232  options.feedback = feedback;
233  if ( saveMetadata )
234  {
235  options.layerMetadata = layer->metadata();
236  options.saveMetadata = true;
237  }
238 
239  // remove any existing FID field, let this be completely recreated
240  // since many layer sources have fid fields which are not compatible with gpkg requirements
241  const QgsFields fields = layer->fields();
242  const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
243 
244  options.attributes = fields.allAttributesList();
245  if ( fidIndex >= 0 )
246  options.attributes.removeAll( fidIndex );
247  if ( options.attributes.isEmpty() )
248  {
249  // fid was the only field
250  options.skipAttributeCreation = true;
251  }
252 
253  QString error;
254  QString newFilename;
255  QString newLayer;
256  if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
257  {
258  feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
259  return false;
260  }
261  else
262  {
263  if ( saveStyles )
264  {
265  std::unique_ptr< QgsVectorLayer > res = std::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
266  if ( res )
267  {
268  QString errorMsg;
269  QDomDocument doc( QStringLiteral( "qgis" ) );
270  const QgsReadWriteContext context;
271  layer->exportNamedStyle( doc, errorMsg, context );
272  if ( !errorMsg.isEmpty() )
273  {
274  feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
275  }
276  else
277  {
278  if ( !res->importNamedStyle( doc, errorMsg ) )
279  {
280  feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
281  }
282  else
283  {
284  QgsSettings settings;
285  // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
286  const QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
287  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
288  res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
289  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
290  if ( !errorMsg.isEmpty() )
291  {
292  feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
293  }
294  }
295  }
296  }
297  else
298  {
299  feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
300  }
301  }
302  return true;
303  }
304 }
305 
qgsalgorithmpackage.h
QgsVectorFileWriter::SaveVectorOptions
Options to pass to writeAsVectorFormat()
Definition: qgsvectorfilewriter.h:458
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:161
QgsMapLayerType::MeshLayer
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
QgsReadWriteContext
The class is used as a container of context for various read/write operations on other objects.
Definition: qgsreadwritecontext.h:34
QgsMapLayerType::VectorLayer
@ VectorLayer
Vector layer.
QgsProcessingFeedback
Base class for providing feedback from a processing algorithm.
Definition: qgsprocessingfeedback.h:37
QgsProcessingFeedback::pushInfo
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Definition: qgsprocessingfeedback.cpp:77
QgsProcessingFeedback::reportError
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Definition: qgsprocessingfeedback.cpp:59
gdal::ogr_datasource_unique_ptr
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:119
QgsMapLayerType::AnnotationLayer
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsFeedback::isCanceled
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:67
QgsVectorFileWriter::SaveVectorOptions::layerName
QString layerName
Layer name. If let empty, it will be derived from the filename.
Definition: qgsvectorfilewriter.h:470
qgsogrutils.h
QgsSettings
This class is a composition of two QSettings instances:
Definition: qgssettings.h:61
QgsVectorFileWriter::SaveVectorOptions::saveMetadata
bool saveMetadata
Set to true to save layer metadata for the exported vector file.
Definition: qgsvectorfilewriter.h:547
QgsVectorFileWriter::CreateOrOverwriteLayer
@ CreateOrOverwriteLayer
Create or overwrite layer.
Definition: qgsvectorfilewriter.h:282
QgsProcessingParameterMultipleLayers
A parameter for processing algorithms which accepts multiple map layers.
Definition: qgsprocessingparameters.h:2097
QgsVectorFileWriter::SaveVectorOptions::attributes
QgsAttributeList attributes
Attributes to export (empty means all unless skipAttributeCreation is set)
Definition: qgsvectorfilewriter.h:497
QgsVectorFileWriter::SaveVectorOptions::feedback
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
Definition: qgsvectorfilewriter.h:532
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsMapLayer::metadata
QgsLayerMetadata metadata
Definition: qgsmaplayer.h:78
qgsgeometryengine.h
QgsVectorFileWriter::SaveVectorOptions::layerMetadata
QgsLayerMetadata layerMetadata
Layer metadata to save for the exported vector file.
Definition: qgsvectorfilewriter.h:555
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
QgsProcessingMultiStepFeedback
Processing feedback object for multi-step operations.
Definition: qgsprocessingfeedback.h:166
QgsMapLayerType::GroupLayer
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
QgsProcessingContext
Contains information about the context in which a processing algorithm is executed.
Definition: qgsprocessingcontext.h:46
QgsVectorFileWriter::SaveVectorOptions::fileEncoding
QString fileEncoding
Encoding to use.
Definition: qgsvectorfilewriter.h:476
QgsProcessingParameterFileDestination
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
Definition: qgsprocessingparameters.h:3451
QgsProcessingContext::defaultEncoding
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
Definition: qgsprocessingcontext.h:496
QgsProcessingParameterDefinition::setMetadata
void setMetadata(const QVariantMap &metadata)
Sets the parameter's freeform metadata.
Definition: qgsprocessingparameters.h:721
QgsMapLayerType::RasterLayer
@ RasterLayer
Raster layer.
QgsVectorFileWriter::SaveVectorOptions::onlySelectedFeatures
bool onlySelectedFeatures
Write only selected features of layer.
Definition: qgsvectorfilewriter.h:485
QgsProcessingContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Definition: qgsprocessingcontext.h:165
QgsSettings::setValue
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Definition: qgssettings.cpp:279
QgsMapLayer::exportNamedStyle
virtual void exportNamedStyle(QDomDocument &doc, QString &errorMsg, const QgsReadWriteContext &context=QgsReadWriteContext(), QgsMapLayer::StyleCategories categories=QgsMapLayer::AllStyleCategories) const
Export the properties of this layer as named style in a QDomDocument.
Definition: qgsmaplayer.cpp:1288
QgsVectorFileWriter::SaveVectorOptions::actionOnExistingFile
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
Definition: qgsvectorfilewriter.h:473
qgsvectorlayer.h
QgsProcessingFeedback::pushDebugInfo
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
Definition: qgsprocessingfeedback.cpp:95
QgsVectorFileWriter::writeAsVectorFormatV3
static QgsVectorFileWriter::WriterError writeAsVectorFormatV3(QgsVectorLayer *layer, const QString &fileName, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QString *errorMessage=nullptr, QString *newFilename=nullptr, QString *newLayer=nullptr)
Writes a layer out to a vector file.
Definition: qgsvectorfilewriter.cpp:3454
QgsFields::allAttributesList
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:376
QgsProcessingParameterBoolean
A boolean parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1709
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsProcessingOutputMultipleLayers
A multi-layer output for processing algorithms which create map layers, when the number and nature of...
Definition: qgsprocessingoutputs.h:268
qgssettings.h
QgsMapLayerType::VectorTileLayer
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsVectorFileWriter::NoError
@ NoError
Definition: qgsvectorfilewriter.h:171
QgsVectorLayer::selectedFeatureCount
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
Definition: qgsvectorlayer.cpp:3616
qgsvectorfilewriter.h
QgsFields::lookupField
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
QgsVectorFileWriter::SaveVectorOptions::skipAttributeCreation
bool skipAttributeCreation
Only write geometries.
Definition: qgsvectorfilewriter.h:494
QgsVectorFileWriter::SaveVectorOptions::driverName
QString driverName
OGR driver to use.
Definition: qgsvectorfilewriter.h:467
QgsMapLayerType::PointCloudLayer
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
QgsProcessingException
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
QgsMapLayerType::PluginLayer
@ PluginLayer
Plugin based layer.