QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 * )
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  return true;
79 }
80 
81 QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
82 {
83  const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
84  const bool saveStyles = parameterAsBoolean( parameters, QStringLiteral( "SAVE_STYLES" ), context );
85  QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
86  if ( packagePath.isEmpty() )
87  throw QgsProcessingException( QObject::tr( "No output file specified." ) );
88 
89  // delete existing geopackage if it exists
90  if ( overwrite && QFile::exists( packagePath ) )
91  {
92  feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
93  if ( !QFile( packagePath ).remove() )
94  {
95  throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ) );
96  }
97  }
98 
99  OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
100  if ( !hGpkgDriver )
101  {
102  throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
103  }
104 
105  gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
106  if ( !hDS )
107  throw QgsProcessingException( QObject::tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
108 
109  bool errored = false;
110 
111  QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
112 
113  QStringList outputLayers;
114  int i = 0;
115  for ( const auto &layer : mLayers )
116  {
117  if ( feedback->isCanceled() )
118  break;
119 
120  multiStepFeedback.setCurrentStep( i );
121  i++;
122 
123  if ( !layer )
124  {
125  // don't throw immediately - instead do what we can and error out later
126  feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
127  errored = true;
128  continue;
129  }
130 
131  feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
132 
133  switch ( layer->type() )
134  {
136  {
137  if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer.get() ), packagePath,
138  context, &multiStepFeedback, saveStyles ) )
139  errored = true;
140  else
141  outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
142  break;
143  }
144 
146  {
147  //not supported
148  feedback->pushDebugInfo( QObject::tr( "Packaging raster layers is not supported." ) );
149  errored = true;
150  break;
151  }
152 
154  //not supported
155  feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
156  errored = true;
157  break;
158 
160  //not supported
161  feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
162  errored = true;
163  break;
164  }
165  }
166 
167  if ( errored )
168  throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
169 
170  QVariantMap outputs;
171  outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
172  outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
173  return outputs;
174 }
175 
176 bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
177  QgsProcessingFeedback *feedback, bool saveStyles )
178 {
180  options.driverName = QStringLiteral( "GPKG" );
181  options.layerName = layer->name();
183  options.fileEncoding = context.defaultEncoding();
184  options.feedback = feedback;
185 
186  // remove any existing FID field, let this be completely recreated
187  // since many layer sources have fid fields which are not compatible with gpkg requirements
188  QgsFields fields = layer->fields();
189  const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
190 
191  options.attributes = fields.allAttributesList();
192  if ( fidIndex >= 0 )
193  options.attributes.removeAll( fidIndex );
194  if ( options.attributes.isEmpty() )
195  {
196  // fid was the only field
197  options.skipAttributeCreation = true;
198  }
199 
200  QString error;
201  QString newFilename;
202  QString newLayer;
203  if ( QgsVectorFileWriter::writeAsVectorFormat( layer, path, options, &newFilename, &error, &newLayer ) != QgsVectorFileWriter::NoError )
204  {
205  feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
206  return false;
207  }
208  else
209  {
210  if ( saveStyles )
211  {
212  std::unique_ptr< QgsVectorLayer > res = qgis::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
213  if ( res )
214  {
215  QString errorMsg;
216  QDomDocument doc( QStringLiteral( "qgis" ) );
217  QgsReadWriteContext context;
218  layer->exportNamedStyle( doc, errorMsg, context );
219  if ( !errorMsg.isEmpty() )
220  {
221  feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
222  }
223  else
224  {
225  if ( !res->importNamedStyle( doc, errorMsg ) )
226  {
227  feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
228  }
229  else
230  {
231  QgsSettings settings;
232  // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
233  QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
234  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
235  res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
236  settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
237  if ( !errorMsg.isEmpty() )
238  {
239  feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
240  }
241  }
242  }
243  }
244  else
245  {
246  feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
247  }
248  }
249  return true;
250  }
251 }
252 
int lookupField(const QString &fieldName) const
Looks up field&#39;s index from the field name.
Definition: qgsfields.cpp:324
A boolean parameter for processing algorithms.
The class is used as a container of context for various read/write operations on other objects...
A parameter for processing algorithms which accepts multiple map layers.
QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile
Action on existing file.
Base class for all map layer types.
Definition: qgsmaplayer.h:78
Base class for providing feedback from a processing algorithm.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
Processing feedback object for multi-step operations.
QgsAttributeList attributes
Attributes to export (empty means all unless skipAttributeCreation is set)
void setCurrentStep(int step)
Sets the step which is being executed.
Container of fields for a vector layer.
Definition: qgsfields.h:42
Options to pass to writeAsVectorFormat()
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList allAttributesList() const
Utility function to get list of attribute indexes.
Definition: qgsfields.cpp:351
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
virtual void pushDebugInfo(const QString &info)
Pushes an informational message containing debugging helpers from the algorithm.
static QgsVectorFileWriter::WriterError writeAsVectorFormat(QgsVectorLayer *layer, const QString &fileName, const QString &fileEncoding, const QgsCoordinateReferenceSystem &destCRS=QgsCoordinateReferenceSystem(), const QString &driverName="GPKG", bool onlySelected=false, QString *errorMessage=nullptr, const QStringList &datasourceOptions=QStringList(), const QStringList &layerOptions=QStringList(), bool skipAttributeCreation=false, QString *newFilename=nullptr, QgsVectorFileWriter::SymbologyExport symbologyExport=QgsVectorFileWriter::NoSymbology, double symbologyScale=1.0, const QgsRectangle *filterExtent=nullptr, QgsWkbTypes::Type overrideGeometryType=QgsWkbTypes::Unknown, bool forceMulti=false, bool includeZ=false, const QgsAttributeList &attributes=QgsAttributeList(), QgsVectorFileWriter::FieldValueConverter *fieldValueConverter=nullptr, QString *newLayer=nullptr)
Write contents of vector layer to an (OGR supported) vector format.
A generic file based destination parameter, for specifying the destination path for a file (non-map l...
A multi-layer output for processing algorithms which create map layers, when the number and nature of...
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.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
bool skipAttributeCreation
Only write geometries.
QString defaultEncoding() const
Returns the default encoding to use for newly created files.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
QString name
Definition: qgsmaplayer.h:82
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
QString layerName
Layer name. If let empty, it will be derived from the filename.
std::unique_ptr< std::remove_pointer< OGRDataSourceH >::type, OGRDataSourceDeleter > ogr_datasource_unique_ptr
Scoped OGR data source.
Definition: qgsogrutils.h:114
QgsFeedback * feedback
Optional feedback object allowing cancellation of layer save.
Represents a vector layer which manages a vector based data sets.
Contains information about the context in which a processing algorithm is executed.