33#include <QtAlgorithms>
35#include "moc_qgslayoutatlas.cpp"
37using namespace Qt::StringLiterals;
42 , mFilenameExpressionString( u
"'output_'||@atlas_featurenumber"_s )
48 mLayout->setCustomProperty( u
"singleFile"_s,
true );
63 return mLayout.data();
68 QDomElement atlasElem = document.createElement( u
"Atlas"_s );
69 atlasElem.setAttribute( u
"enabled"_s, mEnabled ? u
"1"_s : u
"0"_s );
73 atlasElem.setAttribute( u
"coverageLayer"_s, mCoverageLayer.layerId );
74 atlasElem.setAttribute( u
"coverageLayerName"_s, mCoverageLayer.name );
75 atlasElem.setAttribute( u
"coverageLayerSource"_s, mCoverageLayer.source );
76 atlasElem.setAttribute( u
"coverageLayerProvider"_s, mCoverageLayer.provider );
80 atlasElem.setAttribute( u
"coverageLayer"_s, QString() );
83 if ( mLimitCoverageLayerRenderToCurrentFeature )
84 atlasElem.setAttribute( u
"limitCoverageLayerRenderToCurrentFeature"_s, u
"1"_s );
86 atlasElem.setAttribute( u
"hideCoverage"_s, mHideCoverage ? u
"1"_s : u
"0"_s );
87 atlasElem.setAttribute( u
"filenamePattern"_s, mFilenameExpressionString );
88 atlasElem.setAttribute( u
"pageNameExpression"_s, mPageNameExpression );
90 atlasElem.setAttribute( u
"sortFeatures"_s, mSortFeatures ? u
"1"_s : u
"0"_s );
93 atlasElem.setAttribute( u
"sortKey"_s, mSortExpression );
94 atlasElem.setAttribute( u
"sortAscending"_s, mSortAscending ? u
"1"_s : u
"0"_s );
96 atlasElem.setAttribute( u
"filterFeatures"_s, mFilterFeatures ? u
"1"_s : u
"0"_s );
97 if ( mFilterFeatures )
99 atlasElem.setAttribute( u
"featureFilter"_s, mFilterExpression );
102 parentElement.appendChild( atlasElem );
109 mEnabled = atlasElem.attribute( u
"enabled"_s, u
"0"_s ).toInt();
112 const QString layerId = atlasElem.attribute( u
"coverageLayer"_s );
113 const QString layerName = atlasElem.attribute( u
"coverageLayerName"_s );
114 const QString layerSource = atlasElem.attribute( u
"coverageLayerSource"_s );
115 const QString layerProvider = atlasElem.attribute( u
"coverageLayerProvider"_s );
117 mCoverageLayer =
QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
118 mCoverageLayer.resolveWeakly( mLayout->project() );
119 mLayout->reportContext().setLayer( mCoverageLayer.get() );
121 mPageNameExpression = atlasElem.attribute( u
"pageNameExpression"_s, QString() );
125 mSortFeatures = atlasElem.attribute( u
"sortFeatures"_s, u
"0"_s ).toInt();
126 mSortExpression = atlasElem.attribute( u
"sortKey"_s );
127 mSortAscending = atlasElem.attribute( u
"sortAscending"_s, u
"1"_s ).toInt();
128 mFilterFeatures = atlasElem.attribute( u
"filterFeatures"_s, u
"0"_s ).toInt();
129 mFilterExpression = atlasElem.attribute( u
"featureFilter"_s );
131 mLimitCoverageLayerRenderToCurrentFeature = atlasElem.attribute( u
"limitCoverageLayerRenderToCurrentFeature"_s, u
"0"_s ).toInt();
133 mHideCoverage = atlasElem.attribute( u
"hideCoverage"_s, u
"0"_s ).toInt();
153void QgsLayoutAtlas::removeLayers(
const QStringList &layers )
155 if ( !mCoverageLayer )
160 for (
const QString &layerId : layers )
162 if ( layerId == mCoverageLayer.layerId )
165 mCoverageLayer.setLayer(
nullptr );
174 if ( layer == mCoverageLayer.get() )
179 mCoverageLayer.setLayer( layer );
185 if ( mPageNameExpression == expression )
188 mPageNameExpression = expression;
194 if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
197 return mFeatureIds.at( pageNumber ).second;
202 if ( mSortFeatures ==
enabled )
211 if ( mSortAscending == ascending )
214 mSortAscending = ascending;
220 if ( mSortExpression == expression )
223 mSortExpression = expression;
229 if ( mFilterFeatures == filtered )
232 mFilterFeatures = filtered;
239 const bool hasChanged = mFilterExpression != expression;
240 mFilterExpression = expression;
256class AtlasFeatureSorter
259 AtlasFeatureSorter( QgsLayoutAtlas::SorterKeys &keys,
bool ascending =
true )
261 , mAscending( ascending )
264 bool operator()(
const QPair< QgsFeatureId, QString > &id1,
const QPair< QgsFeatureId, QString > &id2 )
270 QgsLayoutAtlas::SorterKeys &mKeys;
278 mCurrentFeatureNo = -1;
279 if ( !mCoverageLayer )
287 updateFilenameExpression( error );
294 mFilterParserError.clear();
295 if ( mFilterFeatures && !mFilterExpression.isEmpty() )
308#ifdef HAVE_SERVER_PYTHON_PLUGINS
309 if ( mLayout->renderContext().featureFilterProvider() )
312 if ( mLayout->renderContext().featureFilterProvider()->isFilterThreadSafe() )
314 mLayout->renderContext().featureFilterProvider()->filterFeatures( mCoverageLayer.get()->id(), req );
318 mLayout->renderContext().featureFilterProvider()->filterFeatures( mCoverageLayer.get(), req );
326 std::unique_ptr<QgsExpression> nameExpression;
327 if ( !mPageNameExpression.isEmpty() )
329 nameExpression = std::make_unique< QgsExpression >( mPageNameExpression );
330 if ( nameExpression->hasParserError() )
332 nameExpression.reset(
nullptr );
336 nameExpression->prepare( &expressionContext );
344 mFeatureKeys.clear();
347 if ( mSortFeatures && !mSortExpression.isEmpty() )
349 sortExpression = std::make_unique< QgsExpression >( mSortExpression );
365 if ( nameExpression )
367 const QVariant result = nameExpression->evaluate( &expressionContext );
368 if ( nameExpression->hasEvalError() )
372 pageName = result.toString();
375 mFeatureIds.push_back( qMakePair( feat.
id(), pageName ) );
379 const QVariant result =
sortExpression->evaluate( &expressionContext );
384 mFeatureKeys.insert( feat.
id(), result );
389 if ( !mFeatureKeys.isEmpty() )
392 std::sort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
396 return mFeatureIds.size();
401 if ( !mCoverageLayer )
426 return mFeatureIds.size();
431 const QFileInfo fi( baseFilePath );
432 const QDir dir = fi.dir();
433 QString base = dir.filePath( mCurrentFilename );
434 if ( !extension.startsWith(
'.' ) )
442 const int newFeatureNo = mCurrentFeatureNo + 1;
443 if ( newFeatureNo >= mFeatureIds.size() )
448 return prepareForFeature( newFeatureNo );
453 const int newFeatureNo = mCurrentFeatureNo - 1;
454 if ( newFeatureNo < 0 )
459 return prepareForFeature( newFeatureNo );
464 return prepareForFeature( 0 );
469 return prepareForFeature( mFeatureIds.size() - 1 );
474 return prepareForFeature( feature );
480 auto it = mFeatureIds.constBegin();
481 for (
int currentIdx = 0; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
483 if ( ( *it ).first == feature.
id() )
501 prepareForFeature( mCurrentFeatureNo );
507 if ( hide == mHideCoverage )
510 mHideCoverage = hide;
518 if ( limit == mLimitCoverageLayerRenderToCurrentFeature )
521 mLimitCoverageLayerRenderToCurrentFeature = limit;
528 const bool hasChanged = mFilenameExpressionString != pattern;
529 mFilenameExpressionString = pattern;
534 return updateFilenameExpression( errorString );
539 return mCurrentFilename;
551 if ( mCoverageLayer )
552 expressionContext.
appendScope( mCoverageLayer->createExpressionContextScope() );
554 if ( mLayout && mEnabled )
556 if ( mCurrentFeature.isValid() )
560 else if ( mCoverageLayer )
562 QgsFeature feature { mCoverageLayer->fields() };
567 return expressionContext;
570bool QgsLayoutAtlas::updateFilenameExpression( QString &error )
572 if ( !mCoverageLayer )
578 bool evalResult {
true };
580 if ( !mFilenameExpressionString.isEmpty() )
598 evalResult = evalFeatureFilename( expressionContext );
603 error = mFilenameExpressionError;
612 mFilenameExpressionError.clear();
613 if ( !mFilenameExpressionString.isEmpty() )
625 mCurrentFilename = filenameRes.toString();
630bool QgsLayoutAtlas::prepareForFeature(
const int featureI )
632 if ( !mCoverageLayer )
637 if ( mFeatureIds.isEmpty() )
643 if ( featureI >= mFeatureIds.size() )
648 mCurrentFeatureNo = featureI;
651 if ( !mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[featureI].
first ) ).nextFeature( mCurrentFeature ) )
654 mLayout->reportContext().blockSignals(
true );
655 mLayout->reportContext().setLayer( mCoverageLayer.get() );
656 mLayout->reportContext().blockSignals(
false );
657 mLayout->reportContext().setFeature( mCurrentFeature );
663 if ( !evalFeatureFilename( expressionContext ) )
670 emit
messagePushed( tr(
"Atlas feature %1 of %2" ).arg( featureI + 1 ).arg( mFeatureIds.size() ) );
672 return mCurrentFeature.isValid();
@ LimitCoverageLayerRenderToCurrentFeature
Limit coverage layer rendering to the current atlas feature.
@ HideCoverageLayer
Hide coverage layer in outputs.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the scope.
static QgsExpressionContextScope * layoutScope(const QgsLayout *layout)
Creates a new scope which contains variables and functions relating to a QgsLayout layout.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * atlasScope(const QgsLayoutAtlas *atlas)
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
void setValid(bool validity)
Sets the validity of the feature.
QString nameForPage(int page) const
Returns the calculated name for a specified atlas page number.
QString sortExpression() const
Returns the expression (or field name) to use for sorting features.
bool writeXml(QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext &context) const override
Stores the objects's state in a DOM element.
QString filenameExpression() const
Returns the filename expression used for generating output filenames for each atlas page.
void toggled(bool enabled)
Emitted when atlas is enabled or disabled.
void setCoverageLayer(QgsVectorLayer *layer)
Sets the coverage layer to use for the atlas features.
bool beginRender() override
Called when rendering begins, before iteration commences.
bool setFilterExpression(const QString &expression, QString &errorString)
Sets the expression used for filtering features in the coverage layer.
void setSortAscending(bool ascending)
Sets whether features should be sorted in an ascending order.
bool seekTo(int feature)
Seeks to the specified feature number.
void featureChanged(const QgsFeature &feature)
Emitted when the current atlas feature changes.
void setEnabled(bool enabled)
Sets whether the atlas is enabled.
friend class AtlasFeatureSorter
void setPageNameExpression(const QString &expression)
Sets the expression (or field name) used for calculating the page name.
bool first()
Seeks to the first feature, returning false if no feature was found.
QString filterExpression() const
Returns the expression used for filtering features in the coverage layer.
QgsLayout * layout() override
Returns the layout associated with the iterator.
bool enabled() const
Returns whether the atlas generation is enabled.
bool setFilenameExpression(const QString &expression, QString &errorString)
Sets the filename expression used for generating output filenames for each atlas page.
QgsExpressionContext createExpressionContext() const override
This method needs to be reimplemented in all classes which implement this interface and return an exp...
void setLimitCoverageLayerRenderToCurrentFeature(bool limit)
Sets whether the rendering of the coverage layer should be limited to the current feature.
void setSortFeatures(bool enabled)
Sets whether features should be sorted in the atlas.
QString filePath(const QString &baseFilePath, const QString &extension) override
Returns the file path for the current feature, based on a specified base file path and extension.
QString currentFilename() const
Returns the current feature filename.
bool readXml(const QDomElement &element, const QDomDocument &document, const QgsReadWriteContext &context) override
Sets the objects's state from a DOM element.
bool last()
Seeks to the last feature, returning false if no feature was found.
QgsLayoutAtlas(QgsLayout *layout)
Constructor for new QgsLayoutAtlas.
int count() const override
Returns the number of features to iterate over.
void numberFeaturesChanged(int numFeatures)
Emitted when the number of features for the atlas changes.
void messagePushed(const QString &message)
Emitted when the atlas has an updated status bar message.
void setSortExpression(const QString &expression)
Sets the expression (or field name) to use for sorting features.
void coverageLayerChanged(QgsVectorLayer *layer)
Emitted when the coverage layer for the atlas changes.
void setFilterFeatures(bool filtered)
Sets whether features should be filtered in the coverage layer.
void renderBegun()
Emitted when atlas rendering has begun.
void renderEnded()
Emitted when atlas rendering has ended.
void changed()
Emitted when one of the atlas parameters changes.
bool previous()
Iterates to the previous feature, returning false if no previous feature exists.
void refreshCurrentFeature()
Refreshes the current atlas feature, by refetching its attributes from the vector layer provider.
bool endRender() override
Ends the render, performing any required cleanup tasks.
int updateFeatures()
Requeries the current atlas coverage layer and applies filtering and sorting.
QString stringType() const override
Returns the object type as a string.
void setHideCoverage(bool hide)
Sets whether the coverage layer should be hidden in map items in the layouts.
Base class for layouts, which can contain items such as maps, labels, scalebars, etc.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
A container for the context for various read/write operations on objects.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
_LayerRef< QgsVectorLayer > QgsVectorLayerRef