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