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