QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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  addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination GeoPackage" ), QObject::tr( "GeoPackage files (*.gpkg)" ) ) );
56  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing GeoPackage" ), false ) );
57  addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SAVE_STYLES" ), QObject::tr( "Save layer styles into GeoPackage" ), true ) );
58  addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) );
59 }
60 
61 QString QgsPackageAlgorithm::shortHelpString() const
62 {
63  return QObject::tr( "This algorithm collects a number of existing layers and packages them together into a single GeoPackage database." );
64 }
65 
66 QgsPackageAlgorithm *QgsPackageAlgorithm::createInstance() const
67 {
68  return new QgsPackageAlgorithm();
69 }
70 
71 bool QgsPackageAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
72 {
73  const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
74  for ( QgsMapLayer *layer : layers )
75  {
76  mLayers.emplace_back( layer->clone() );
77  }
78 
79  if ( mLayers.empty() )
80  feedback->reportError( QObject::tr( "No layers selected, geopackage will be empty" ), false );
81 
82  return true;
83 }
84 
85 QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
86 {
87  const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
88  const bool saveStyles = parameterAsBoolean( parameters, QStringLiteral( "SAVE_STYLES" ), context );
89  QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
90  if ( packagePath.isEmpty() )
91  throw QgsProcessingException( QObject::tr( "No output file specified." ) );
92 
93  // delete existing geopackage if it exists
94  if ( overwrite && QFile::exists( packagePath ) )
95  {
96  feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
97  if ( !QFile( packagePath ).remove() )
98  {
99  throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( packagePath ) );
100  }
101  }
102 
103  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
104  if ( !hGpkgDriver )
105  {
106  throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
107  }
108 
110 
111  if ( !QFile::exists( packagePath ) )
112  {
113  hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
114  if ( !hDS )
115  throw QgsProcessingException( QObject::tr( "Creation of database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
116  }
117  else
118  {
119  hDS = gdal::ogr_datasource_unique_ptr( OGROpen( packagePath.toUtf8().constData(), true, nullptr ) );
120  if ( !hDS )
121  throw QgsProcessingException( QObject::tr( "Opening database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
122  }
123 
124 
125  bool errored = false;
126 
127  QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
128 
129  QStringList outputLayers;
130  int i = 0;
131  for ( const auto &layer : mLayers )
132  {
133  if ( feedback->isCanceled() )
134  break;
135 
136  multiStepFeedback.setCurrentStep( i );
137  i++;
138 
139  if ( !layer )
140  {
141  // don't throw immediately - instead do what we can and error out later
142  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
143  errored = true;
144  continue;
145  }
146 
147  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
148 
149  switch ( layer->type() )
150  {
152  {
153  if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), packagePath,
154  context, &multiStepFeedback, saveStyles ) )
155  errored = true;
156  else
157  outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
158  break;
159  }
160 
162  {
163  //not supported
164  feedback->pushDebugInfo( QObject::tr( "Packaging raster layers is not supported." ) );
165  errored = true;
166  break;
167  }
168 
170  //not supported
171  feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
172  errored = true;
173  break;
174 
176  //not supported
177  feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
178  errored = true;
179  break;
180 
182  //not supported
183  feedback->pushDebugInfo( QObject::tr( "Packaging vector tile layers is not supported." ) );
184  errored = true;
185  break;
186  }
187  }
188 
189  if ( errored )
190  throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
191 
192  QVariantMap outputs;
193  outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
194  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
195  return outputs;
196 }
197 
198 bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
199  QgsProcessingFeedback *feedback, bool saveStyles )
200 {
202  options.driverName = QStringLiteral( "GPKG" );
203  options.layerName = layer->name();
205  options.fileEncoding = context.defaultEncoding();
206  options.feedback = feedback;
207 
208  // remove any existing FID field, let this be completely recreated
209  // since many layer sources have fid fields which are not compatible with gpkg requirements
210  QgsFields fields = layer->fields();
211  const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
212 
213  options.attributes = fields.allAttributesList();
214  if ( fidIndex >= 0 )
215  options.attributes.removeAll( fidIndex );
216  if ( options.attributes.isEmpty() )
217  {
218  // fid was the only field
219  options.skipAttributeCreation = true;
220  }
221 
222  QString error;
223  QString newFilename;
224  QString newLayer;
225  if ( QgsVectorFileWriter::writeAsVectorFormatV2( layer, path, context.transformContext(), options, &newFilename, &newLayer, &error ) != QgsVectorFileWriter::NoError )
226  {
227  feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
228  return false;
229  }
230  else
231  {
232  if ( saveStyles )
233  {
234  std::unique_ptr< QgsVectorLayer > res = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
235  if ( res )
236  {
237  QString errorMsg;
238  QDomDocument doc( QStringLiteral( "qgis" ) );
239  QgsReadWriteContext context;
240  layer->exportNamedStyle( doc, errorMsg, context );
241  if ( !errorMsg.isEmpty() )
242  {
243  feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
244  }
245  else
246  {
247  if ( !res->importNamedStyle( doc, errorMsg ) )
248  {
249  feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
250  }
251  else
252  {
253  QgsSettings settings;
254  // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
255  QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
256  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
257  res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
258  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
259  if ( !errorMsg.isEmpty() )
260  {
261  feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
262  }
263  }
264  }
265  }
266  else
267  {
268  feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
269  }
270  }
271  return true;
272  }
273 }
274 
qgsalgorithmpackage.h
QgsVectorFileWriter::SaveVectorOptions
Definition: qgsvectorfilewriter.h:443
QgsSettings::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Definition: qgssettings.cpp:174
QgsMapLayerType::MeshLayer
@ MeshLayer
Added in 3.2.
QgsReadWriteContext
Definition: qgsreadwritecontext.h:34
QgsMapLayerType::VectorLayer
@ VectorLayer
QgsProcessingFeedback
Definition: qgsprocessingfeedback.h:37
QgsProcessingFeedback::pushInfo
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Definition: qgsprocessingfeedback.cpp:48
QgsProcessingFeedback::reportError
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Definition: qgsprocessingfeedback.cpp:39
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:114
QgsFields
Definition: qgsfields.h:44
QgsVectorFileWriter::SaveVectorOptions::layerName
QString layerName
Layer name. If let empty, it will be derived from the filename.
Definition: qgsvectorfilewriter.h:455
qgsogrutils.h
QgsSettings
Definition: qgssettings.h:61
QgsVectorFileWriter::CreateOrOverwriteLayer
@ CreateOrOverwriteLayer
Create or overwrite layer.
Definition: qgsvectorfilewriter.h:267
QgsProcessingParameterMultipleLayers
Definition: qgsprocessingparameters.h:1756
QgsVectorFileWriter::SaveVectorOptions::attributes
QgsAttributeList attributes
Attributes to export (empty means all unless skipAttributeCreation is set)
Definition: qgsvectorfilewriter.h:481
QgsVectorFileWriter::SaveVectorOptions::feedback
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
Definition: qgsvectorfilewriter.h:512
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3280
qgsgeometryengine.h
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:53
QgsProcessingMultiStepFeedback
Definition: qgsprocessingfeedback.h:146
QgsProcessingContext
Definition: qgsprocessingcontext.h:43
QgsVectorFileWriter::SaveVectorOptions::fileEncoding
QString fileEncoding
Encoding to use.
Definition: qgsvectorfilewriter.h:461
QgsProcessingParameterFileDestination
Definition: qgsprocessingparameters.h:3005
QgsProcessingContext::defaultEncoding
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
Definition: qgsprocessingcontext.h:385
QgsMapLayerType::RasterLayer
@ RasterLayer
QgsProcessingContext::transformContext
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Definition: qgsprocessingcontext.h:135
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:289
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:1095
QgsVectorFileWriter::SaveVectorOptions::actionOnExistingFile
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
Definition: qgsvectorfilewriter.h:458
qgsvectorlayer.h
QgsProcessingFeedback::pushDebugInfo
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
Definition: qgsprocessingfeedback.cpp:66
QgsFeedback::isCanceled
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:66
QgsFields::allAttributesList
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:351
QgsProcessingParameterBoolean
Definition: qgsprocessingparameters.h:1439
QgsVectorLayer
Definition: qgsvectorlayer.h:385
QgsMapLayer
Definition: qgsmaplayer.h:81
QgsProcessingOutputMultipleLayers
Definition: qgsprocessingoutputs.h:248
qgssettings.h
QgsMapLayerType::VectorTileLayer
@ VectorTileLayer
Added in 3.14.
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:85
QgsVectorFileWriter::NoError
@ NoError
Definition: qgsvectorfilewriter.h:169
QgsVectorFileWriter::writeAsVectorFormatV2
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.
Definition: qgsvectorfilewriter.cpp:3151
qgsvectorfilewriter.h
QgsFields::lookupField
int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
Definition: qgsfields.cpp:324
QgsVectorFileWriter::SaveVectorOptions::skipAttributeCreation
bool skipAttributeCreation
Only write geometries.
Definition: qgsvectorfilewriter.h:478
QgsVectorFileWriter::SaveVectorOptions::driverName
QString driverName
OGR driver to use.
Definition: qgsvectorfilewriter.h:452
QgsProcessingException
Definition: qgsexception.h:82
QgsMapLayerType::PluginLayer
@ PluginLayer