QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
Container of fields for a vector layer.
Definition: qgsfields.h:45
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:376
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:349
Base class for all map layer types.
Definition: qgsmaplayer.h:73
QString name
Definition: qgsmaplayer.h:76
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.
QgsLayerMetadata metadata
Definition: qgsmaplayer.h:78
Contains information about the context in which a processing algorithm is executed.
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Processing feedback object for multi-step operations.
A multi-layer output for processing algorithms which create map layers, when the number and nature of...
A boolean parameter for processing algorithms.
void setMetadata(const QVariantMap &metadata)
Sets the parameter's freeform metadata.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A parameter for processing algorithms which accepts multiple map layers.
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
The class is used as a container of context for various read/write operations on other objects.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Options to pass to writeAsVectorFormat()
QgsLayerMetadata layerMetadata
Layer metadata to save for the exported vector file.
QString layerName
Layer name. If let empty, it will be derived from the filename.
bool saveMetadata
Set to true to save layer metadata for the exported vector file.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
QgsAttributeList attributes
Attributes to export (empty means all unless skipAttributeCreation is set)
bool onlySelectedFeatures
Write only selected features of layer.
bool skipAttributeCreation
Only write geometries.
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
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.
@ CreateOrOverwriteLayer
Create or overwrite layer.
Represents a vector layer which manages a vector based data sets.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
int selectedFeatureCount() const
Returns the number of features that are selected in this layer.
@ PointCloudLayer
Point cloud layer. Added in QGIS 3.18.
@ MeshLayer
Mesh layer. Added in QGIS 3.2.
@ VectorLayer
Vector layer.
@ RasterLayer
Raster layer.
@ GroupLayer
Composite group layer. Added in QGIS 3.24.
@ VectorTileLayer
Vector tile layer. Added in QGIS 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
@ PluginLayer
Plugin based layer.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:118