QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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  addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) );
61 }
62 
63 QString QgsPackageAlgorithm::shortHelpString() const
64 {
65  return QObject::tr( "This algorithm collects a number of existing layers and packages them together into a single GeoPackage database." );
66 }
67 
68 QgsPackageAlgorithm *QgsPackageAlgorithm::createInstance() const
69 {
70  return new QgsPackageAlgorithm();
71 }
72 
73 bool QgsPackageAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
74 {
75  const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
76  for ( QgsMapLayer *layer : layers )
77  {
78  mLayers.emplace_back( layer->clone() );
79  }
80 
81  if ( mLayers.empty() )
82  feedback->reportError( QObject::tr( "No layers selected, geopackage will be empty" ), false );
83 
84  return true;
85 }
86 
87 QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
88 {
89  const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
90  const bool saveStyles = parameterAsBoolean( parameters, QStringLiteral( "SAVE_STYLES" ), context );
91  QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
92  if ( packagePath.isEmpty() )
93  throw QgsProcessingException( QObject::tr( "No output file specified." ) );
94 
95  // delete existing geopackage if it exists
96  if ( overwrite && QFile::exists( packagePath ) )
97  {
98  feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
99  if ( !QFile( packagePath ).remove() )
100  {
101  throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( packagePath ) );
102  }
103  }
104 
105  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
106  if ( !hGpkgDriver )
107  {
108  throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
109  }
110 
112 
113  if ( !QFile::exists( packagePath ) )
114  {
115  hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
116  if ( !hDS )
117  throw QgsProcessingException( QObject::tr( "Creation of database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
118  }
119  else
120  {
121  hDS = gdal::ogr_datasource_unique_ptr( OGROpen( packagePath.toUtf8().constData(), true, nullptr ) );
122  if ( !hDS )
123  throw QgsProcessingException( QObject::tr( "Opening database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
124  }
125 
126 
127  bool errored = false;
128 
129  QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
130 
131  QStringList outputLayers;
132  int i = 0;
133  for ( const auto &layer : mLayers )
134  {
135  if ( feedback->isCanceled() )
136  break;
137 
138  multiStepFeedback.setCurrentStep( i );
139  i++;
140 
141  if ( !layer )
142  {
143  // don't throw immediately - instead do what we can and error out later
144  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
145  errored = true;
146  continue;
147  }
148 
149  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
150 
151  switch ( layer->type() )
152  {
154  {
155  if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), packagePath,
156  context, &multiStepFeedback, saveStyles ) )
157  errored = true;
158  else
159  outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
160  break;
161  }
162 
164  {
165  //not supported
166  feedback->pushDebugInfo( QObject::tr( "Packaging raster layers is not supported." ) );
167  errored = true;
168  break;
169  }
170 
172  //not supported
173  feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
174  errored = true;
175  break;
176 
178  //not supported
179  feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
180  errored = true;
181  break;
182 
184  //not supported
185  feedback->pushDebugInfo( QObject::tr( "Packaging point cloud layers is not supported." ) );
186  errored = true;
187  break;
188 
190  //not supported
191  feedback->pushDebugInfo( QObject::tr( "Packaging vector tile layers is not supported." ) );
192  errored = true;
193  break;
194 
196  //not supported
197  feedback->pushDebugInfo( QObject::tr( "Packaging annotation layers is not supported." ) );
198  errored = true;
199  break;
200  }
201  }
202 
203  if ( errored )
204  throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
205 
206  QVariantMap outputs;
207  outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
208  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
209  return outputs;
210 }
211 
212 bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
213  QgsProcessingFeedback *feedback, bool saveStyles )
214 {
216  options.driverName = QStringLiteral( "GPKG" );
217  options.layerName = layer->name();
219  options.fileEncoding = context.defaultEncoding();
220  options.feedback = feedback;
221 
222  // remove any existing FID field, let this be completely recreated
223  // since many layer sources have fid fields which are not compatible with gpkg requirements
224  QgsFields fields = layer->fields();
225  const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
226 
227  options.attributes = fields.allAttributesList();
228  if ( fidIndex >= 0 )
229  options.attributes.removeAll( fidIndex );
230  if ( options.attributes.isEmpty() )
231  {
232  // fid was the only field
233  options.skipAttributeCreation = true;
234  }
235 
236  QString error;
237  QString newFilename;
238  QString newLayer;
239  if ( QgsVectorFileWriter::writeAsVectorFormatV2( layer, path, context.transformContext(), options, &newFilename, &newLayer, &error ) != QgsVectorFileWriter::NoError )
240  {
241  feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
242  return false;
243  }
244  else
245  {
246  if ( saveStyles )
247  {
248  std::unique_ptr< QgsVectorLayer > res = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
249  if ( res )
250  {
251  QString errorMsg;
252  QDomDocument doc( QStringLiteral( "qgis" ) );
253  QgsReadWriteContext context;
254  layer->exportNamedStyle( doc, errorMsg, context );
255  if ( !errorMsg.isEmpty() )
256  {
257  feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
258  }
259  else
260  {
261  if ( !res->importNamedStyle( doc, errorMsg ) )
262  {
263  feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
264  }
265  else
266  {
267  QgsSettings settings;
268  // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
269  QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
270  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
271  res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
272  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
273  if ( !errorMsg.isEmpty() )
274  {
275  feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
276  }
277  }
278  }
279  }
280  else
281  {
282  feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
283  }
284  }
285  return true;
286  }
287 }
288 
bool isCanceled() const
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:85
QString name
Definition: qgsmaplayer.h:88
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.
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:53
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()
QString layerName
Layer name. If let empty, it will be derived from the filename.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
QgsAttributeList attributes
Attributes to export (empty means all unless skipAttributeCreation is set)
bool skipAttributeCreation
Only write geometries.
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
static QgsVectorFileWriter::WriterError writeAsVectorFormatV2(QgsVectorLayer *layer, const QString &fileName, const QgsCoordinateTransformContext &transformContext, const QgsVectorFileWriter::SaveVectorOptions &options, QString *newFilename=nullptr, QString *newLayer=nullptr, QString *errorMessage=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.
@ 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:114