Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE][processing] Package layers algorithm
Allows multiple vector layers to be collected together into a
geopackage database output

Previously all existing methods avilable in QGIS rely on adding
layers one-at-a-time, which is tedious for large numbers of layers.

Sponsored by SMEC
  • Loading branch information
nyalldawson committed Nov 23, 2017
1 parent e6d86bb commit a8eee62
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -46,6 +46,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmminimumenclosingcircle.cpp
processing/qgsalgorithmmultiparttosinglepart.cpp
processing/qgsalgorithmorientedminimumboundingbox.cpp
processing/qgsalgorithmpackage.cpp
processing/qgsalgorithmpromotetomultipart.cpp
processing/qgsalgorithmrasterlayeruniquevalues.cpp
processing/qgsalgorithmremovenullgeometry.cpp
Expand Down
169 changes: 169 additions & 0 deletions src/analysis/processing/qgsalgorithmpackage.cpp
@@ -0,0 +1,169 @@
/***************************************************************************
qgsalgorithmpackage.cpp
---------------------
begin : November 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsalgorithmpackage.h"
#include "qgsgeometryengine.h"
#include "qgsogrutils.h"
#include "qgsvectorfilewriter.h"

///@cond PRIVATE

QString QgsPackageAlgorithm::name() const
{
return QStringLiteral( "package" );
}

QString QgsPackageAlgorithm::displayName() const
{
return QObject::tr( "Package layers" );
}

QStringList QgsPackageAlgorithm::tags() const
{
return QObject::tr( "geopackage,collect,merge,combine" ).split( ',' );
}

QString QgsPackageAlgorithm::group() const
{
return QObject::tr( "Database" );
}

void QgsPackageAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterMultipleLayers( QStringLiteral( "LAYERS" ), QObject::tr( "Input layers" ), QgsProcessing::TypeVector ) );
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination GeoPackage" ), QStringLiteral( "*.gpkg" ) ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing GeoPackage" ), false ) );
}

QString QgsPackageAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm collects a number of existing layers and packages them together into a single GeoPackage database." );
}

QgsPackageAlgorithm *QgsPackageAlgorithm::createInstance() const
{
return new QgsPackageAlgorithm();
}

QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
bool overwrite = parameterAsBool( parameters, QStringLiteral( "OVERWRITE" ), context );
QString packagePath = parameterAsString( parameters, QStringLiteral( "OUTPUT" ), context );
if ( packagePath.isEmpty() )
throw QgsProcessingException( QObject::tr( "No output file specified." ) );

// delete existing geopackage if it exists
if ( overwrite && QFile::exists( packagePath ) )
{
feedback->pushInfo( QObject::tr( "Removing existing file '%1'" ).arg( packagePath ) );
if ( !QFile( packagePath ).remove() )
{
throw QgsProcessingException( QObject::tr( "Could not remove existing file '%1'" ) );
}
}

OGRSFDriverH hGpkgDriver = OGRGetDriverByName( "GPKG" );
if ( !hGpkgDriver )
{
throw QgsProcessingException( QObject::tr( "GeoPackage driver not found." ) );
}

gdal::ogr_datasource_unique_ptr hDS( OGR_Dr_CreateDataSource( hGpkgDriver, packagePath.toUtf8().constData(), nullptr ) );
if ( !hDS )
throw QgsProcessingException( QObject::tr( "Creation of database failed (OGR error: %1)" ).arg( QString::fromUtf8( CPLGetLastErrorMsg() ) ) );

bool errored = false;
const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );

QgsProcessingMultiStepFeedback multiStepFeedback( layers.count(), feedback );

int i = 0;
for ( QgsMapLayer *layer : layers )
{
if ( feedback->isCanceled() )
break;

multiStepFeedback.setCurrentStep( i );
i++;

feedback->pushInfo( QObject::tr( "Packaging layer %1/%2: %3" ).arg( i ).arg( layers.count() ).arg( layer->name() ) );

if ( !layer )
{
// don't throw immediately - instead do what we can and error out later
feedback->pushDebugInfo( QObject::tr( "Error retrieving map layer." ) );
errored = true;
continue;
}

switch ( layer->type() )
{
case QgsMapLayer::VectorLayer:
{
if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer ), packagePath,
context, &multiStepFeedback ) )
errored = true;
break;
}

case QgsMapLayer::RasterLayer:
{
//not supported
feedback->pushDebugInfo( QObject::tr( "Raster layers are not currently supported." ) );
errored = true;
break;
}

case QgsMapLayer::PluginLayer:
//not supported
feedback->pushDebugInfo( QObject::tr( "Packaging plugin layers is not supported." ) );
errored = true;
break;
}
}

if ( errored )
throw QgsProcessingException( QObject::tr( "Error obtained while packaging one or more layers." ) );

QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
return outputs;
}

bool QgsPackageAlgorithm::packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context,
QgsProcessingFeedback *feedback )
{
QgsVectorFileWriter::SaveVectorOptions options;
options.driverName = QStringLiteral( "GPKG" );
options.layerName = layer->name();
options.actionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
options.fileEncoding = context.defaultEncoding();
options.feedback = feedback;

QString error;
if ( QgsVectorFileWriter::writeAsVectorFormat( layer, path, options, &error ) != QgsVectorFileWriter::NoError )
{
feedback->pushDebugInfo( QObject::tr( "Packaging layer failed: %1" ).arg( error ) );
return false;
}
else
{
return true;
}
}

///@endcond
62 changes: 62 additions & 0 deletions src/analysis/processing/qgsalgorithmpackage.h
@@ -0,0 +1,62 @@
/***************************************************************************
qgsalgorithmpackage.h
------------------
begin : November 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSALGORITHMPACKAGE_H
#define QGSALGORITHMPACKAGE_H

#define SIP_NO_FILE

#include "qgis.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

class QgsVectorLayer;

/**
* Native package layers algorithm.
*/
class QgsPackageAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsPackageAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
virtual QStringList tags() const override;
QString group() const override;
QString shortHelpString() const override;
QgsPackageAlgorithm *createInstance() const override SIP_FACTORY;

protected:

virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

private:

bool packageVectorLayer( QgsVectorLayer *layer, const QString &path, QgsProcessingContext &context, QgsProcessingFeedback *feedback );

};

///@endcond PRIVATE

#endif // QGSALGORITHMPACKAGE_H


2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -43,6 +43,7 @@
#include "qgsalgorithmminimumenclosingcircle.h"
#include "qgsalgorithmmultiparttosinglepart.h"
#include "qgsalgorithmorientedminimumboundingbox.h"
#include "qgsalgorithmpackage.h"
#include "qgsalgorithmpromotetomultipart.h"
#include "qgsalgorithmrasterlayeruniquevalues.h"
#include "qgsalgorithmremovenullgeometry.h"
Expand Down Expand Up @@ -118,6 +119,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsMinimumEnclosingCircleAlgorithm() );
addAlgorithm( new QgsMultipartToSinglepartAlgorithm() );
addAlgorithm( new QgsOrientedMinimumBoundingBoxAlgorithm() );
addAlgorithm( new QgsPackageAlgorithm() );
addAlgorithm( new QgsPromoteToMultipartAlgorithm() );
addAlgorithm( new QgsRasterLayerUniqueValuesReportAlgorithm() );
addAlgorithm( new QgsRemoveNullGeometryAlgorithm() );
Expand Down

0 comments on commit a8eee62

Please sign in to comment.