QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  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  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  }
206  }
207 
208  if ( errored )
209  throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
210 
211  QVariantMap outputs;
212  outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
213  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
214  return outputs;
215 }
216 
217 bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
218  QgsProcessingFeedback *feedback, bool saveStyles, bool saveMetadata, bool selectedFeaturesOnly )
219 {
221  options.driverName = QStringLiteral( "GPKG" );
222  options.layerName = layer->name();
224  options.fileEncoding = context.defaultEncoding();
225  options.onlySelectedFeatures = selectedFeaturesOnly;
226  options.feedback = feedback;
227  if ( saveMetadata )
228  {
229  options.layerMetadata = layer->metadata();
230  options.saveMetadata = true;
231  }
232 
233  // remove any existing FID field, let this be completely recreated
234  // since many layer sources have fid fields which are not compatible with gpkg requirements
235  QgsFields fields = layer->fields();
236  const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
237 
238  options.attributes = fields.allAttributesList();
239  if ( fidIndex >= 0 )
240  options.attributes.removeAll( fidIndex );
241  if ( options.attributes.isEmpty() )
242  {
243  // fid was the only field
244  options.skipAttributeCreation = true;
245  }
246 
247  QString error;
248  QString newFilename;
249  QString newLayer;
250  if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
251  {
252  feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
253  return false;
254  }
255  else
256  {
257  if ( saveStyles )
258  {
259  std::unique_ptr< QgsVectorLayer > res = std::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
260  if ( res )
261  {
262  QString errorMsg;
263  QDomDocument doc( QStringLiteral( "qgis" ) );
264  QgsReadWriteContext context;
265  layer->exportNamedStyle( doc, errorMsg, context );
266  if ( !errorMsg.isEmpty() )
267  {
268  feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
269  }
270  else
271  {
272  if ( !res->importNamedStyle( doc, errorMsg ) )
273  {
274  feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
275  }
276  else
277  {
278  QgsSettings settings;
279  // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
280  QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
281  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
282  res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
283  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
284  if ( !errorMsg.isEmpty() )
285  {
286  feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
287  }
288  }
289  }
290  }
291  else
292  {
293  feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
294  }
295  }
296  return true;
297  }
298 }
299 
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:371
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:344
Base class for all map layer types.
Definition: qgsmaplayer.h:70
QString name
Definition: qgsmaplayer.h:73
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:75
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.
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
Added in 3.18.
@ MeshLayer
Added in 3.2.
@ VectorTileLayer
Added in 3.14.
@ AnnotationLayer
Contains freeform, georeferenced annotations. Added in QGIS 3.16.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:116