Skip to content

Commit

Permalink
[FEATURE][processing] Export layout atlas as PDF algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
nirvn committed Aug 11, 2020
1 parent e1b65f3 commit 13be3de
Show file tree
Hide file tree
Showing 6 changed files with 392 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/analysis/CMakeLists.txt
Expand Up @@ -93,6 +93,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmjoinwithlines.cpp
processing/qgsalgorithmkmeansclustering.cpp
processing/qgsalgorithmlayoutatlastoimage.cpp
processing/qgsalgorithmlayoutatlastopdf.cpp
processing/qgsalgorithmlayouttoimage.cpp
processing/qgsalgorithmlayouttopdf.cpp
processing/qgsalgorithmlinedensity.cpp
Expand Down
286 changes: 286 additions & 0 deletions src/analysis/processing/qgsalgorithmlayoutatlastopdf.cpp
@@ -0,0 +1,286 @@
/***************************************************************************
qgsalgorithmlayoutatlastopdf.cpp
---------------------
begin : August 2020
copyright : (C) 2020 by Mathieu Pellerin
email : nirvn dot asia 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 "qgsalgorithmlayoutatlastopdf.h"
#include "qgslayout.h"
#include "qgslayoutatlas.h"
#include "qgslayoutitemmap.h"
#include "qgsprintlayout.h"
#include "qgsprocessingoutputs.h"
#include "qgslayoutexporter.h"

#include <QImageWriter>

///@cond PRIVATE

QString QgsLayoutAtlasToPdfAlgorithm::name() const
{
return QStringLiteral( "atlaslayouttopdf" );
}

QString QgsLayoutAtlasToPdfAlgorithm::displayName() const
{
return QObject::tr( "Export atlas layout as PDF" );
}

QStringList QgsLayoutAtlasToPdfAlgorithm::tags() const
{
return QObject::tr( "layout,atlas,composer,composition,save" ).split( ',' );
}

QString QgsLayoutAtlasToPdfAlgorithm::group() const
{
return QObject::tr( "Cartography" );
}

QString QgsLayoutAtlasToPdfAlgorithm::groupId() const
{
return QStringLiteral( "cartography" );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortDescription() const
{
return QObject::tr( "Exports an atlas layout as a PDF." );
}

QString QgsLayoutAtlasToPdfAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm outputs an atlas layout as a PDF file.\n\n"
"If a coverage layer is set, the selected layout's atlas settings exposed in this algorithm "
"will be overwritten. In this case, an empty filter or sort by expression will turn those "
"settings off." );
}

void QgsLayoutAtlasToPdfAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Atlas layout" ) ) );

addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "COVERAGE_LAYER" ), QObject::tr( "Coverage layer" ), QList< int >(), QVariant(), true ) );
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILTER_EXPRESSION" ), QObject::tr( "Filter expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Sort expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "SORTBY_REVERSE" ), QObject::tr( "Reverse sort order (used when a sort expression is provided)" ), false, true ) );

addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "PDF file" ), QObject::tr( "PDF Format" ) + " (*.pdf *.PDF)" ) );

std::unique_ptr< QgsProcessingParameterMultipleLayers > layersParam = qgis::make_unique< QgsProcessingParameterMultipleLayers>( QStringLiteral( "LAYERS" ), QObject::tr( "Map layers to assign to unlocked map item(s)" ), QgsProcessing::TypeMapLayer, QVariant(), true );
layersParam->setFlags( layersParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( layersParam.release() );

std::unique_ptr< QgsProcessingParameterNumber > dpiParam = qgis::make_unique< QgsProcessingParameterNumber >( QStringLiteral( "DPI" ), QObject::tr( "DPI (leave blank for default layout DPI)" ), QgsProcessingParameterNumber::Double, QVariant(), true, 0 );
dpiParam->setFlags( dpiParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( dpiParam.release() );

std::unique_ptr< QgsProcessingParameterBoolean > forceVectorParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "FORCE_VECTOR" ), QObject::tr( "Always export as vectors" ), false );
forceVectorParam->setFlags( forceVectorParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( forceVectorParam.release() );

std::unique_ptr< QgsProcessingParameterBoolean > appendGeorefParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "GEOREFERENCE" ), QObject::tr( "Append georeference information" ), true );
appendGeorefParam->setFlags( appendGeorefParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( appendGeorefParam.release() );

std::unique_ptr< QgsProcessingParameterBoolean > exportRDFParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "INCLUDE_METADATA" ), QObject::tr( "Export RDF metadata (title, author, etc.)" ), true );
exportRDFParam->setFlags( exportRDFParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( exportRDFParam.release() );

std::unique_ptr< QgsProcessingParameterBoolean > disableTiled = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "DISABLE_TILED" ), QObject::tr( "Disable tiled raster layer exports" ), false );
disableTiled->setFlags( disableTiled->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( disableTiled.release() );

std::unique_ptr< QgsProcessingParameterBoolean > simplify = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "SIMPLIFY" ), QObject::tr( "Simplify geometries to reduce output file size" ), true );
simplify->setFlags( simplify->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( simplify.release() );

QStringList textExportOptions
{
QObject::tr( "Always Export Text as Paths (Recommended)" ),
QObject::tr( "Always Export Text as Text Objects" )
};

std::unique_ptr< QgsProcessingParameterEnum > textFormat = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "TEXT_FORMAT" ), QObject::tr( "Text export" ), textExportOptions, false, 0 );
textFormat->setFlags( textFormat->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
addParameter( textFormat.release() );
}

QgsProcessingAlgorithm::Flags QgsLayoutAtlasToPdfAlgorithm::flags() const
{
return QgsProcessingAlgorithm::flags() | FlagNoThreading;
}

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

QVariantMap QgsLayoutAtlasToPdfAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
// this needs to be done in main thread, layouts are not thread safe
QgsPrintLayout *layout = parameterAsLayout( parameters, QStringLiteral( "LAYOUT" ), context );
if ( !layout )
throw QgsProcessingException( QObject::tr( "Cannot find layout with name \"%1\"" ).arg( parameters.value( QStringLiteral( "LAYOUT" ) ).toString() ) );

QgsLayoutAtlas *atlas = layout->atlas();

QgsVectorLayer *previousCoverageLayer = atlas->coverageLayer();
const bool previousEnabled = atlas->enabled();
const QString previousFilterExpression = atlas->filterExpression();
const bool previousSortFeatures = atlas->sortFeatures();
const bool previousSortAscending = atlas->sortAscending();
const QString previousSortExpression = atlas->sortExpression();
QStringList previousMapItemLayersReset;

auto restoreAtlas = [ layout, atlas,
previousCoverageLayer,
previousEnabled,
previousFilterExpression,
previousSortFeatures,
previousSortAscending,
previousSortExpression,
previousMapItemLayersReset ]()
{
QString error;
atlas->setEnabled( previousEnabled );
atlas->setCoverageLayer( previousCoverageLayer );
atlas->setFilterExpression( previousFilterExpression, error );
atlas->setSortFeatures( previousSortFeatures );
atlas->setSortAscending( previousSortAscending );
atlas->setSortExpression( previousSortExpression );

if ( previousMapItemLayersReset.size() > 0 )
{
const QList<QGraphicsItem *> items = layout->items();
for ( QGraphicsItem *graphicsItem : items )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemMap *map = dynamic_cast<QgsLayoutItemMap *>( item );
if ( map && previousMapItemLayersReset.contains( map->uuid() ) )
{
map->setLayers( QList<QgsMapLayer *>() );
}
}
}
};

QString expression, error;
QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "COVERAGE_LAYER" ), context );
if ( layer )
{
atlas->setEnabled( true );
atlas->setCoverageLayer( layer );

expression = parameterAsString( parameters, QStringLiteral( "FILTER_EXPRESSION" ), context );
atlas->setFilterExpression( expression, error );
if ( !expression.isEmpty() && !error.isEmpty() )
{
restoreAtlas();
throw QgsProcessingException( QObject::tr( "Error setting atlas filter expression" ) );
}
error.clear();

expression = parameterAsString( parameters, QStringLiteral( "SORTBY_EXPRESSION" ), context );
if ( !expression.isEmpty() )
{
const bool sortByReverse = parameterAsBool( parameters, QStringLiteral( "SORTBY_REVERSE" ), context );
atlas->setSortFeatures( true );
atlas->setSortExpression( expression );
atlas->setSortAscending( !sortByReverse );
}
else
{
atlas->setSortFeatures( false );
}
}
else if ( !atlas->enabled() )
{
throw QgsProcessingException( QObject::tr( "Layout being export doesn't have an enabled atlas" ) );
}

QgsLayoutExporter exporter( layout );
QgsLayoutExporter::PdfExportSettings settings;

if ( parameters.value( QStringLiteral( "DPI" ) ).isValid() )
{
settings.dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context );
}
settings.forceVectorOutput = parameterAsBool( parameters, QStringLiteral( "FORCE_VECTOR" ), context );
settings.appendGeoreference = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context );
settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context );
settings.simplifyGeometries = parameterAsBool( parameters, QStringLiteral( "SIMPLIFY" ), context );
settings.textRenderFormat = parameterAsEnum( parameters, QStringLiteral( "TEXT_FORMAT" ), context ) == 0 ? QgsRenderContext::TextFormatAlwaysOutlines : QgsRenderContext::TextFormatAlwaysText;

if ( parameterAsBool( parameters, QStringLiteral( "DISABLE_TILED" ), context ) )
settings.flags = settings.flags | QgsLayoutRenderContext::FlagDisableTiledRasterLayerRenders;
else
settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagDisableTiledRasterLayerRenders;

const QList< QgsMapLayer * > layers = parameterAsLayerList( parameters, QStringLiteral( "LAYERS" ), context );
if ( layers.size() > 0 )
{
const QList<QGraphicsItem *> items = layout->items();
for ( QGraphicsItem *graphicsItem : items )
{
QgsLayoutItem *item = dynamic_cast<QgsLayoutItem *>( graphicsItem );
QgsLayoutItemMap *map = dynamic_cast<QgsLayoutItemMap *>( item );
if ( map && !map->followVisibilityPreset() && !map->keepLayerSet() )
{
previousMapItemLayersReset << map->uuid();
map->setLayers( layers );
}
}
}

const QString dest = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT" ), context );

QgsLayoutExporter::ExportResult result = exporter.exportToPdf( atlas, dest, settings, error, feedback );
restoreAtlas();

switch ( result )
{
case QgsLayoutExporter::Success:
{
feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( dest ) ) );
break;
}

case QgsLayoutExporter::FileError:
throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( dest ) ) );

case QgsLayoutExporter::PrintError:
throw QgsProcessingException( QObject::tr( "Could not create print device." ) );

case QgsLayoutExporter::MemoryError:
throw QgsProcessingException( QObject::tr( "Trying to create the image "
"resulted in a memory overflow.\n\n"
"Please try a lower resolution or a smaller paper size." ) );

case QgsLayoutExporter::IteratorError:
throw QgsProcessingException( QObject::tr( "Error encountered while exporting atlas." ) );

case QgsLayoutExporter::SvgLayerError:
case QgsLayoutExporter::Canceled:
// no meaning for imageexports, will not be encountered
break;
}

feedback->setProgress( 100 );

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

///@endcond

60 changes: 60 additions & 0 deletions src/analysis/processing/qgsalgorithmlayoutatlastopdf.h
@@ -0,0 +1,60 @@
/***************************************************************************
qgsalgorithmlayoutatlastopdf.h
---------------------
begin : August 2020
copyright : (C) 2020 by Mathieu Pellerin
email : nirvn dot asia 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 QGSALGORITHMLAYOUTATLASTOPDF_H
#define QGSALGORITHMLAYOUTATLASTOPDF_H

#define SIP_NO_FILE

#include "qgis_sip.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

/**
* Native export layout to image algorithm.
*/
class QgsLayoutAtlasToPdfAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsLayoutAtlasToPdfAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
Flags flags() const override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortDescription() const override;
QString shortHelpString() const override;
QgsLayoutAtlasToPdfAlgorithm *createInstance() const override SIP_FACTORY;

protected:

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


};

///@endcond PRIVATE

#endif // QGSALGORITHMLAYOUTATLASTOPDF_H


2 changes: 2 additions & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -89,6 +89,7 @@
#include "qgsalgorithmintersection.h"
#include "qgsalgorithmkmeansclustering.h"
#include "qgsalgorithmlayoutatlastoimage.h"
#include "qgsalgorithmlayoutatlastopdf.h"
#include "qgsalgorithmlayouttoimage.h"
#include "qgsalgorithmlayouttopdf.h"
#include "qgsalgorithmlinedensity.h"
Expand Down Expand Up @@ -318,6 +319,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsLayerToBookmarksAlgorithm() );
addAlgorithm( new QgsLayoutMapExtentToLayerAlgorithm() );
addAlgorithm( new QgsLayoutAtlasToImageAlgorithm() );
addAlgorithm( new QgsLayoutAtlasToPdfAlgorithm() );
addAlgorithm( new QgsLayoutToImageAlgorithm() );
addAlgorithm( new QgsLayoutToPdfAlgorithm() );
addAlgorithm( new QgsLineDensityAlgorithm() );
Expand Down

0 comments on commit 13be3de

Please sign in to comment.