QGIS API Documentation  2.99.0-Master (ae4d26a)
qgsatlascomposition.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsatlascomposition.cpp
3  -----------------------
4  begin : October 2012
5  copyright : (C) 2005 by Hugo Mercier
6  email : hugo dot mercier at oslandia 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 "qgsatlascomposition.h"
22 #include "qgsfeatureiterator.h"
23 #include "qgsvectorlayer.h"
24 #include "qgscomposermap.h"
25 #include "qgscomposition.h"
26 #include "qgsvectordataprovider.h"
27 #include "qgsexpression.h"
28 #include "qgsgeometry.h"
29 #include "qgsproject.h"
30 #include "qgsmessagelog.h"
31 #include "qgsexpressioncontext.h"
32 #include "qgscrscache.h"
33 #include "qgsmapsettings.h"
34 
36  : mComposition( composition )
37  , mEnabled( false )
38  , mHideCoverage( false )
39  , mFilenamePattern( QStringLiteral( "'output_'||@atlas_featurenumber" ) )
40  , mSingleFile( false )
41  , mFileFormat( QStringLiteral( "png" ) )
42  , mSortFeatures( false )
43  , mSortAscending( true )
44  , mCurrentFeatureNo( 0 )
45  , mFilterFeatures( false )
46 {
47 
48  //listen out for layer removal
49  connect( mComposition->project(), static_cast < void ( QgsProject::* )( const QStringList & ) >( &QgsProject::layersWillBeRemoved ), this, &QgsAtlasComposition::removeLayers );
50 }
51 
53 {
54  if ( enabled == mEnabled )
55  {
56  return;
57  }
58 
59  mEnabled = enabled;
60  mComposition->setAtlasMode( QgsComposition::AtlasOff );
61  emit toggled( enabled );
62  emit parameterChanged();
63 }
64 
65 void QgsAtlasComposition::removeLayers( const QStringList &layers )
66 {
67  if ( !mCoverageLayer )
68  {
69  return;
70  }
71 
72  for ( const QString &layerId : layers )
73  {
74  if ( layerId == mCoverageLayer.layerId )
75  {
76  //current coverage layer removed
77  mCoverageLayer.setLayer( nullptr );
78  setEnabled( false );
79  return;
80  }
81  }
82 }
83 
85 {
86  if ( layer == mCoverageLayer.get() )
87  {
88  return;
89  }
90 
91  mCoverageLayer.setLayer( layer );
92  emit coverageLayerChanged( layer );
93 }
94 
95 QString QgsAtlasComposition::nameForPage( int pageNumber ) const
96 {
97  if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
98  return QString();
99 
100  return mFeatureIds.at( pageNumber ).second;
101 }
102 
104 class FieldSorter
105 {
106  public:
107  FieldSorter( QgsAtlasComposition::SorterKeys &keys, bool ascending = true )
108  : mKeys( keys )
109  , mAscending( ascending )
110  {}
111 
112  bool operator()( const QPair< QgsFeatureId, QString > &id1, const QPair< QgsFeatureId, QString > &id2 )
113  {
114  return mAscending ? qgsVariantLessThan( mKeys.value( id1.first ), mKeys.value( id2.first ) )
115  : qgsVariantGreaterThan( mKeys.value( id1.first ), mKeys.value( id2.first ) );
116  }
117 
118  private:
120  bool mAscending;
121 };
122 
124 
126 {
127  //needs to be called when layer, filter, sort changes
128 
129  if ( !mCoverageLayer )
130  {
131  return 0;
132  }
133 
134  QgsExpressionContext expressionContext = createExpressionContext();
135 
136  updateFilenameExpression();
137 
138  // select all features with all attributes
139  QgsFeatureRequest req;
140 
141  std::unique_ptr<QgsExpression> filterExpression;
142  if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
143  {
144  filterExpression.reset( new QgsExpression( mFeatureFilter ) );
145  if ( filterExpression->hasParserError() )
146  {
147  mFilterParserError = filterExpression->parserErrorString();
148  return 0;
149  }
150 
151  //filter good to go
152  req.setFilterExpression( mFeatureFilter );
153  }
154  mFilterParserError = QString();
155 
156  QgsFeatureIterator fit = mCoverageLayer->getFeatures( req );
157 
158  std::unique_ptr<QgsExpression> nameExpression;
159  if ( !mPageNameExpression.isEmpty() )
160  {
161  nameExpression.reset( new QgsExpression( mPageNameExpression ) );
162  if ( nameExpression->hasParserError() )
163  {
164  nameExpression.reset( nullptr );
165  }
166  else
167  {
168  nameExpression->prepare( &expressionContext );
169  }
170  }
171 
172  // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
173  // We thus store the feature ids for future extraction
174  QgsFeature feat;
175  mFeatureIds.clear();
176  mFeatureKeys.clear();
177  int sortIdx = mCoverageLayer->fields().lookupField( mSortKeyAttributeName );
178 
179  while ( fit.nextFeature( feat ) )
180  {
181  expressionContext.setFeature( feat );
182 
183  QString pageName;
184  if ( nameExpression )
185  {
186  QVariant result = nameExpression->evaluate( &expressionContext );
187  if ( nameExpression->hasEvalError() )
188  {
189  QgsMessageLog::logMessage( tr( "Atlas name eval error: %1" ).arg( nameExpression->evalErrorString() ), tr( "Composer" ) );
190  }
191  pageName = result.toString();
192  }
193 
194  mFeatureIds.push_back( qMakePair( feat.id(), pageName ) );
195 
196  if ( mSortFeatures && sortIdx != -1 )
197  {
198  mFeatureKeys.insert( feat.id(), feat.attributes().at( sortIdx ) );
199  }
200  }
201 
202  // sort features, if asked for
203  if ( !mFeatureKeys.isEmpty() )
204  {
205  FieldSorter sorter( mFeatureKeys, mSortAscending );
206  std::sort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
207  }
208 
209  emit numberFeaturesChanged( mFeatureIds.size() );
210 
211  //jump to first feature if currently using an atlas preview
212  //need to do this in case filtering/layer change has altered matching features
213  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
214  {
215  firstFeature();
216  }
217 
218  return mFeatureIds.size();
219 }
220 
222 {
223  return nameForPage( currentFeatureNumber() );
224 }
225 
227 {
228  if ( !mCoverageLayer )
229  {
230  return false;
231  }
232 
233  emit renderBegun();
234 
235  bool featuresUpdated = updateFeatures();
236  if ( !featuresUpdated )
237  {
238  //no matching features found
239  return false;
240  }
241 
242  return true;
243 }
244 
246 {
247  if ( !mCoverageLayer )
248  {
249  return;
250  }
251 
252  emit featureChanged( nullptr );
253 
254  updateAtlasMaps();
255 
256  emit renderEnded();
257 }
258 
259 void QgsAtlasComposition::updateAtlasMaps()
260 {
261  //update atlas-enabled composer maps
262  QList<QgsComposerMap *> maps;
263  mComposition->composerItems( maps );
264  for ( QList<QgsComposerMap *>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
265  {
266  QgsComposerMap *currentMap = ( *mit );
267  if ( !currentMap->atlasDriven() )
268  {
269  continue;
270  }
271 
272  currentMap->invalidateCache();
273  }
274 }
275 
277 {
278  return mFeatureIds.size();
279 }
280 
282 {
283  int newFeatureNo = mCurrentFeatureNo + 1;
284  if ( newFeatureNo >= mFeatureIds.size() )
285  {
286  newFeatureNo = mFeatureIds.size() - 1;
287  }
288 
289  prepareForFeature( newFeatureNo );
290 }
291 
293 {
294  int newFeatureNo = mCurrentFeatureNo - 1;
295  if ( newFeatureNo < 0 )
296  {
297  newFeatureNo = 0;
298  }
299 
300  prepareForFeature( newFeatureNo );
301 }
302 
304 {
305  prepareForFeature( 0 );
306 }
307 
309 {
310  prepareForFeature( mFeatureIds.size() - 1 );
311 }
312 
314 {
315  int featureI = -1;
316  QVector< QPair<QgsFeatureId, QString> >::const_iterator it = mFeatureIds.constBegin();
317  int currentIdx = 0;
318  for ( ; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
319  {
320  if ( ( *it ).first == feat->id() )
321  {
322  featureI = currentIdx;
323  break;
324  }
325  }
326 
327  if ( featureI < 0 )
328  {
329  //feature not found
330  return false;
331  }
332 
333  return prepareForFeature( featureI );
334 }
335 
337 {
338  prepareForFeature( mCurrentFeatureNo, false );
339 }
340 
341 bool QgsAtlasComposition::prepareForFeature( const int featureI, const bool updateMaps )
342 {
343  if ( !mCoverageLayer )
344  {
345  return false;
346  }
347 
348  if ( mFeatureIds.isEmpty() )
349  {
350  emit statusMsgChanged( tr( "No matching atlas features" ) );
351  return false;
352  }
353 
354  if ( featureI >= mFeatureIds.size() )
355  {
356  return false;
357  }
358 
359  mCurrentFeatureNo = featureI;
360 
361  // retrieve the next feature, based on its id
362  mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ].first ) ).nextFeature( mCurrentFeature );
363 
364  QgsExpressionContext expressionContext = createExpressionContext();
365 
366  // generate filename for current feature
367  if ( !evalFeatureFilename( expressionContext ) )
368  {
369  //error evaluating filename
370  return false;
371  }
372 
373  mGeometryCache.clear();
374  emit featureChanged( &mCurrentFeature );
375  emit statusMsgChanged( QString( tr( "Atlas feature %1 of %2" ) ).arg( featureI + 1 ).arg( mFeatureIds.size() ) );
376 
377  if ( !mCurrentFeature.isValid() )
378  {
379  //bad feature
380  return true;
381  }
382 
383  if ( !updateMaps )
384  {
385  //nothing more to do
386  return true;
387  }
388 
389  //update composer maps
390 
391  //build a list of atlas-enabled composer maps
392  QList<QgsComposerMap *> maps;
393  QList<QgsComposerMap *> atlasMaps;
394  mComposition->composerItems( maps );
395  if ( maps.isEmpty() )
396  {
397  return true;
398  }
399  for ( QList<QgsComposerMap *>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
400  {
401  QgsComposerMap *currentMap = ( *mit );
402  if ( !currentMap->atlasDriven() )
403  {
404  continue;
405  }
406  atlasMaps << currentMap;
407  }
408 
409  if ( !atlasMaps.isEmpty() )
410  {
411  //clear the transformed bounds of the previous feature
412  mTransformedFeatureBounds = QgsRectangle();
413 
414  // compute extent of current feature in the map CRS. This should be set on a per-atlas map basis,
415  // but given that it's not currently possible to have maps with different CRSes we can just
416  // calculate it once based on the first atlas maps' CRS.
417  computeExtent( atlasMaps[0] );
418  }
419 
420  for ( QList<QgsComposerMap *>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
421  {
422  if ( ( *mit )->atlasDriven() )
423  {
424  // map is atlas driven, so update it's bounds (causes a redraw)
425  prepareMap( *mit );
426  }
427  else
428  {
429  // map is not atlas driven, so manually force a redraw (to reflect possibly atlas
430  // dependent symbology)
431  ( *mit )->invalidateCache();
432  }
433  }
434 
435  return true;
436 }
437 
438 void QgsAtlasComposition::computeExtent( QgsComposerMap *map )
439 {
440  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
441  // We have to transform the geometry to the destination CRS and ask for the bounding box
442  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
443  QgsGeometry g = currentGeometry( map->crs() );
444  // Rotating the geometry, so the bounding box is correct wrt map rotation
445  if ( map->mapRotation() != 0.0 )
446  {
447  QgsPointXY prevCenter = g.boundingBox().center();
448  g.rotate( map->mapRotation(), g.boundingBox().center() );
449  // Rotation center will be still the bounding box center of an unrotated geometry.
450  // Which means, if the center of bbox moves after rotation, the viewport will
451  // also be offset, and part of the geometry will fall out of bounds.
452  // Here we compensate for that roughly: by extending the rotated bounds
453  // so that its center is the same as the original.
454  QgsRectangle bounds = g.boundingBox();
455  double dx = std::max( std::abs( prevCenter.x() - bounds.xMinimum() ),
456  std::abs( prevCenter.x() - bounds.xMaximum() ) );
457  double dy = std::max( std::abs( prevCenter.y() - bounds.yMinimum() ),
458  std::abs( prevCenter.y() - bounds.yMaximum() ) );
459  QgsPointXY center = g.boundingBox().center();
460  mTransformedFeatureBounds = QgsRectangle( center.x() - dx, center.y() - dy,
461  center.x() + dx, center.y() + dy );
462  }
463  else
464  {
465  mTransformedFeatureBounds = g.boundingBox();
466  }
467 }
468 
470 {
471  if ( !map->atlasDriven() || mCoverageLayer->wkbType() == QgsWkbTypes::NoGeometry )
472  {
473  return;
474  }
475 
476  if ( mTransformedFeatureBounds.isEmpty() )
477  {
478  //transformed extent of current feature hasn't been calculated yet. This can happen if
479  //a map has been set to be atlas controlled after prepare feature was called
480  computeExtent( map );
481  }
482 
483  double xa1 = mTransformedFeatureBounds.xMinimum();
484  double xa2 = mTransformedFeatureBounds.xMaximum();
485  double ya1 = mTransformedFeatureBounds.yMinimum();
486  double ya2 = mTransformedFeatureBounds.yMaximum();
487  QgsRectangle newExtent = mTransformedFeatureBounds;
488  QgsRectangle mOrigExtent( map->extent() );
489 
490  //sanity check - only allow fixed scale mode for point layers
491  bool isPointLayer = false;
492  switch ( mCoverageLayer->wkbType() )
493  {
494  case QgsWkbTypes::Point:
498  isPointLayer = true;
499  break;
500  default:
501  isPointLayer = false;
502  break;
503  }
504 
505  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || map->atlasScalingMode() == QgsComposerMap::Predefined || isPointLayer )
506  {
507  QgsScaleCalculator calc;
508  calc.setMapUnits( map->crs().mapUnits() );
509  calc.setDpi( 25.4 );
510  double originalScale = calc.calculate( mOrigExtent, map->rect().width() );
511  double geomCenterX = ( xa1 + xa2 ) / 2.0;
512  double geomCenterY = ( ya1 + ya2 ) / 2.0;
513 
514  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || isPointLayer )
515  {
516  // only translate, keep the original scale (i.e. width x height)
517  double xMin = geomCenterX - mOrigExtent.width() / 2.0;
518  double yMin = geomCenterY - mOrigExtent.height() / 2.0;
519  newExtent = QgsRectangle( xMin,
520  yMin,
521  xMin + mOrigExtent.width(),
522  yMin + mOrigExtent.height() );
523 
524  //scale newExtent to match original scale of map
525  //this is required for geographic coordinate systems, where the scale varies by extent
526  double newScale = calc.calculate( newExtent, map->rect().width() );
527  newExtent.scale( originalScale / newScale );
528  }
529  else if ( map->atlasScalingMode() == QgsComposerMap::Predefined )
530  {
531  // choose one of the predefined scales
532  double newWidth = mOrigExtent.width();
533  double newHeight = mOrigExtent.height();
534  const QVector<qreal> &scales = mPredefinedScales;
535  for ( int i = 0; i < scales.size(); i++ )
536  {
537  double ratio = scales[i] / originalScale;
538  newWidth = mOrigExtent.width() * ratio;
539  newHeight = mOrigExtent.height() * ratio;
540 
541  // compute new extent, centered on feature
542  double xMin = geomCenterX - newWidth / 2.0;
543  double yMin = geomCenterY - newHeight / 2.0;
544  newExtent = QgsRectangle( xMin,
545  yMin,
546  xMin + newWidth,
547  yMin + newHeight );
548 
549  //scale newExtent to match desired map scale
550  //this is required for geographic coordinate systems, where the scale varies by extent
551  double newScale = calc.calculate( newExtent, map->rect().width() );
552  newExtent.scale( scales[i] / newScale );
553 
554  if ( ( newExtent.width() >= mTransformedFeatureBounds.width() ) && ( newExtent.height() >= mTransformedFeatureBounds.height() ) )
555  {
556  // this is the smallest extent that embeds the feature, stop here
557  break;
558  }
559  }
560  }
561  }
562  else if ( map->atlasScalingMode() == QgsComposerMap::Auto )
563  {
564  // auto scale
565 
566  double geomRatio = mTransformedFeatureBounds.width() / mTransformedFeatureBounds.height();
567  double mapRatio = mOrigExtent.width() / mOrigExtent.height();
568 
569  // geometry height is too big
570  if ( geomRatio < mapRatio )
571  {
572  // extent the bbox's width
573  double adjWidth = ( mapRatio * mTransformedFeatureBounds.height() - mTransformedFeatureBounds.width() ) / 2.0;
574  xa1 -= adjWidth;
575  xa2 += adjWidth;
576  }
577  // geometry width is too big
578  else if ( geomRatio > mapRatio )
579  {
580  // extent the bbox's height
581  double adjHeight = ( mTransformedFeatureBounds.width() / mapRatio - mTransformedFeatureBounds.height() ) / 2.0;
582  ya1 -= adjHeight;
583  ya2 += adjHeight;
584  }
585  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
586 
587  if ( map->atlasMargin() > 0.0 )
588  {
589  newExtent.scale( 1 + map->atlasMargin() );
590  }
591  }
592 
593  // set the new extent (and render)
594  map->setNewAtlasFeatureExtent( newExtent );
595 }
596 
598 {
599  return mCurrentFilename;
600 }
601 
602 void QgsAtlasComposition::writeXml( QDomElement &elem, QDomDocument &doc ) const
603 {
604  QDomElement atlasElem = doc.createElement( QStringLiteral( "Atlas" ) );
605  atlasElem.setAttribute( QStringLiteral( "enabled" ), mEnabled ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
606  if ( !mEnabled )
607  {
608  return;
609  }
610 
611  if ( mCoverageLayer )
612  {
613  atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), mCoverageLayer.layerId );
614  atlasElem.setAttribute( QStringLiteral( "coverageLayerName" ), mCoverageLayer.name );
615  atlasElem.setAttribute( QStringLiteral( "coverageLayerSource" ), mCoverageLayer.source );
616  atlasElem.setAttribute( QStringLiteral( "coverageLayerProvider" ), mCoverageLayer.provider );
617  }
618  else
619  {
620  atlasElem.setAttribute( QStringLiteral( "coverageLayer" ), QLatin1String( "" ) );
621  }
622 
623  atlasElem.setAttribute( QStringLiteral( "hideCoverage" ), mHideCoverage ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
624  atlasElem.setAttribute( QStringLiteral( "singleFile" ), mSingleFile ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
625  atlasElem.setAttribute( QStringLiteral( "filenamePattern" ), mFilenamePattern );
626  atlasElem.setAttribute( QStringLiteral( "pageNameExpression" ), mPageNameExpression );
627 
628  atlasElem.setAttribute( QStringLiteral( "sortFeatures" ), mSortFeatures ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
629  if ( mSortFeatures )
630  {
631  atlasElem.setAttribute( QStringLiteral( "sortKey" ), mSortKeyAttributeName );
632  atlasElem.setAttribute( QStringLiteral( "sortAscending" ), mSortAscending ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
633  }
634  atlasElem.setAttribute( QStringLiteral( "filterFeatures" ), mFilterFeatures ? QStringLiteral( "true" ) : QStringLiteral( "false" ) );
635  if ( mFilterFeatures )
636  {
637  atlasElem.setAttribute( QStringLiteral( "featureFilter" ), mFeatureFilter );
638  }
639 
640  atlasElem.setAttribute( QStringLiteral( "fileFormat" ), mFileFormat );
641 
642  elem.appendChild( atlasElem );
643 }
644 
645 void QgsAtlasComposition::readXml( const QDomElement &atlasElem, const QDomDocument & )
646 {
647  mEnabled = atlasElem.attribute( QStringLiteral( "enabled" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
648  emit toggled( mEnabled );
649  if ( !mEnabled )
650  {
651  emit parameterChanged();
652  return;
653  }
654 
655  // look for stored layer name
656  QString layerId = atlasElem.attribute( QStringLiteral( "coverageLayer" ) );
657  QString layerName = atlasElem.attribute( QStringLiteral( "coverageLayerName" ) );
658  QString layerSource = atlasElem.attribute( QStringLiteral( "coverageLayerSource" ) );
659  QString layerProvider = atlasElem.attribute( QStringLiteral( "coverageLayerProvider" ) );
660 
661  mCoverageLayer = QgsVectorLayerRef( layerId, layerName, layerSource, layerProvider );
662  mCoverageLayer.resolveWeakly( mComposition->project() );
663 
664  mPageNameExpression = atlasElem.attribute( QStringLiteral( "pageNameExpression" ), QString() );
665  mSingleFile = atlasElem.attribute( QStringLiteral( "singleFile" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
666  mFilenamePattern = atlasElem.attribute( QStringLiteral( "filenamePattern" ), QLatin1String( "" ) );
667 
668  mSortFeatures = atlasElem.attribute( QStringLiteral( "sortFeatures" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
669  if ( mSortFeatures )
670  {
671  mSortKeyAttributeName = atlasElem.attribute( QStringLiteral( "sortKey" ), QLatin1String( "" ) );
672  // since 2.3, the field name is saved instead of the field index
673  // following code keeps compatibility with version 2.2 projects
674  // to be removed in QGIS 3.0
675  bool isIndex;
676  int idx = mSortKeyAttributeName.toInt( &isIndex );
677  if ( isIndex && mCoverageLayer )
678  {
679  QgsFields fields = mCoverageLayer->fields();
680  if ( idx >= 0 && idx < fields.count() )
681  {
682  mSortKeyAttributeName = fields.at( idx ).name();
683  }
684  }
685  mSortAscending = atlasElem.attribute( QStringLiteral( "sortAscending" ), QStringLiteral( "true" ) ) == QLatin1String( "true" );
686  }
687  mFilterFeatures = atlasElem.attribute( QStringLiteral( "filterFeatures" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
688  if ( mFilterFeatures )
689  {
690  mFeatureFilter = atlasElem.attribute( QStringLiteral( "featureFilter" ), QLatin1String( "" ) );
691  }
692 
693  mHideCoverage = atlasElem.attribute( QStringLiteral( "hideCoverage" ), QStringLiteral( "false" ) ) == QLatin1String( "true" );
694 
695  mFileFormat = atlasElem.attribute( QStringLiteral( "fileFormat" ), QStringLiteral( "png" ) );
696 
697  emit parameterChanged();
698 }
699 
701 {
702  mHideCoverage = hide;
703 
704  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
705  {
706  //an atlas preview is enabled, so reflect changes in coverage layer visibility immediately
707  updateAtlasMaps();
708  mComposition->update();
709  }
710 
711 }
712 
713 bool QgsAtlasComposition::setFilenamePattern( const QString &pattern )
714 {
715  mFilenamePattern = pattern;
716  return updateFilenameExpression();
717 }
718 
719 QgsExpressionContext QgsAtlasComposition::createExpressionContext()
720 {
721  QgsExpressionContext expressionContext;
722  expressionContext << QgsExpressionContextUtils::globalScope();
723  if ( mComposition )
724  expressionContext << QgsExpressionContextUtils::projectScope( mComposition->project() )
726 
727  expressionContext.appendScope( QgsExpressionContextUtils::atlasScope( this ) );
728  if ( mCoverageLayer )
729  expressionContext.lastScope()->setFields( mCoverageLayer->fields() );
730  if ( mComposition && mComposition->atlasMode() != QgsComposition::AtlasOff )
731  expressionContext.lastScope()->setFeature( mCurrentFeature );
732 
733  return expressionContext;
734 }
735 
736 bool QgsAtlasComposition::updateFilenameExpression()
737 {
738  if ( !mCoverageLayer )
739  {
740  return false;
741  }
742 
743  QgsExpressionContext expressionContext = createExpressionContext();
744 
745  if ( !mFilenamePattern.isEmpty() )
746  {
747  mFilenameExpr.reset( new QgsExpression( mFilenamePattern ) );
748  // expression used to evaluate each filename
749  // test for evaluation errors
750  if ( mFilenameExpr->hasParserError() )
751  {
752  mFilenameParserError = mFilenameExpr->parserErrorString();
753  return false;
754  }
755 
756  // prepare the filename expression
757  mFilenameExpr->prepare( &expressionContext );
758  }
759 
760  //if atlas preview is currently enabled, regenerate filename for current feature
761  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
762  {
763  evalFeatureFilename( expressionContext );
764  }
765  return true;
766 }
767 
768 bool QgsAtlasComposition::evalFeatureFilename( const QgsExpressionContext &context )
769 {
770  //generate filename for current atlas feature
771  if ( !mFilenamePattern.isEmpty() && mFilenameExpr )
772  {
773  QVariant filenameRes = mFilenameExpr->evaluate( &context );
774  if ( mFilenameExpr->hasEvalError() )
775  {
776  QgsMessageLog::logMessage( tr( "Atlas filename evaluation error: %1" ).arg( mFilenameExpr->evalErrorString() ), tr( "Composer" ) );
777  return false;
778  }
779 
780  mCurrentFilename = filenameRes.toString();
781  }
782  return true;
783 }
784 
785 void QgsAtlasComposition::setPredefinedScales( const QVector<qreal> &scales )
786 {
787  mPredefinedScales = scales;
788  // make sure the list is sorted
789  std::sort( mPredefinedScales.begin(), mPredefinedScales.end() );
790 }
791 
793 {
794  if ( !mCoverageLayer || !mCurrentFeature.isValid() || !mCurrentFeature.hasGeometry() )
795  {
796  return QgsGeometry();
797  }
798 
799  if ( !crs.isValid() )
800  {
801  // no projection, return the native geometry
802  return mCurrentFeature.geometry();
803  }
804 
805  QMap<long, QgsGeometry>::const_iterator it = mGeometryCache.constFind( crs.srsid() );
806  if ( it != mGeometryCache.constEnd() )
807  {
808  // we have it in cache, return it
809  return it.value();
810  }
811 
812  if ( mCoverageLayer->crs() == crs )
813  {
814  return mCurrentFeature.geometry();
815  }
816 
817  QgsGeometry transformed = mCurrentFeature.geometry();
818  transformed.transform( QgsCoordinateTransformCache::instance()->transform( mCoverageLayer->crs().authid(), crs.authid() ) );
819  mGeometryCache[crs.srsid()] = transformed;
820  return transformed;
821 }
bool prepareForFeature(const int i, const bool updateMaps=true)
Prepare the atlas map for the given feature.
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
QgsFeatureId id
Definition: qgsfeature.h:71
QMap< QgsFeatureId, QVariant > SorterKeys
Wrapper for iterator of features from vector data provider or vector layer.
bool atlasDriven() const
Returns whether the map extent is set to follow the current atlas feature.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
The extent is adjusted so that each feature is fully visible.
void renderEnded()
Is emitted when atlas rendering has ended.
double atlasMargin(const QgsComposerObject::PropertyValueType valueType=QgsComposerObject::EvaluatedValue)
Returns the margin size (percentage) used when the map is in atlas mode.
QString name
Definition: qgsfield.h:56
static QgsExpressionContextScope * atlasScope(const QgsAtlasComposition *atlas)
Creates a new scope which contains variables and functions relating to a QgsAtlasComposition.
void setNewAtlasFeatureExtent(const QgsRectangle &extent)
Sets new Extent for the current atlas preview and changes width, height (and implicitly also scale)...
QgsAtlasComposition(QgsComposition *composition)
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void writeXml(QDomElement &elem, QDomDocument &doc) const
QgsRectangle extent() const
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
OperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
QString currentPageName() const
Returns the name of the page for the current atlas feature.
QString nameForPage(int pageNumber) const
Returns the calculated name for a specified atlas page number.
void setDpi(double dpi)
Set the dpi to be used in scale calculations.
QString currentFilename() const
Returns the current filename. Must be called after prepareForFeature()
Container of fields for a vector layer.
Definition: qgsfields.h:42
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:94
int numFeatures() const
Returns the number of features in the coverage layer.
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the scope.
The current scale of the map is used for each feature of the atlas.
void toggled(bool)
Emitted when atlas is enabled or disabled.
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
void setHideCoverage(bool hide)
Sets whether the coverage layer should be hidden in map items in the composition. ...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
QgsFields fields
Definition: qgsfeature.h:73
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition: qgis.cpp:210
static QgsCoordinateTransformCache * instance()
Definition: qgscrscache.cpp:22
int count() const
Return number of items.
Definition: qgsfields.cpp:115
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition: qgis.cpp:142
void endRender()
Ends the rendering. Restores original extent.
QgsField at(int i) const
Get field at particular index (must be in range 0..N-1)
Definition: qgsfields.cpp:145
bool setFilenamePattern(const QString &pattern)
Sets the filename expression used for generating output filenames for each atlas page.
void setCoverageLayer(QgsVectorLayer *layer)
Sets the coverage layer to use for the atlas features.
QgsExpressionContextScope * lastScope()
Returns the last scope added to the context.
QgsFeatureRequest & setFilterExpression(const QString &expression)
Set the filter expression.
QString layerId
Original layer ID.
bool beginRender()
Begins the rendering.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
double calculate(const QgsRectangle &mapExtent, int canvasWidth)
Calculate the scale denominator.
void prepareMap(QgsComposerMap *map)
Recalculates the bounds of an atlas driven map.
void refreshFeature()
Refreshes the current atlas feature, by refetching its attributes from the vector layer provider...
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:131
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setMapUnits(QgsUnitTypes::DistanceUnit mapUnits)
Set the map units.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Reads and writes project states.
Definition: qgsproject.h:81
void setEnabled(bool enabled)
Sets whether the atlas is enabled.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the scope.
void setLayer(TYPE *l)
Sets the reference to point to a specified layer.
int updateFeatures()
Requeries the current atlas coverage layer and applies filtering and sorting.
Graphics scene for map printing.
Object representing map window.
int currentFeatureNumber() const
Returns the current feature number, where a value of 0 corresponds to the first feature.
double x
Definition: qgspointxy.h:47
void featureChanged(QgsFeature *feature)
Is emitted when the current atlas feature changes.
void coverageLayerChanged(QgsVectorLayer *layer)
Is emitted when the coverage layer for an atlas changes.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:119
Calculates scale for a given combination of canvas size, map extent, and monitor dpi.
bool setAtlasMode(const QgsComposition::AtlasMode mode)
Sets the current atlas mode of the composition.
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:104
static void logMessage(const QString &message, const QString &tag=QString(), MessageLevel level=QgsMessageLog::WARNING)
add a message to the instance (and create it if necessary)
QgsGeometry currentGeometry(const QgsCoordinateReferenceSystem &projectedTo=QgsCoordinateReferenceSystem()) const
Returns the current atlas geometry in the given projection system (default to the coverage layer&#39;s CR...
void renderBegun()
Is emitted when atlas rendering has begun.
QgsCoordinateReferenceSystem crs() const
Returns coordinate reference system used for rendering the map.
OperationResult transform(const QgsCoordinateTransform &ct)
Transforms this geometry as described by CoordinateTransform ct.
void layersWillBeRemoved(const QStringList &layerIds)
Emitted when one or more layers are about to be removed from the registry.
AtlasScalingMode atlasScalingMode() const
Returns the current atlas scaling mode.
A scale is chosen from the predefined scales.
QgsProject * project() const
The project associated with the composition.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
_LayerRef< QgsVectorLayer > QgsVectorLayerRef
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
void parameterChanged()
Emitted when one of the parameters changes.
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:109
void readXml(const QDomElement &elem, const QDomDocument &doc)
Reads general atlas settings from xml.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:114
void numberFeaturesChanged(int numFeatures)
Is emitted when the number of features for the atlas changes.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:159
bool nextFeature(QgsFeature &f)
long srsid() const
Returns the internal CRS ID, if available.
double mapRotation(QgsComposerObject::PropertyValueType valueType=QgsComposerObject::EvaluatedValue) const
Returns the rotation used for drawing the map within the composer item, in degrees clockwise...
Represents a vector layer which manages a vector based data sets.
TYPE * get() const
Returns a pointer to the layer, or nullptr if the reference has not yet been matched to a layer...
void statusMsgChanged(const QString &message)
Is emitted when the atlas has an updated status bar message for the composer window.
QgsAbstractGeometry * geometry() const
Returns the underlying geometry store.
QString authid() const
Returns the authority identifier for the CRS.
QgsAttributes attributes
Definition: qgsfeature.h:72
bool enabled() const
Returns whether the atlas generation is enabled.
void setPredefinedScales(const QVector< qreal > &scales)
Sets the list of predefined scales for the atlas.
void invalidateCache()
Forces a deferred update of the cached map image on next paint.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:138
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
static QgsExpressionContextScope * compositionScope(const QgsComposition *composition)
Creates a new scope which contains variables and functions relating to a QgsComposition.