diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index 84ff50562e6..169ed733be2 100644 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -91,6 +91,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmjoinbynearest.cpp processing/qgsalgorithmjoinwithlines.cpp processing/qgsalgorithmkmeansclustering.cpp + processing/qgsalgorithmlayoutatlastoimage.cpp processing/qgsalgorithmlayouttoimage.cpp processing/qgsalgorithmlayouttopdf.cpp processing/qgsalgorithmlinedensity.cpp diff --git a/src/analysis/processing/qgsalgorithmlayoutatlastoimage.cpp b/src/analysis/processing/qgsalgorithmlayoutatlastoimage.cpp new file mode 100644 index 00000000000..39a6df7c29f --- /dev/null +++ b/src/analysis/processing/qgsalgorithmlayoutatlastoimage.cpp @@ -0,0 +1,250 @@ +/*************************************************************************** + qgsalgorithmlayoutatlastoimage.cpp + --------------------- + begin : June 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 "qgsalgorithmlayoutatlastoimage.h" +#include "qgslayout.h" +#include "qgslayoutatlas.h" +#include "qgsprintlayout.h" +#include "qgsprocessingoutputs.h" +#include "qgslayoutexporter.h" + +#include + +///@cond PRIVATE + +QString QgsLayoutAtlasToImageAlgorithm::name() const +{ + return QStringLiteral( "printlayoutatlastoimage" ); +} + +QString QgsLayoutAtlasToImageAlgorithm::displayName() const +{ + return QObject::tr( "Export print layout atlas as image(s)" ); +} + +QStringList QgsLayoutAtlasToImageAlgorithm::tags() const +{ + return QObject::tr( "layout,atlas,composer,composition,save,png,jpeg,jpg" ).split( ',' ); +} + +QString QgsLayoutAtlasToImageAlgorithm::group() const +{ + return QObject::tr( "Cartography" ); +} + +QString QgsLayoutAtlasToImageAlgorithm::groupId() const +{ + return QStringLiteral( "cartography" ); +} + +QString QgsLayoutAtlasToImageAlgorithm::shortDescription() const +{ + return QObject::tr( "Exports a print layout atlas as one or more images." ); +} + +QString QgsLayoutAtlasToImageAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm outputs a print layout atkas as one or more images file (e.g. PNG or JPEG images)." ); +} + +void QgsLayoutAtlasToImageAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterLayout( QStringLiteral( "LAYOUT" ), QObject::tr( "Print layout" ) ) ); + + addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "COVERAGE_LAYER" ), QObject::tr( "Atlas coverage layer" ) ) ); + + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FILTER_EXPRESSION" ), QObject::tr( "Atlas coverage layer's filter expression" ), QString(), QStringLiteral( "COVERAGE_LAYER" ), true ) ); + + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "SORTBY_EXPRESSION" ), QObject::tr( "Atlas coverage layer's feature 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 QgsProcessingParameterExpression( QStringLiteral( "FILENAME_EXPRESSION" ), QObject::tr( "Output filename expression" ), QStringLiteral( "'output_'||@atlas_featurenumber" ), QStringLiteral( "COVERAGE_LAYER" ) ) ); + + addParameter( new QgsProcessingParameterFile( QStringLiteral( "FOLDER" ), QObject::tr( "Output folder" ), QgsProcessingParameterFile::Folder ) ); + + QStringList imageFormats; + const auto supportedImageFormats { QImageWriter::supportedImageFormats() }; + for ( const auto format : QImageWriter::supportedImageFormats() ) + { + if ( format == QByteArray( "svg" ) ) + continue; + imageFormats << QString( format ); + } + std::unique_ptr< QgsProcessingParameterEnum > extensionParam = qgis::make_unique< QgsProcessingParameterEnum >( QStringLiteral( "EXTENSION" ), QObject::tr( "Image format" ), imageFormats, false, imageFormats.indexOf( QStringLiteral( "png" ) ) ); + extensionParam->setFlags( extensionParam->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); + addParameter( extensionParam.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 > appendGeorefParam = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "GEOREFERENCE" ), QObject::tr( "Generate world file" ), 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 > antialias = qgis::make_unique< QgsProcessingParameterBoolean >( QStringLiteral( "ANTIALIAS" ), QObject::tr( "Enable antialiasing" ), true ); + antialias->setFlags( antialias->flags() | QgsProcessingParameterDefinition::FlagAdvanced ); + addParameter( antialias.release() ); +} + +QgsProcessingAlgorithm::Flags QgsLayoutAtlasToImageAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | FlagNoThreading; +} + +QgsLayoutAtlasToImageAlgorithm *QgsLayoutAtlasToImageAlgorithm::createInstance() const +{ + return new QgsLayoutAtlasToImageAlgorithm(); +} + +QVariantMap QgsLayoutAtlasToImageAlgorithm::processAlgorithm( const QVariantMap ¶meters, 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() ) ); + + QString error; + QgsLayoutAtlas *atlas = layout->atlas(); + + QgsVectorLayer *previousCoverageLayer = atlas->coverageLayer(); + const bool previousEnabled = atlas->enabled(); + const QString previousFilenameExpression = atlas->filenameExpression(); + const QString previousFilterExpression = atlas->filterExpression(); + const bool previousSortFeatures = atlas->sortFeatures(); + const bool previousSortAscending = atlas->sortAscending(); + const QString previousSortExpression = atlas->sortExpression(); + + auto restoreAtlas = [ atlas, + previousCoverageLayer, + previousEnabled, + previousFilenameExpression, + previousFilterExpression, + previousSortFeatures, + previousSortAscending, + previousSortExpression ]() + { + QString error; + atlas->setEnabled( previousEnabled ); + atlas->setCoverageLayer( previousCoverageLayer ); + atlas->setFilenameExpression( previousFilenameExpression, error ); + atlas->setFilterExpression( previousFilterExpression, error ); + atlas->setSortFeatures( previousSortFeatures ); + atlas->setSortAscending( previousSortAscending ); + atlas->setSortExpression( previousSortExpression ); + }; + + QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "COVERAGE_LAYER" ), context ); + atlas->setEnabled( true ); + atlas->setCoverageLayer( layer ); + + QString expression = parameterAsString( parameters, QStringLiteral( "FILENAME_EXPRESSION" ), context ); + atlas->setFilenameExpression( expression, error ); + if ( !error.isEmpty() ) + { + restoreAtlas(); + throw QgsProcessingException( QObject::tr( "Error setting atlas filename expression" ) ); + } + + expression = parameterAsString( parameters, QStringLiteral( "FILTER_EXPRESSION" ), context ); + atlas->setFilterExpression( expression, error ); + + 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 ); + } + + const QString directory = parameterAsFileOutput( parameters, QStringLiteral( "FOLDER" ), context ); + QString fileName = QDir( directory ).filePath( QStringLiteral( "atlas" ) ); + + QStringList imageFormats; + const auto supportedImageFormats { QImageWriter::supportedImageFormats() }; + for ( const auto format : QImageWriter::supportedImageFormats() ) + { + if ( format == QByteArray( "svg" ) ) + continue; + imageFormats << QString( format ); + } + int idx = parameterAsEnum( parameters, QStringLiteral( "EXTENSION" ), context ); + QString extension = '.' + imageFormats.at( idx ); + + QgsLayoutExporter exporter( layout ); + QgsLayoutExporter::ImageExportSettings settings; + + if ( parameters.value( QStringLiteral( "DPI" ) ).isValid() ) + { + settings.dpi = parameterAsDouble( parameters, QStringLiteral( "DPI" ), context ); + } + + settings.exportMetadata = parameterAsBool( parameters, QStringLiteral( "INCLUDE_METADATA" ), context ); + settings.generateWorldFile = parameterAsBool( parameters, QStringLiteral( "GEOREFERENCE" ), context ); + + if ( parameterAsBool( parameters, QStringLiteral( "ANTIALIAS" ), context ) ) + settings.flags = settings.flags | QgsLayoutRenderContext::FlagAntialiasing; + else + settings.flags = settings.flags & ~QgsLayoutRenderContext::FlagAntialiasing; + + QgsLayoutExporter::ExportResult result = exporter.exportToImage( atlas, fileName, extension, settings, error, feedback ); + restoreAtlas(); + + switch ( result ) + { + case QgsLayoutExporter::Success: + { + feedback->pushInfo( QObject::tr( "Successfully exported layout to %1" ).arg( QDir::toNativeSeparators( directory ) ) ); + break; + } + + case QgsLayoutExporter::FileError: + throw QgsProcessingException( QObject::tr( "Cannot write to %1.\n\nThis file may be open in another application." ).arg( QDir::toNativeSeparators( directory ) ) ); + + 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::PrintError: + case QgsLayoutExporter::Canceled: + // no meaning for imageexports, will not be encountered + break; + } + + feedback->setProgress( 100 ); + + QVariantMap outputs; + outputs.insert( QStringLiteral( "FOLDER" ), directory ); + return outputs; +} + +///@endcond + diff --git a/src/analysis/processing/qgsalgorithmlayoutatlastoimage.h b/src/analysis/processing/qgsalgorithmlayoutatlastoimage.h new file mode 100644 index 00000000000..814db235fc9 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmlayoutatlastoimage.h @@ -0,0 +1,60 @@ +/*************************************************************************** + qgsalgorithmlayoutatlastoimage.h + --------------------- + begin : June 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 QGSALGORITHMLAYOUTATLASTOIMAGE_H +#define QGSALGORITHMLAYOUTATLASTOIMAGE_H + +#define SIP_NO_FILE + +#include "qgis_sip.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native export layout to image algorithm. + */ +class QgsLayoutAtlasToImageAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsLayoutAtlasToImageAlgorithm() = 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; + QgsLayoutAtlasToImageAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMLAYOUTATLASTOIMAGE_H + + diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index 24c424de034..863c5a57b8b 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -87,6 +87,7 @@ #include "qgsalgorithminterpolatepoint.h" #include "qgsalgorithmintersection.h" #include "qgsalgorithmkmeansclustering.h" +#include "qgsalgorithmlayoutatlastoimage.h" #include "qgsalgorithmlayouttoimage.h" #include "qgsalgorithmlayouttopdf.h" #include "qgsalgorithmlinedensity.h" @@ -313,6 +314,7 @@ void QgsNativeAlgorithms::loadAlgorithms() addAlgorithm( new QgsKMeansClusteringAlgorithm() ); addAlgorithm( new QgsLayerToBookmarksAlgorithm() ); addAlgorithm( new QgsLayoutMapExtentToLayerAlgorithm() ); + addAlgorithm( new QgsLayoutAtlasToImageAlgorithm() ); addAlgorithm( new QgsLayoutToImageAlgorithm() ); addAlgorithm( new QgsLayoutToPdfAlgorithm() ); addAlgorithm( new QgsLineDensityAlgorithm() ); diff --git a/src/ui/qgstextformatwidgetbase.ui b/src/ui/qgstextformatwidgetbase.ui index d4ca2cdf3f1..ad947f84e62 100644 --- a/src/ui/qgstextformatwidgetbase.ui +++ b/src/ui/qgstextformatwidgetbase.ui @@ -5913,6 +5913,16 @@ font-style: italic; Obstacles + + + + true + + + When activated, featuring acting as obstacles discourage labels and diagrams from covering them. + + + @@ -5928,7 +5938,7 @@ font-style: italic; true - Discourage labels and diagrams from covering features + Features act as obstacles