Branch data Line data Source code
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 : :
25 : : ///@cond PRIVATE
26 : :
27 : 0 : QString QgsPackageAlgorithm::name() const
28 : : {
29 : 0 : return QStringLiteral( "package" );
30 : : }
31 : :
32 : 0 : QString QgsPackageAlgorithm::displayName() const
33 : : {
34 : 0 : return QObject::tr( "Package layers" );
35 : : }
36 : :
37 : 0 : QStringList QgsPackageAlgorithm::tags() const
38 : : {
39 : 0 : return QObject::tr( "geopackage,collect,merge,combine,styles" ).split( ',' );
40 : 0 : }
41 : :
42 : 0 : QString QgsPackageAlgorithm::group() const
43 : : {
44 : 0 : return QObject::tr( "Database" );
45 : : }
46 : :
47 : 0 : QString QgsPackageAlgorithm::groupId() const
48 : : {
49 : 0 : return QStringLiteral( "database" );
50 : : }
51 : :
52 : 0 : void QgsPackageAlgorithm::initAlgorithm( const QVariantMap & )
53 : : {
54 : 0 : addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
55 : 0 : QgsProcessingParameterFileDestination *outputParameter = new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination GeoPackage" ), QObject::tr( "GeoPackage files (*.gpkg)" ) );
56 : 0 : outputParameter->setMetadata( QVariantMap( {{QStringLiteral( "widget_wrapper" ), QVariantMap( {{QStringLiteral( "dontconfirmoverwrite" ), true }} ) }} ) );
57 : 0 : addParameter( outputParameter );
58 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing GeoPackage" ), false ) );
59 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SAVE_STYLES" ), QObject::tr( "Save layer styles into GeoPackage" ), true ) );
60 : 0 : addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SELECTED_FEATURES_ONLY" ), QObject::tr( "Save only selected features" ), false ) );
61 : 0 : addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) );
62 : 0 : }
63 : :
64 : 0 : QString QgsPackageAlgorithm::shortHelpString() const
65 : : {
66 : 0 : return QObject::tr( "This algorithm collects a number of existing layers and packages them together into a single GeoPackage database." );
67 : : }
68 : :
69 : 0 : QgsPackageAlgorithm *QgsPackageAlgorithm::createInstance() const
70 : : {
71 : 0 : return new QgsPackageAlgorithm();
72 : : }
73 : :
74 : 0 : bool QgsPackageAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
75 : : {
76 : 0 : const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
77 : 0 : for ( QgsMapLayer *layer : layers )
78 : : {
79 : 0 : mLayers.emplace_back( layer->clone() );
80 : : }
81 : :
82 : 0 : if ( mLayers.empty() )
83 : 0 : feedback->reportError( QObject::tr( "No layers selected, geopackage will be empty" ), false );
84 : :
85 : : return true;
86 : 0 : }
87 : :
88 : 0 : QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
89 : : {
90 : 0 : const bool overwrite = parameterAsBoolean( parameters, QStringLiteral( "OVERWRITE" ), context );
91 : 0 : const bool saveStyles = parameterAsBoolean( parameters, QStringLiteral( "SAVE_STYLES" ), context );
92 : 0 : const bool selectedFeaturesOnly = parameterAsBoolean( parameters, QStringLiteral( "SELECTED_FEATURES_ONLY" ), context );
93 : 0 : QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
94 : 0 : if ( packagePath.isEmpty() )
95 : 0 : throw QgsProcessingException( QObject::tr( "No output file specified." ) );
96 : :
97 : : // delete existing geopackage if it exists
98 : 0 : if ( overwrite && QFile::exists( packagePath ) )
99 : : {
100 : 0 : feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
101 : 0 : if ( !QFile( packagePath ).remove() )
102 : : {
103 : 0 : throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ).arg( packagePath ) );
104 : : }
105 : 0 : }
106 : :
107 : 0 : OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
108 : 0 : if ( !hGpkgDriver )
109 : : {
110 : 0 : throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
111 : : }
112 : :
113 : 0 : gdal::ogr_datasource_unique_ptr hDS;
114 : :
115 : 0 : if ( !QFile::exists( packagePath ) )
116 : : {
117 : 0 : hDS = gdal::ogr_datasource_unique_ptr( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
118 : 0 : if ( !hDS )
119 : 0 : throw QgsProcessingException( QObject::tr( "Creation of database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
120 : 0 : }
121 : : else
122 : : {
123 : 0 : hDS = gdal::ogr_datasource_unique_ptr( OGROpen( packagePath.toUtf8().constData(), true, nullptr ) );
124 : 0 : if ( !hDS )
125 : 0 : throw QgsProcessingException( QObject::tr( "Opening database %1 failed (OGR error: %2)" ).arg( packagePath, QString::fromUtf8( CPLGetLastErrorMsg() ) ) );
126 : : }
127 : :
128 : :
129 : 0 : bool errored = false;
130 : :
131 : 0 : QgsProcessingMultiStepFeedback multiStepFeedback( mLayers.size(), feedback );
132 : :
133 : 0 : QStringList outputLayers;
134 : 0 : int i = 0;
135 : 0 : for ( const auto &layer : mLayers )
136 : : {
137 : 0 : if ( feedback->isCanceled() )
138 : 0 : break;
139 : :
140 : 0 : multiStepFeedback.setCurrentStep( i );
141 : 0 : i++;
142 : :
143 : 0 : if ( !layer )
144 : : {
145 : : // don't throw immediately - instead do what we can and error out later
146 : 0 : feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
147 : 0 : errored = true;
148 : 0 : continue;
149 : : }
150 : :
151 : 0 : feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( mLayers.size() ).arg( layer ? layer->name() : QString() ) );
152 : :
153 : 0 : switch ( layer->type() )
154 : : {
155 : : case QgsMapLayerType::VectorLayer:
156 : : {
157 : 0 : QgsVectorLayer *vectorLayer = qobject_cast<QgsVectorLayer *>( layer.get() );
158 : 0 : bool onlySaveSelected = vectorLayer->selectedFeatureCount() > 0 && selectedFeaturesOnly;
159 : 0 : if ( !packageVectorLayer( vectorLayer, packagePath, context, &multiStepFeedback, saveStyles, onlySaveSelected ) )
160 : 0 : errored = true;
161 : : else
162 : 0 : outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
163 : 0 : break;
164 : : }
165 : :
166 : : case QgsMapLayerType::RasterLayer:
167 : : {
168 : : //not supported
169 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging raster layers is not supported." ) );
170 : 0 : errored = true;
171 : 0 : break;
172 : : }
173 : :
174 : : case QgsMapLayerType::PluginLayer:
175 : : //not supported
176 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
177 : 0 : errored = true;
178 : 0 : break;
179 : :
180 : : case QgsMapLayerType::MeshLayer:
181 : : //not supported
182 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging mesh layers is not supported." ) );
183 : 0 : errored = true;
184 : 0 : break;
185 : :
186 : : case QgsMapLayerType::PointCloudLayer:
187 : : //not supported
188 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging point cloud layers is not supported." ) );
189 : 0 : errored = true;
190 : 0 : break;
191 : :
192 : : case QgsMapLayerType::VectorTileLayer:
193 : : //not supported
194 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging vector tile layers is not supported." ) );
195 : 0 : errored = true;
196 : 0 : break;
197 : :
198 : : case QgsMapLayerType::AnnotationLayer:
199 : : //not supported
200 : 0 : feedback->pushDebugInfo( QObject::tr( "Packaging annotation layers is not supported." ) );
201 : 0 : errored = true;
202 : 0 : break;
203 : : }
204 : : }
205 : :
206 : 0 : if ( errored )
207 : 0 : throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );
208 : :
209 : 0 : QVariantMap outputs;
210 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
211 : 0 : outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
212 : 0 : return outputs;
213 : 0 : }
214 : :
215 : 0 : bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
216 : : QgsProcessingFeedback *feedback, bool saveStyles, bool selectedFeaturesOnly )
217 : : {
218 : 0 : QgsVectorFileWriter::SaveVectorOptions options;
219 : 0 : options.driverName = QStringLiteral( "GPKG" );
220 : 0 : options.layerName = layer->name();
221 : 0 : options.actionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
222 : 0 : options.fileEncoding = context.defaultEncoding();
223 : 0 : options.onlySelectedFeatures = selectedFeaturesOnly;
224 : 0 : options.feedback = feedback;
225 : :
226 : : // remove any existing FID field, let this be completely recreated
227 : : // since many layer sources have fid fields which are not compatible with gpkg requirements
228 : 0 : QgsFields fields = layer->fields();
229 : 0 : const int fidIndex = fields.lookupField( QStringLiteral( "fid" ) );
230 : :
231 : 0 : options.attributes = fields.allAttributesList();
232 : 0 : if ( fidIndex >= 0 )
233 : 0 : options.attributes.removeAll( fidIndex );
234 : 0 : if ( options.attributes.isEmpty() )
235 : : {
236 : : // fid was the only field
237 : 0 : options.skipAttributeCreation = true;
238 : 0 : }
239 : :
240 : 0 : QString error;
241 : 0 : QString newFilename;
242 : 0 : QString newLayer;
243 : 0 : if ( QgsVectorFileWriter::writeAsVectorFormatV3( layer, path, context.transformContext(), options, &error, &newFilename, &newLayer ) != QgsVectorFileWriter::NoError )
244 : : {
245 : 0 : feedback->reportError( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
246 : 0 : return false;
247 : : }
248 : : else
249 : : {
250 : 0 : if ( saveStyles )
251 : : {
252 : 0 : std::unique_ptr< QgsVectorLayer > res = std::make_unique< QgsVectorLayer >( QStringLiteral( "%1|layername=%2" ).arg( newFilename, newLayer ) );
253 : 0 : if ( res )
254 : : {
255 : 0 : QString errorMsg;
256 : 0 : QDomDocument doc( QStringLiteral( "qgis" ) );
257 : 0 : QgsReadWriteContext context;
258 : 0 : layer->exportNamedStyle( doc, errorMsg, context );
259 : 0 : if ( !errorMsg.isEmpty() )
260 : : {
261 : 0 : feedback->reportError( QObject::tr( "Could not retrieve existing layer style: %1 " ).arg( errorMsg ) );
262 : 0 : }
263 : : else
264 : : {
265 : 0 : if ( !res->importNamedStyle( doc, errorMsg ) )
266 : : {
267 : 0 : feedback->reportError( QObject::tr( "Could not set existing layer style: %1 " ).arg( errorMsg ) );
268 : 0 : }
269 : : else
270 : : {
271 : 0 : QgsSettings settings;
272 : : // this is not nice -- but needed to avoid an "overwrite" prompt messagebox from the provider! This api needs a rework to avoid this.
273 : 0 : QVariant prevOverwriteStyle = settings.value( QStringLiteral( "qgis/overwriteStyle" ) );
274 : 0 : settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), true );
275 : 0 : res->saveStyleToDatabase( newLayer, QString(), true, QString(), errorMsg );
276 : 0 : settings.setValue( QStringLiteral( "qgis/overwriteStyle" ), prevOverwriteStyle );
277 : 0 : if ( !errorMsg.isEmpty() )
278 : : {
279 : 0 : feedback->reportError( QObject::tr( "Could not save layer style: %1 " ).arg( errorMsg ) );
280 : 0 : }
281 : 0 : }
282 : : }
283 : 0 : }
284 : : else
285 : : {
286 : 0 : feedback->reportError( QObject::tr( "Could not save layer style -- error loading: %1 %2" ).arg( newFilename, newLayer ) );
287 : : }
288 : 0 : }
289 : 0 : return true;
290 : : }
291 : 0 : }
292 : :
293 : : ///@endcond
|