Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgslayoutatlas.cpp
3 : : ----------------
4 : : begin : December 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 : : #include <algorithm>
18 : : #include <stdexcept>
19 : : #include <QtAlgorithms>
20 : :
21 : : #include "qgslayoutatlas.h"
22 : : #include "qgslayout.h"
23 : : #include "qgsmessagelog.h"
24 : : #include "qgsfeaturerequest.h"
25 : : #include "qgsfeatureiterator.h"
26 : : #include "qgsvectorlayer.h"
27 : : #include "qgsexpressioncontextutils.h"
28 : :
29 : 0 : QgsLayoutAtlas::QgsLayoutAtlas( QgsLayout *layout )
30 : 0 : : QObject( layout )
31 : 0 : , mLayout( layout )
32 : 0 : , mFilenameExpressionString( QStringLiteral( "'output_'||@atlas_featurenumber" ) )
33 : 0 : {
34 : :
35 : : //listen out for layer removal
36 : 0 : connect( mLayout->project(), static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsLayoutAtlas::removeLayers );
37 : 0 : }
38 : :
39 : 0 : QString QgsLayoutAtlas::stringType() const
40 : : {
41 : 0 : return QStringLiteral( "atlas" );
42 : : }
43 : :
44 : 0 : QgsLayout *QgsLayoutAtlas::layout()
45 : : {
46 : 0 : return mLayout;
47 : : }
48 : :
49 : 0 : const QgsLayout *QgsLayoutAtlas::layout() const
50 : : {
51 : 0 : return mLayout.data();
52 : : }
53 : :
54 : 0 : bool QgsLayoutAtlas::writeXml( QDomElement &parentElement, QDomDocument &document, const QgsReadWriteContext & ) const
55 : : {
56 : 0 : QDomElement atlasElem = document.createElement( QStringLiteral( "Atlas" ) );
57 : 0 : atlasElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
58 : :
59 : 0 : if ( mCoverageLayer )
60 : : {
61 : 0 : atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), mCoverageLayer.layerId );
62 : 0 : atlasElem.setAttribute( QStringLiteral( "coverageLayerName" ), mCoverageLayer.name );
63 : 0 : atlasElem.setAttribute( QStringLiteral( "coverageLayerSource" ), mCoverageLayer.source );
64 : 0 : atlasElem.setAttribute( QStringLiteral( "coverageLayerProvider" ), mCoverageLayer.provider );
65 : 0 : }
66 : : else
67 : : {
68 : 0 : atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), QString() );
69 : : }
70 : :
71 : 0 : atlasElem.setAttribute( QStringLiteral( "hideCoverage" ), mHideCoverage ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
72 : 0 : atlasElem.setAttribute( QStringLiteral( "filenamePattern" ), mFilenameExpressionString );
73 : 0 : atlasElem.setAttribute( QStringLiteral( "pageNameExpression" ), mPageNameExpression );
74 : :
75 : 0 : atlasElem.setAttribute( QStringLiteral( "sortFeatures" ), mSortFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
76 : 0 : if ( mSortFeatures )
77 : : {
78 : 0 : atlasElem.setAttribute( QStringLiteral( "sortKey" ), mSortExpression );
79 : 0 : atlasElem.setAttribute( QStringLiteral( "sortAscending" ), mSortAscending ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
80 : 0 : }
81 : 0 : atlasElem.setAttribute( QStringLiteral( "filterFeatures" ), mFilterFeatures ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
82 : 0 : if ( mFilterFeatures )
83 : : {
84 : 0 : atlasElem.setAttribute( QStringLiteral( "featureFilter" ), mFilterExpression );
85 : 0 : }
86 : :
87 : 0 : parentElement.appendChild( atlasElem );
88 : :
89 : : return true;
90 : 0 : }
91 : :
92 : 0 : bool QgsLayoutAtlas::readXml( const QDomElement &atlasElem, const QDomDocument &, const QgsReadWriteContext & )
93 : : {
94 : 0 : mEnabled = atlasElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "0" ) ).toInt();
95 : :
96 : : // look for stored layer name
97 : 0 : QString layerId = atlasElem.attribute( QStringLiteral( "coverageLayer" ) );
98 : 0 : QString layerName = atlasElem.attribute( QStringLiteral( "coverageLayerName" ) );
99 : 0 : QString layerSource = atlasElem.attribute( QStringLiteral( "coverageLayerSource" ) );
100 : 0 : QString layerProvider = atlasElem.attribute( QStringLiteral( "coverageLayerProvider" ) );
101 : :
102 : 0 : mCoverageLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
103 : 0 : mCoverageLayer.resolveWeakly( mLayout->project() );
104 : 0 : mLayout->reportContext().setLayer( mCoverageLayer.get() );
105 : :
106 : 0 : mPageNameExpression = atlasElem.attribute( QStringLiteral( "pageNameExpression" ), QString() );
107 : 0 : QString error;
108 : 0 : setFilenameExpression( atlasElem.attribute( QStringLiteral( "filenamePattern" ), QString() ), error );
109 : :
110 : 0 : mSortFeatures = atlasElem.attribute( QStringLiteral( "sortFeatures" ), QStringLiteral( "0" ) ).toInt();
111 : 0 : mSortExpression = atlasElem.attribute( QStringLiteral( "sortKey" ) );
112 : 0 : mSortAscending = atlasElem.attribute( QStringLiteral( "sortAscending" ), QStringLiteral( "1" ) ).toInt();
113 : 0 : mFilterFeatures = atlasElem.attribute( QStringLiteral( "filterFeatures" ), QStringLiteral( "0" ) ).toInt();
114 : 0 : mFilterExpression = atlasElem.attribute( QStringLiteral( "featureFilter" ) );
115 : :
116 : 0 : mHideCoverage = atlasElem.attribute( QStringLiteral( "hideCoverage" ), QStringLiteral( "0" ) ).toInt();
117 : :
118 : 0 : emit toggled( mEnabled );
119 : 0 : emit changed();
120 : : return true;
121 : 0 : }
122 : :
123 : 0 : void QgsLayoutAtlas::setEnabled( bool enabled )
124 : : {
125 : 0 : if ( enabled == mEnabled )
126 : : {
127 : 0 : return;
128 : : }
129 : :
130 : 0 : mEnabled = enabled;
131 : 0 : emit toggled( enabled );
132 : 0 : emit changed();
133 : 0 : }
134 : :
135 : 0 : void QgsLayoutAtlas::removeLayers( const QStringList &layers )
136 : : {
137 : 0 : if ( !mCoverageLayer )
138 : : {
139 : 0 : return;
140 : : }
141 : :
142 : 0 : for ( const QString &layerId : layers )
143 : : {
144 : 0 : if ( layerId == mCoverageLayer.layerId )
145 : : {
146 : : //current coverage layer removed
147 : 0 : mCoverageLayer.setLayer( nullptr );
148 : 0 : setEnabled( false );
149 : 0 : break;
150 : : }
151 : : }
152 : 0 : }
153 : :
154 : 0 : void QgsLayoutAtlas::setCoverageLayer( QgsVectorLayer *layer )
155 : : {
156 : 0 : if ( layer == mCoverageLayer.get() )
157 : : {
158 : 0 : return;
159 : : }
160 : :
161 : 0 : mCoverageLayer.setLayer( layer );
162 : 0 : emit coverageLayerChanged( layer );
163 : 0 : }
164 : :
165 : 0 : void QgsLayoutAtlas::setPageNameExpression( const QString &expression )
166 : : {
167 : 0 : if ( mPageNameExpression == expression )
168 : 0 : return;
169 : :
170 : 0 : mPageNameExpression = expression;
171 : 0 : emit changed();
172 : 0 : }
173 : :
174 : 0 : QString QgsLayoutAtlas::nameForPage( int pageNumber ) const
175 : : {
176 : 0 : if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
177 : 0 : return QString();
178 : :
179 : 0 : return mFeatureIds.at( pageNumber ).second;
180 : 0 : }
181 : :
182 : 0 : void QgsLayoutAtlas::setSortFeatures( bool enabled )
183 : : {
184 : 0 : if ( mSortFeatures == enabled )
185 : 0 : return;
186 : :
187 : 0 : mSortFeatures = enabled;
188 : 0 : emit changed();
189 : 0 : }
190 : :
191 : 0 : void QgsLayoutAtlas::setSortAscending( bool ascending )
192 : : {
193 : 0 : if ( mSortAscending == ascending )
194 : 0 : return;
195 : :
196 : 0 : mSortAscending = ascending;
197 : 0 : emit changed();
198 : 0 : }
199 : :
200 : 0 : void QgsLayoutAtlas::setSortExpression( const QString &expression )
201 : : {
202 : 0 : if ( mSortExpression == expression )
203 : 0 : return;
204 : :
205 : 0 : mSortExpression = expression;
206 : 0 : emit changed();
207 : 0 : }
208 : :
209 : 0 : void QgsLayoutAtlas::setFilterFeatures( bool filtered )
210 : : {
211 : 0 : if ( mFilterFeatures == filtered )
212 : 0 : return;
213 : :
214 : 0 : mFilterFeatures = filtered;
215 : 0 : emit changed();
216 : 0 : }
217 : :
218 : 0 : bool QgsLayoutAtlas::setFilterExpression( const QString &expression, QString &errorString )
219 : : {
220 : 0 : errorString.clear();
221 : 0 : const bool hasChanged = mFilterExpression != expression;
222 : 0 : mFilterExpression = expression;
223 : :
224 : 0 : QgsExpression filterExpression( mFilterExpression );
225 : 0 : if ( hasChanged )
226 : 0 : emit changed();
227 : 0 : if ( filterExpression.hasParserError() )
228 : : {
229 : 0 : errorString = filterExpression.parserErrorString();
230 : 0 : return false;
231 : : }
232 : :
233 : 0 : return true;
234 : 0 : }
235 : :
236 : :
237 : : /// @cond PRIVATE
238 : : class AtlasFeatureSorter
239 : : {
240 : : public:
241 : 0 : AtlasFeatureSorter( QgsLayoutAtlas::SorterKeys &keys, bool ascending = true )
242 : 0 : : mKeys( keys )
243 : 0 : , mAscending( ascending )
244 : 0 : {}
245 : :
246 : 0 : bool operator()( const QPair< QgsFeatureId, QString > &id1, const QPair< QgsFeatureId, QString > &id2 )
247 : : {
248 : 0 : return mAscending ? qgsVariantLessThan( mKeys.value( id1.first ), mKeys.value( id2.first ) )
249 : 0 : : qgsVariantGreaterThan( mKeys.value( id1.first ), mKeys.value( id2.first ) );
250 : 0 : }
251 : :
252 : : private:
253 : : QgsLayoutAtlas::SorterKeys &mKeys;
254 : : bool mAscending;
255 : : };
256 : :
257 : : /// @endcond
258 : :
259 : 0 : int QgsLayoutAtlas::updateFeatures()
260 : : {
261 : 0 : mCurrentFeatureNo = -1;
262 : 0 : if ( !mCoverageLayer )
263 : : {
264 : 0 : return 0;
265 : : }
266 : :
267 : 0 : QgsExpressionContext expressionContext = createExpressionContext();
268 : :
269 : 0 : QString error;
270 : 0 : updateFilenameExpression( error );
271 : :
272 : : // select all features with all attributes
273 : 0 : QgsFeatureRequest req;
274 : :
275 : 0 : req.setExpressionContext( expressionContext );
276 : :
277 : 0 : mFilterParserError.clear();
278 : 0 : if ( mFilterFeatures && !mFilterExpression.isEmpty() )
279 : : {
280 : 0 : QgsExpression filterExpression( mFilterExpression );
281 : 0 : if ( filterExpression.hasParserError() )
282 : : {
283 : 0 : mFilterParserError = filterExpression.parserErrorString();
284 : 0 : return 0;
285 : : }
286 : :
287 : : //filter good to go
288 : 0 : req.setFilterExpression( mFilterExpression );
289 : 0 : }
290 : :
291 : : #ifdef HAVE_SERVER_PYTHON_PLUGINS
292 : : if ( mLayout->renderContext().featureFilterProvider() )
293 : : {
294 : : mLayout->renderContext().featureFilterProvider()->filterFeatures( mCoverageLayer.get(), req );
295 : : }
296 : : #endif
297 : :
298 : 0 : QgsFeatureIterator fit = mCoverageLayer->getFeatures( req );
299 : :
300 : 0 : std::unique_ptr<QgsExpression> nameExpression;
301 : 0 : if ( !mPageNameExpression.isEmpty() )
302 : : {
303 : 0 : nameExpression = std::make_unique< QgsExpression >( mPageNameExpression );
304 : 0 : if ( nameExpression->hasParserError() )
305 : : {
306 : 0 : nameExpression.reset( nullptr );
307 : 0 : }
308 : : else
309 : : {
310 : 0 : nameExpression->prepare( &expressionContext );
311 : : }
312 : 0 : }
313 : :
314 : : // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
315 : : // We thus store the feature ids for future extraction
316 : 0 : QgsFeature feat;
317 : 0 : mFeatureIds.clear();
318 : 0 : mFeatureKeys.clear();
319 : :
320 : 0 : std::unique_ptr<QgsExpression> sortExpression;
321 : 0 : if ( mSortFeatures && !mSortExpression.isEmpty() )
322 : : {
323 : 0 : sortExpression = std::make_unique< QgsExpression >( mSortExpression );
324 : 0 : if ( sortExpression->hasParserError() )
325 : : {
326 : 0 : sortExpression.reset( nullptr );
327 : 0 : }
328 : : else
329 : : {
330 : 0 : sortExpression->prepare( &expressionContext );
331 : : }
332 : 0 : }
333 : :
334 : 0 : while ( fit.nextFeature( feat ) )
335 : : {
336 : 0 : expressionContext.setFeature( feat );
337 : :
338 : 0 : QString pageName;
339 : 0 : if ( nameExpression )
340 : : {
341 : 0 : QVariant result = nameExpression->evaluate( &expressionContext );
342 : 0 : if ( nameExpression->hasEvalError() )
343 : : {
344 : 0 : QgsMessageLog::logMessage( tr( "Atlas name eval error: %1" ).arg( nameExpression->evalErrorString() ), tr( "Layout" ) );
345 : 0 : }
346 : 0 : pageName = result.toString();
347 : 0 : }
348 : :
349 : 0 : mFeatureIds.push_back( qMakePair( feat.id(), pageName ) );
350 : :
351 : 0 : if ( sortExpression )
352 : : {
353 : 0 : QVariant result = sortExpression->evaluate( &expressionContext );
354 : 0 : if ( sortExpression->hasEvalError() )
355 : : {
356 : 0 : QgsMessageLog::logMessage( tr( "Atlas sort eval error: %1" ).arg( sortExpression->evalErrorString() ), tr( "Layout" ) );
357 : 0 : }
358 : 0 : mFeatureKeys.insert( feat.id(), result );
359 : 0 : }
360 : 0 : }
361 : :
362 : : // sort features, if asked for
363 : 0 : if ( !mFeatureKeys.isEmpty() )
364 : : {
365 : 0 : AtlasFeatureSorter sorter( mFeatureKeys, mSortAscending );
366 : 0 : std::sort( mFeatureIds.begin(), mFeatureIds.end(), sorter ); // clazy:exclude=detaching-member
367 : 0 : }
368 : :
369 : 0 : emit numberFeaturesChanged( mFeatureIds.size() );
370 : 0 : return mFeatureIds.size();
371 : 0 : }
372 : :
373 : 0 : bool QgsLayoutAtlas::beginRender()
374 : 0 : {
375 : 0 : if ( !mCoverageLayer )
376 : : {
377 : 0 : return false;
378 : : }
379 : :
380 : 0 : emit renderBegun();
381 : :
382 : 0 : if ( !updateFeatures() )
383 : : {
384 : : //no matching features found
385 : 0 : return false;
386 : : }
387 : :
388 : 0 : return true;
389 : 0 : }
390 : :
391 : 0 : bool QgsLayoutAtlas::endRender()
392 : : {
393 : 0 : emit featureChanged( QgsFeature() );
394 : 0 : emit renderEnded();
395 : 0 : return true;
396 : 0 : }
397 : :
398 : 0 : int QgsLayoutAtlas::count() const
399 : : {
400 : 0 : return mFeatureIds.size();
401 : : }
402 : :
403 : 0 : QString QgsLayoutAtlas::filePath( const QString &baseFilePath, const QString &extension )
404 : : {
405 : 0 : QFileInfo fi( baseFilePath );
406 : 0 : QDir dir = fi.dir(); // ignore everything except the directory
407 : 0 : QString base = dir.filePath( mCurrentFilename );
408 : 0 : if ( !extension.startsWith( '.' ) )
409 : 0 : base += '.';
410 : 0 : base += extension;
411 : 0 : return base;
412 : 0 : }
413 : :
414 : 0 : bool QgsLayoutAtlas::next()
415 : : {
416 : 0 : int newFeatureNo = mCurrentFeatureNo + 1;
417 : 0 : if ( newFeatureNo >= mFeatureIds.size() )
418 : : {
419 : 0 : return false;
420 : : }
421 : :
422 : 0 : return prepareForFeature( newFeatureNo );
423 : 0 : }
424 : :
425 : 0 : bool QgsLayoutAtlas::previous()
426 : : {
427 : 0 : int newFeatureNo = mCurrentFeatureNo - 1;
428 : 0 : if ( newFeatureNo < 0 )
429 : : {
430 : 0 : return false;
431 : : }
432 : :
433 : 0 : return prepareForFeature( newFeatureNo );
434 : 0 : }
435 : :
436 : 0 : bool QgsLayoutAtlas::first()
437 : : {
438 : 0 : return prepareForFeature( 0 );
439 : : }
440 : :
441 : 0 : bool QgsLayoutAtlas::last()
442 : : {
443 : 0 : return prepareForFeature( mFeatureIds.size() - 1 );
444 : : }
445 : :
446 : 0 : bool QgsLayoutAtlas::seekTo( int feature )
447 : : {
448 : 0 : return prepareForFeature( feature );
449 : : }
450 : :
451 : 0 : bool QgsLayoutAtlas::seekTo( const QgsFeature &feature )
452 : : {
453 : 0 : int i = -1;
454 : 0 : auto it = mFeatureIds.constBegin();
455 : 0 : for ( int currentIdx = 0; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
456 : : {
457 : 0 : if ( ( *it ).first == feature.id() )
458 : : {
459 : 0 : i = currentIdx;
460 : 0 : break;
461 : : }
462 : 0 : }
463 : :
464 : 0 : if ( i < 0 )
465 : : {
466 : : //feature not found
467 : 0 : return false;
468 : : }
469 : :
470 : 0 : return seekTo( i );
471 : 0 : }
472 : :
473 : 0 : void QgsLayoutAtlas::refreshCurrentFeature()
474 : : {
475 : 0 : prepareForFeature( mCurrentFeatureNo );
476 : 0 : }
477 : :
478 : 0 : void QgsLayoutAtlas::setHideCoverage( bool hide )
479 : : {
480 : 0 : mLayout->renderContext().setFlag( QgsLayoutRenderContext::FlagHideCoverageLayer, hide );
481 : 0 : if ( hide == mHideCoverage )
482 : 0 : return;
483 : :
484 : 0 : mHideCoverage = hide;
485 : 0 : mLayout->refresh();
486 : 0 : emit changed();
487 : 0 : }
488 : :
489 : 0 : bool QgsLayoutAtlas::setFilenameExpression( const QString &pattern, QString &errorString )
490 : : {
491 : 0 : const bool hasChanged = mFilenameExpressionString != pattern;
492 : 0 : mFilenameExpressionString = pattern;
493 : :
494 : 0 : if ( hasChanged )
495 : 0 : emit changed();
496 : :
497 : 0 : return updateFilenameExpression( errorString );
498 : : }
499 : :
500 : 0 : QString QgsLayoutAtlas::currentFilename() const
501 : : {
502 : 0 : return mCurrentFilename;
503 : : }
504 : :
505 : 0 : QgsExpressionContext QgsLayoutAtlas::createExpressionContext() const
506 : : {
507 : 0 : QgsExpressionContext expressionContext;
508 : 0 : expressionContext << QgsExpressionContextUtils::globalScope();
509 : 0 : if ( mLayout )
510 : 0 : expressionContext << QgsExpressionContextUtils::projectScope( mLayout->project() )
511 : 0 : << QgsExpressionContextUtils::layoutScope( mLayout );
512 : :
513 : 0 : expressionContext.appendScope( QgsExpressionContextUtils::atlasScope( this ) );
514 : :
515 : 0 : if ( mCoverageLayer )
516 : 0 : expressionContext.appendScope( mCoverageLayer->createExpressionContextScope() );
517 : :
518 : 0 : if ( mLayout && mEnabled )
519 : : {
520 : 0 : if ( mCurrentFeature.isValid() )
521 : : {
522 : 0 : expressionContext.lastScope()->setFeature( mCurrentFeature );
523 : 0 : }
524 : 0 : else if ( mCoverageLayer ) // Create an empty feature for the expression validation
525 : : {
526 : 0 : QgsFeature feature{ mCoverageLayer->fields() };
527 : 0 : feature.setValid( true );
528 : 0 : expressionContext.lastScope()->setFeature( feature );
529 : 0 : }
530 : 0 : }
531 : 0 : return expressionContext;
532 : 0 : }
533 : :
534 : 0 : bool QgsLayoutAtlas::updateFilenameExpression( QString &error )
535 : : {
536 : 0 : if ( !mCoverageLayer )
537 : : {
538 : 0 : return false;
539 : : }
540 : :
541 : 0 : QgsExpressionContext expressionContext = createExpressionContext();
542 : 0 : bool evalResult { true };
543 : :
544 : 0 : if ( !mFilenameExpressionString.isEmpty() )
545 : : {
546 : 0 : QgsExpression filenameExpression( mFilenameExpressionString );
547 : : // expression used to evaluate each filename
548 : : // test for evaluation errors
549 : 0 : if ( filenameExpression.hasParserError() )
550 : : {
551 : 0 : error = filenameExpression.parserErrorString();
552 : 0 : return false;
553 : : }
554 : :
555 : : // prepare the filename expression
556 : 0 : evalResult = filenameExpression.prepare( &expressionContext );
557 : 0 : }
558 : :
559 : : // regenerate current filename
560 : 0 : if ( evalResult )
561 : : {
562 : 0 : evalResult = evalFeatureFilename( expressionContext );
563 : 0 : }
564 : :
565 : 0 : if ( ! evalResult )
566 : : {
567 : 0 : error = mFilenameExpressionError;
568 : 0 : }
569 : :
570 : 0 : return evalResult;
571 : 0 : }
572 : :
573 : 0 : bool QgsLayoutAtlas::evalFeatureFilename( const QgsExpressionContext &context )
574 : : {
575 : : //generate filename for current atlas feature
576 : 0 : mFilenameExpressionError.clear();
577 : 0 : if ( !mFilenameExpressionString.isEmpty() )
578 : : {
579 : 0 : QgsExpression filenameExpression( mFilenameExpressionString );
580 : 0 : filenameExpression.prepare( &context );
581 : 0 : QVariant filenameRes = filenameExpression.evaluate( &context );
582 : 0 : if ( filenameExpression.hasEvalError() )
583 : : {
584 : 0 : mFilenameExpressionError = filenameExpression.evalErrorString();
585 : 0 : QgsMessageLog::logMessage( tr( "Atlas filename evaluation error: %1" ).arg( filenameExpression.evalErrorString() ), tr( "Layout" ) );
586 : 0 : return false;
587 : : }
588 : :
589 : 0 : mCurrentFilename = filenameRes.toString();
590 : 0 : }
591 : 0 : return true;
592 : 0 : }
593 : :
594 : 0 : bool QgsLayoutAtlas::prepareForFeature( const int featureI )
595 : : {
596 : 0 : if ( !mCoverageLayer )
597 : : {
598 : 0 : return false;
599 : : }
600 : :
601 : 0 : if ( mFeatureIds.isEmpty() )
602 : : {
603 : 0 : emit messagePushed( tr( "No matching atlas features" ) );
604 : 0 : return false;
605 : : }
606 : :
607 : 0 : if ( featureI >= mFeatureIds.size() )
608 : : {
609 : 0 : return false;
610 : : }
611 : :
612 : 0 : mCurrentFeatureNo = featureI;
613 : :
614 : : // retrieve the next feature, based on its id
615 : 0 : if ( !mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ].first ) ).nextFeature( mCurrentFeature ) )
616 : 0 : return false;
617 : :
618 : 0 : mLayout->reportContext().blockSignals( true ); // setFeature emits changed, we don't want 2 signals
619 : 0 : mLayout->reportContext().setLayer( mCoverageLayer.get() );
620 : 0 : mLayout->reportContext().blockSignals( false );
621 : 0 : mLayout->reportContext().setFeature( mCurrentFeature );
622 : :
623 : : // must come after we've set the report context feature, or the expression context will have an outdated atlas feature
624 : 0 : QgsExpressionContext expressionContext = createExpressionContext();
625 : :
626 : : // generate filename for current feature
627 : 0 : if ( !evalFeatureFilename( expressionContext ) )
628 : : {
629 : : //error evaluating filename
630 : 0 : return false;
631 : : }
632 : :
633 : 0 : emit featureChanged( mCurrentFeature );
634 : 0 : emit messagePushed( tr( "Atlas feature %1 of %2" ).arg( featureI + 1 ).arg( mFeatureIds.size() ) );
635 : :
636 : 0 : return mCurrentFeature.isValid();
637 : 0 : }
638 : :
|