QGIS API Documentation  2.14.0-Essen
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 "qgsvectorlayer.h"
22 #include "qgscomposermap.h"
23 #include "qgscomposition.h"
24 #include "qgsvectordataprovider.h"
25 #include "qgsexpression.h"
26 #include "qgsgeometry.h"
27 #include "qgsmaplayerregistry.h"
28 #include "qgsproject.h"
29 #include "qgsmessagelog.h"
30 #include "qgsexpressioncontext.h"
31 #include "qgscrscache.h"
32 
34  : mComposition( composition )
35  , mEnabled( false )
36  , mHideCoverage( false )
37  , mFilenamePattern( "'output_'||@atlas_featurenumber" )
38  , mCoverageLayer( nullptr )
39  , mSingleFile( false )
40  , mSortFeatures( false )
41  , mSortAscending( true )
42  , mCurrentFeatureNo( 0 )
43  , mFilterFeatures( false )
44 {
45 
46  //listen out for layer removal
47  connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( removeLayers( QStringList ) ) );
48 }
49 
51 {
52 }
53 
55 {
56  if ( enabled == mEnabled )
57  {
58  return;
59  }
60 
61  mEnabled = enabled;
62  mComposition->setAtlasMode( QgsComposition::AtlasOff );
63  emit toggled( enabled );
64  emit parameterChanged();
65 }
66 
67 void QgsAtlasComposition::removeLayers( const QStringList& layers )
68 {
69  if ( !mCoverageLayer )
70  {
71  return;
72  }
73 
74  Q_FOREACH ( const QString& layerId, layers )
75  {
76  if ( layerId == mCoverageLayer->id() )
77  {
78  //current coverage layer removed
79  mCoverageLayer = nullptr;
80  setEnabled( false );
81  return;
82  }
83  }
84 }
85 
87 {
88  if ( layer == mCoverageLayer )
89  {
90  return;
91  }
92 
93  mCoverageLayer = layer;
94  emit coverageLayerChanged( layer );
95 }
96 
98 {
99  if ( pageNumber < 0 || pageNumber >= mFeatureIds.count() )
100  return QString();
101 
102  return mFeatureIds.at( pageNumber ).second;
103 }
104 
106 {
107  //deprecated method. Until removed just return the first atlas-enabled composer map
108 
109  //build a list of composer maps
111  mComposition->composerItems( maps );
112  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
113  {
114  QgsComposerMap* currentMap = ( *mit );
115  if ( currentMap->atlasDriven() )
116  {
117  return currentMap;
118  }
119  }
120 
121  return nullptr;
122 }
123 
125 {
126  //deprecated
127 
128  if ( !map )
129  {
130  return;
131  }
132 
133  map->setAtlasDriven( true );
134 }
135 
136 
138 {
139  if ( !mCoverageLayer )
140  {
141  return -1;
142  }
143  return mCoverageLayer->fieldNameIndex( mSortKeyAttributeName );
144 }
145 
147 {
148  if ( mCoverageLayer )
149  {
150  const QgsFields fields = mCoverageLayer->fields();
151  if ( idx >= 0 && idx < fields.count() )
152  {
153  mSortKeyAttributeName = fields[idx].name();
154  return;
155  }
156  }
157  mSortKeyAttributeName = "";
158 }
159 
160 //
161 // Private class only used for the sorting of features
163 {
164  public:
165  FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
166 
168  {
169  bool result = true;
170 
171  if ( mKeys[ id1.first ].type() == QVariant::Int )
172  {
173  result = mKeys[ id1.first ].toInt() < mKeys[ id2.first ].toInt();
174  }
175  else if ( mKeys[ id1.first ].type() == QVariant::Double )
176  {
177  result = mKeys[ id1.first ].toDouble() < mKeys[ id2.first ].toDouble();
178  }
179  else if ( mKeys[ id1.first ].type() == QVariant::String )
180  {
181  result = ( QString::localeAwareCompare( mKeys[ id1.first ].toString(), mKeys[ id2.first ].toString() ) < 0 );
182  }
183 
184  return mAscending ? result : !result;
185  }
186  private:
188  bool mAscending;
189 };
190 
192 {
193  //needs to be called when layer, filter, sort changes
194 
195  if ( !mCoverageLayer )
196  {
197  return 0;
198  }
199 
200  QgsExpressionContext expressionContext = createExpressionContext();
201 
202  updateFilenameExpression();
203 
204  // select all features with all attributes
205  QgsFeatureRequest req;
206 
207  QScopedPointer<QgsExpression> filterExpression;
208  if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
209  {
210  filterExpression.reset( new QgsExpression( mFeatureFilter ) );
211  if ( filterExpression->hasParserError() )
212  {
213  mFilterParserError = filterExpression->parserErrorString();
214  return 0;
215  }
216 
217  //filter good to go
218  req.setFilterExpression( mFeatureFilter );
219  }
220  mFilterParserError = QString();
221 
222  QgsFeatureIterator fit = mCoverageLayer->getFeatures( req );
223 
224  QScopedPointer<QgsExpression> nameExpression;
225  if ( !mPageNameExpression.isEmpty() )
226  {
227  nameExpression.reset( new QgsExpression( mPageNameExpression ) );
228  if ( nameExpression->hasParserError() )
229  {
230  nameExpression.reset( nullptr );
231  }
232  nameExpression->prepare( &expressionContext );
233  }
234 
235  // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
236  // We thus store the feature ids for future extraction
237  QgsFeature feat;
238  mFeatureIds.clear();
239  mFeatureKeys.clear();
240  int sortIdx = mCoverageLayer->fieldNameIndex( mSortKeyAttributeName );
241 
242  while ( fit.nextFeature( feat ) )
243  {
244  expressionContext.setFeature( feat );
245 
246  QString pageName;
247  if ( !nameExpression.isNull() )
248  {
249  QVariant result = nameExpression->evaluate( &expressionContext );
250  if ( nameExpression->hasEvalError() )
251  {
252  QgsMessageLog::logMessage( tr( "Atlas name eval error: %1" ).arg( nameExpression->evalErrorString() ), tr( "Composer" ) );
253  }
254  pageName = result.toString();
255  }
256 
257  mFeatureIds.push_back( qMakePair( feat.id(), pageName ) );
258 
259  if ( mSortFeatures && sortIdx != -1 )
260  {
261  mFeatureKeys.insert( feat.id(), feat.attributes().at( sortIdx ) );
262  }
263  }
264 
265  // sort features, if asked for
266  if ( !mFeatureKeys.isEmpty() )
267  {
268  FieldSorter sorter( mFeatureKeys, mSortAscending );
269  qSort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
270  }
271 
272  emit numberFeaturesChanged( mFeatureIds.size() );
273 
274  //jump to first feature if currently using an atlas preview
275  //need to do this in case filtering/layer change has altered matching features
276  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
277  {
278  firstFeature();
279  }
280 
281  return mFeatureIds.size();
282 }
283 
285 {
286  return nameForPage( currentFeatureNumber() );
287 }
288 
290 {
291  if ( !mCoverageLayer )
292  {
293  return false;
294  }
295 
296  emit renderBegun();
297 
298  bool featuresUpdated = updateFeatures();
299  if ( !featuresUpdated )
300  {
301  //no matching features found
302  return false;
303  }
304 
305  return true;
306 }
307 
309 {
310  if ( !mCoverageLayer )
311  {
312  return;
313  }
314 
315  emit featureChanged( nullptr );
316 
317  updateAtlasMaps();
318 
319  emit renderEnded();
320 }
321 
322 void QgsAtlasComposition::updateAtlasMaps()
323 {
324  //update atlas-enabled composer maps
326  mComposition->composerItems( maps );
327  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
328  {
329  QgsComposerMap* currentMap = ( *mit );
330  if ( !currentMap->atlasDriven() )
331  {
332  continue;
333  }
334 
335  currentMap->cache();
336  }
337 }
338 
340 {
341  return mFeatureIds.size();
342 }
343 
345 {
346  int newFeatureNo = mCurrentFeatureNo + 1;
347  if ( newFeatureNo >= mFeatureIds.size() )
348  {
349  newFeatureNo = mFeatureIds.size() - 1;
350  }
351 
352  prepareForFeature( newFeatureNo );
353 }
354 
356 {
357  int newFeatureNo = mCurrentFeatureNo - 1;
358  if ( newFeatureNo < 0 )
359  {
360  newFeatureNo = 0;
361  }
362 
363  prepareForFeature( newFeatureNo );
364 }
365 
367 {
368  prepareForFeature( 0 );
369 }
370 
372 {
373  prepareForFeature( mFeatureIds.size() - 1 );
374 }
375 
377 {
378  int featureI = -1;
379  QVector< QPair<QgsFeatureId, QString> >::const_iterator it = mFeatureIds.constBegin();
380  int currentIdx = 0;
381  for ( ; it != mFeatureIds.constEnd(); ++it, ++currentIdx )
382  {
383  if (( *it ).first == feat->id() )
384  {
385  featureI = currentIdx;
386  break;
387  }
388  }
389 
390  if ( featureI < 0 )
391  {
392  //feature not found
393  return false;
394  }
395 
396  return prepareForFeature( featureI );
397 }
398 
400 {
401  prepareForFeature( mCurrentFeatureNo, false );
402 }
403 
404 bool QgsAtlasComposition::prepareForFeature( const int featureI, const bool updateMaps )
405 {
406  if ( !mCoverageLayer )
407  {
408  return false;
409  }
410 
411  if ( mFeatureIds.isEmpty() )
412  {
413  emit statusMsgChanged( tr( "No matching atlas features" ) );
414  return false;
415  }
416 
417  if ( featureI >= mFeatureIds.size() )
418  {
419  return false;
420  }
421 
422  mCurrentFeatureNo = featureI;
423 
424  // retrieve the next feature, based on its id
425  mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ].first ) ).nextFeature( mCurrentFeature );
426 
427  QgsExpressionContext expressionContext = createExpressionContext();
428 
429  // generate filename for current feature
430  if ( !evalFeatureFilename( expressionContext ) )
431  {
432  //error evaluating filename
433  return false;
434  }
435 
436  mGeometryCache.clear();
437  emit featureChanged( &mCurrentFeature );
438  emit statusMsgChanged( QString( tr( "Atlas feature %1 of %2" ) ).arg( featureI + 1 ).arg( mFeatureIds.size() ) );
439 
440  if ( !mCurrentFeature.isValid() )
441  {
442  //bad feature
443  return true;
444  }
445 
446  if ( !updateMaps )
447  {
448  //nothing more to do
449  return true;
450  }
451 
452  //update composer maps
453 
454  //build a list of atlas-enabled composer maps
456  QList<QgsComposerMap*> atlasMaps;
457  mComposition->composerItems( maps );
458  if ( maps.isEmpty() )
459  {
460  return true;
461  }
462  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
463  {
464  QgsComposerMap* currentMap = ( *mit );
465  if ( !currentMap->atlasDriven() )
466  {
467  continue;
468  }
469  atlasMaps << currentMap;
470  }
471 
472  if ( !atlasMaps.isEmpty() )
473  {
474  //clear the transformed bounds of the previous feature
475  mTransformedFeatureBounds = QgsRectangle();
476 
477  // compute extent of current feature in the map CRS. This should be set on a per-atlas map basis,
478  // but given that it's not currently possible to have maps with different CRSes we can just
479  // calculate it once based on the first atlas maps' CRS.
480  computeExtent( atlasMaps[0] );
481  }
482 
483  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
484  {
485  if (( *mit )->atlasDriven() )
486  {
487  // map is atlas driven, so update it's bounds (causes a redraw)
488  prepareMap( *mit );
489  }
490  else
491  {
492  // map is not atlas driven, so manually force a redraw (to reflect possibly atlas
493  // dependent symbology)
494  ( *mit )->cache();
495  }
496  }
497 
498  return true;
499 }
500 
501 void QgsAtlasComposition::computeExtent( QgsComposerMap* map )
502 {
503  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
504  // We have to transform the grometry to the destination CRS and ask for the bounding box
505  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
506  mTransformedFeatureBounds = currentGeometry( map->composition()->mapSettings().destinationCrs() ).boundingBox();
507 }
508 
510 {
511  if ( !map->atlasDriven() || mCoverageLayer->wkbType() == QGis::WKBNoGeometry )
512  {
513  return;
514  }
515 
516  if ( mTransformedFeatureBounds.isEmpty() )
517  {
518  //transformed extent of current feature hasn't been calculated yet. This can happen if
519  //a map has been set to be atlas controlled after prepare feature was called
520  computeExtent( map );
521  }
522 
523  double xa1 = mTransformedFeatureBounds.xMinimum();
524  double xa2 = mTransformedFeatureBounds.xMaximum();
525  double ya1 = mTransformedFeatureBounds.yMinimum();
526  double ya2 = mTransformedFeatureBounds.yMaximum();
527  QgsRectangle newExtent = mTransformedFeatureBounds;
528  QgsRectangle mOrigExtent( map->extent() );
529 
530  //sanity check - only allow fixed scale mode for point layers
531  bool isPointLayer = false;
532  switch ( mCoverageLayer->wkbType() )
533  {
534  case QGis::WKBPoint:
535  case QGis::WKBPoint25D:
536  case QGis::WKBMultiPoint:
538  isPointLayer = true;
539  break;
540  default:
541  isPointLayer = false;
542  break;
543  }
544 
545  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || map->atlasScalingMode() == QgsComposerMap::Predefined || isPointLayer )
546  {
547  QgsScaleCalculator calc;
548  calc.setMapUnits( composition()->mapSettings().mapUnits() );
549  calc.setDpi( 25.4 );
550  double originalScale = calc.calculate( mOrigExtent, map->rect().width() );
551  double geomCenterX = ( xa1 + xa2 ) / 2.0;
552  double geomCenterY = ( ya1 + ya2 ) / 2.0;
553 
554  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || isPointLayer )
555  {
556  // only translate, keep the original scale (i.e. width x height)
557  double xMin = geomCenterX - mOrigExtent.width() / 2.0;
558  double yMin = geomCenterY - mOrigExtent.height() / 2.0;
559  newExtent = QgsRectangle( xMin,
560  yMin,
561  xMin + mOrigExtent.width(),
562  yMin + mOrigExtent.height() );
563 
564  //scale newExtent to match original scale of map
565  //this is required for geographic coordinate systems, where the scale varies by extent
566  double newScale = calc.calculate( newExtent, map->rect().width() );
567  newExtent.scale( originalScale / newScale );
568  }
569  else if ( map->atlasScalingMode() == QgsComposerMap::Predefined )
570  {
571  // choose one of the predefined scales
572  double newWidth = mOrigExtent.width();
573  double newHeight = mOrigExtent.height();
574  const QVector<qreal>& scales = mPredefinedScales;
575  for ( int i = 0; i < scales.size(); i++ )
576  {
577  double ratio = scales[i] / originalScale;
578  newWidth = mOrigExtent.width() * ratio;
579  newHeight = mOrigExtent.height() * ratio;
580 
581  // compute new extent, centered on feature
582  double xMin = geomCenterX - newWidth / 2.0;
583  double yMin = geomCenterY - newHeight / 2.0;
584  newExtent = QgsRectangle( xMin,
585  yMin,
586  xMin + newWidth,
587  yMin + newHeight );
588 
589  //scale newExtent to match desired map scale
590  //this is required for geographic coordinate systems, where the scale varies by extent
591  double newScale = calc.calculate( newExtent, map->rect().width() );
592  newExtent.scale( scales[i] / newScale );
593 
594  if (( newExtent.width() >= mTransformedFeatureBounds.width() ) && ( newExtent.height() >= mTransformedFeatureBounds.height() ) )
595  {
596  // this is the smallest extent that embeds the feature, stop here
597  break;
598  }
599  }
600  }
601  }
602  else if ( map->atlasScalingMode() == QgsComposerMap::Auto )
603  {
604  // auto scale
605 
606  double geomRatio = mTransformedFeatureBounds.width() / mTransformedFeatureBounds.height();
607  double mapRatio = mOrigExtent.width() / mOrigExtent.height();
608 
609  // geometry height is too big
610  if ( geomRatio < mapRatio )
611  {
612  // extent the bbox's width
613  double adjWidth = ( mapRatio * mTransformedFeatureBounds.height() - mTransformedFeatureBounds.width() ) / 2.0;
614  xa1 -= adjWidth;
615  xa2 += adjWidth;
616  }
617  // geometry width is too big
618  else if ( geomRatio > mapRatio )
619  {
620  // extent the bbox's height
621  double adjHeight = ( mTransformedFeatureBounds.width() / mapRatio - mTransformedFeatureBounds.height() ) / 2.0;
622  ya1 -= adjHeight;
623  ya2 += adjHeight;
624  }
625  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
626 
627  if ( map->atlasMargin() > 0.0 )
628  {
629  newExtent.scale( 1 + map->atlasMargin() );
630  }
631  }
632 
633  // set the new extent (and render)
634  map->setNewAtlasFeatureExtent( newExtent );
635 }
636 
638 {
639  return mCurrentFilename;
640 }
641 
643 {
644  QDomElement atlasElem = doc.createElement( "Atlas" );
645  atlasElem.setAttribute( "enabled", mEnabled ? "true" : "false" );
646  if ( !mEnabled )
647  {
648  return;
649  }
650 
651  if ( mCoverageLayer )
652  {
653  atlasElem.setAttribute( "coverageLayer", mCoverageLayer->id() );
654  }
655  else
656  {
657  atlasElem.setAttribute( "coverageLayer", "" );
658  }
659 
660  atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" );
661  atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" );
662  atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
663  atlasElem.setAttribute( "pageNameExpression", mPageNameExpression );
664 
665  atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
666  if ( mSortFeatures )
667  {
668  atlasElem.setAttribute( "sortKey", mSortKeyAttributeName );
669  atlasElem.setAttribute( "sortAscending", mSortAscending ? "true" : "false" );
670  }
671  atlasElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
672  if ( mFilterFeatures )
673  {
674  atlasElem.setAttribute( "featureFilter", mFeatureFilter );
675  }
676 
677  elem.appendChild( atlasElem );
678 }
679 
680 void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocument& )
681 {
682  mEnabled = atlasElem.attribute( "enabled", "false" ) == "true" ? true : false;
683  emit toggled( mEnabled );
684  if ( !mEnabled )
685  {
686  emit parameterChanged();
687  return;
688  }
689 
690  // look for stored layer name
691  mCoverageLayer = nullptr;
693  for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
694  {
695  if ( it.key() == atlasElem.attribute( "coverageLayer" ) )
696  {
697  mCoverageLayer = dynamic_cast<QgsVectorLayer*>( it.value() );
698  break;
699  }
700  }
701 
702  mPageNameExpression = atlasElem.attribute( "pageNameExpression", QString() );
703  mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
704  mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
705 
706  mSortFeatures = atlasElem.attribute( "sortFeatures", "false" ) == "true" ? true : false;
707  if ( mSortFeatures )
708  {
709  mSortKeyAttributeName = atlasElem.attribute( "sortKey", "" );
710  // since 2.3, the field name is saved instead of the field index
711  // following code keeps compatibility with version 2.2 projects
712  // to be removed in QGIS 3.0
713  bool isIndex;
714  int idx = mSortKeyAttributeName.toInt( &isIndex );
715  if ( isIndex && mCoverageLayer )
716  {
717  const QgsFields fields = mCoverageLayer->fields();
718  if ( idx >= 0 && idx < fields.count() )
719  {
720  mSortKeyAttributeName = fields[idx].name();
721  }
722  }
723  mSortAscending = atlasElem.attribute( "sortAscending", "true" ) == "true" ? true : false;
724  }
725  mFilterFeatures = atlasElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
726  if ( mFilterFeatures )
727  {
728  mFeatureFilter = atlasElem.attribute( "featureFilter", "" );
729  }
730 
731  mHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false;
732 
733  emit parameterChanged();
734 }
735 
737 {
738  Q_UNUSED( doc );
739  //look for stored composer map, to upgrade pre 2.1 projects
740  int composerMapNo = elem.attribute( "composerMap", "-1" ).toInt();
741  QgsComposerMap * composerMap = nullptr;
742  if ( composerMapNo != -1 )
743  {
745  mComposition->composerItems( maps );
746  for ( QList<QgsComposerMap*>::iterator it = maps.begin(); it != maps.end(); ++it )
747  {
748  if (( *it )->id() == composerMapNo )
749  {
750  composerMap = ( *it );
751  composerMap->setAtlasDriven( true );
752  break;
753  }
754  }
755  }
756 
757  //upgrade pre 2.1 projects
758  double margin = elem.attribute( "margin", "0.0" ).toDouble();
759  if ( composerMap && !qgsDoubleNear( margin, 0.0 ) )
760  {
761  composerMap->setAtlasMargin( margin );
762  }
763  bool fixedScale = elem.attribute( "fixedScale", "false" ) == "true" ? true : false;
764  if ( composerMap && fixedScale )
765  {
767  }
768 }
769 
771 {
772  mHideCoverage = hide;
773 
774  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
775  {
776  //an atlas preview is enabled, so reflect changes in coverage layer visibility immediately
777  updateAtlasMaps();
778  mComposition->update();
779  }
780 
781 }
782 
784 {
785  mFilenamePattern = pattern;
786  return updateFilenameExpression();
787 }
788 
789 QgsExpressionContext QgsAtlasComposition::createExpressionContext()
790 {
791  QgsExpressionContext expressionContext;
792  expressionContext << QgsExpressionContextUtils::globalScope()
794  if ( mComposition )
795  expressionContext << QgsExpressionContextUtils::compositionScope( mComposition );
796 
797  expressionContext.appendScope( QgsExpressionContextUtils::atlasScope( this ) );
798  if ( mCoverageLayer )
799  expressionContext.lastScope()->setFields( mCoverageLayer->fields() );
800  if ( mComposition && mComposition->atlasMode() != QgsComposition::AtlasOff )
801  expressionContext.lastScope()->setFeature( mCurrentFeature );
802 
803  return expressionContext;
804 }
805 
806 bool QgsAtlasComposition::updateFilenameExpression()
807 {
808  if ( !mCoverageLayer )
809  {
810  return false;
811  }
812 
813  QgsExpressionContext expressionContext = createExpressionContext();
814 
815  if ( !mFilenamePattern.isEmpty() )
816  {
817  mFilenameExpr.reset( new QgsExpression( mFilenamePattern ) );
818  // expression used to evaluate each filename
819  // test for evaluation errors
820  if ( mFilenameExpr->hasParserError() )
821  {
822  mFilenameParserError = mFilenameExpr->parserErrorString();
823  return false;
824  }
825 
826  // prepare the filename expression
827  mFilenameExpr->prepare( &expressionContext );
828  }
829 
830  //if atlas preview is currently enabled, regenerate filename for current feature
831  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
832  {
833  evalFeatureFilename( expressionContext );
834  }
835  return true;
836 }
837 
838 bool QgsAtlasComposition::evalFeatureFilename( const QgsExpressionContext &context )
839 {
840  //generate filename for current atlas feature
841  if ( !mFilenamePattern.isEmpty() && !mFilenameExpr.isNull() )
842  {
843  QVariant filenameRes = mFilenameExpr->evaluate( &context );
844  if ( mFilenameExpr->hasEvalError() )
845  {
846  QgsMessageLog::logMessage( tr( "Atlas filename evaluation error: %1" ).arg( mFilenameExpr->evalErrorString() ), tr( "Composer" ) );
847  return false;
848  }
849 
850  mCurrentFilename = filenameRes.toString();
851  }
852  return true;
853 }
854 
856 {
857  mPredefinedScales = scales;
858  // make sure the list is sorted
859  qSort( mPredefinedScales.begin(), mPredefinedScales.end() );
860 }
861 
864 {
865  //deprecated method. Until removed just return the property for the first atlas-enabled composer map
866  QgsComposerMap * map = composerMap();
867  if ( !map )
868  {
869  return false;
870  }
871 
872  return map->atlasFixedScale();
873 }
874 
876 {
877  //deprecated method. Until removed just set the property for the first atlas-enabled composer map
878  QgsComposerMap * map = composerMap();
879  if ( !map )
880  {
881  return;
882  }
883 
885 }
886 
888 {
889  //deprecated method. Until removed just return the property for the first atlas-enabled composer map
890  QgsComposerMap * map = composerMap();
891  if ( !map )
892  {
893  return 0;
894  }
895 
896  return map->atlasMargin();
897 }
898 
900 {
901  //deprecated method. Until removed just set the property for the first atlas-enabled composer map
902  QgsComposerMap * map = composerMap();
903  if ( !map )
904  {
905  return;
906  }
907 
908  map->setAtlasMargin( static_cast< double >( margin ) );
909 }
910 
912 {
913  if ( !mCoverageLayer || !mCurrentFeature.isValid() || !mCurrentFeature.constGeometry() )
914  {
915  return QgsGeometry();
916  }
917 
918  if ( !crs.isValid() )
919  {
920  // no projection, return the native geometry
921  return *mCurrentFeature.constGeometry();
922  }
923 
924  QMap<long, QgsGeometry>::const_iterator it = mGeometryCache.constFind( crs.srsid() );
925  if ( it != mGeometryCache.constEnd() )
926  {
927  // we have it in cache, return it
928  return it.value();
929  }
930 
931  if ( mCoverageLayer->crs() == crs )
932  {
933  return *mCurrentFeature.constGeometry();
934  }
935 
936  QgsGeometry transformed = *mCurrentFeature.constGeometry();
937  transformed.transform( *QgsCoordinateTransformCache::instance()->transform( mCoverageLayer->crs().authid(), crs.authid() ) );
938  mGeometryCache[crs.srsid()] = transformed;
939  return transformed;
940 }
941 
bool prepareForFeature(const int i, const bool updateMaps=true)
Prepare the atlas map for the given feature.
QgsFeatureId id() const
Get the feature ID for this feature.
Definition: qgsfeature.cpp:65
Class for parsing and evaluation of expressions (formerly called "search strings").
void setMapUnits(QGis::UnitType mapUnits)
Set the map units.
Wrapper for iterator of features from vector data provider or vector layer.
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.
Q_DECL_DEPRECATED int sortKeyAttributeIndex() const
int localeAwareCompare(const QString &other) const
int fieldNameIndex(const QString &fieldName) const
Utility method to get attribute index from name.
Definition: qgsfeature.cpp:286
QDomNode appendChild(const QDomNode &newChild)
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)
QString attribute(const QString &name, const QString &defValue) const
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
QgsFields fields() const
Returns the list of fields of this layer.
long srsid() const
Returns the SrsId, if available.
Q_DECL_DEPRECATED bool fixedScale() const
Returns whether the atlas map uses a fixed scale.
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
QString nameForPage(int pageNumber) const
Returns the calculated name for a specified atlas page number.
void cache()
Create cache image.
const_iterator constEnd() const
void scale(double scaleFactor, const QgsPoint *c=nullptr)
Scale the rectangle around its center point.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:407
void setDpi(double dpi)
Set the dpi to be used in scale calculations.
QgsComposition * composition()
bool enabled() const
Returns whether the atlas generation is enabled.
Container of fields for a vector layer.
Definition: qgsfield.h:187
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
const_iterator constFind(const Key &key) const
void setFields(const QgsFields &fields)
Convenience function for setting a fields for the scope.
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:187
QString currentPageName() const
Returns the name of the page for the current atlas feature.
static QgsCoordinateTransformCache * instance()
Definition: qgscrscache.cpp:22
double toDouble(bool *ok) const
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Definition: qgis.h:285
void endRender()
Ends the rendering.
void readXML(const QDomElement &elem, const QDomDocument &doc)
Reads general atlas settings from xml.
Q_DECL_DEPRECATED void setFixedScale(bool fixed)
Sets whether the atlas map should use a fixed scale.
void reset(T *other)
Q_DECL_DEPRECATED float margin() const
Returns the margin for the atlas map.
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.
Q_DECL_DEPRECATED void setSortKeyAttributeIndex(int idx)
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...
Q_DECL_DEPRECATED void setComposerMap(QgsComposerMap *map)
Sets the map used by the atlas.
double calculate(const QgsRectangle &mapExtent, int canvasWidth)
Calculate the scale denominator.
void prepareMap(QgsComposerMap *map)
Recalculates the bounds of an atlas driven map.
QgsAttributes attributes() const
Returns the feature&#39;s attributes.
Definition: qgsfeature.cpp:110
void setAttribute(const QString &name, const QString &value)
void refreshFeature()
Refreshes the current atlas feature, by refetching its attributes from the vector layer provider...
const QgsComposition * composition() const
Returns the composition the item is attached to.
const QgsCoordinateReferenceSystem & destinationCrs() const
returns CRS of destination coordinate reference system
int toInt(bool *ok, int base) const
bool isEmpty() const
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
const_iterator constEnd() const
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)...
int numFeatures() const
Returns the number of features in the coverage layer.
QString id() const
Get this layer&#39;s unique ID, this ID is used to access this layer from map layer registry.
int count() const
Return number of items.
Definition: qgsfield.cpp:365
Q_DECL_DEPRECATED bool atlasFixedScale() const
Returns true if the map uses a fixed scale when in atlas mode.
void setEnabled(bool enabled)
Sets whether the atlas is enabled.
iterator end()
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.
void writeXML(QDomElement &elem, QDomDocument &doc) const
iterator begin()
Graphics scene for map printing.
QString currentFilename() const
Returns the current filename.
Object representing map window.
void push_back(QChar ch)
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...
iterator end()
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.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:408
bool isNull() const
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
const T & at(int i) const
const_iterator constBegin() const
void renderBegun()
Is emitted when atlas rendering has begun.
static QgsMapLayerRegistry * instance()
Returns the instance pointer, creating the object on the first call.
qreal width() const
QgsRectangle extent() const
FieldSorter(QgsAtlasComposition::SorterKeys &keys, bool ascending=true)
Q_DECL_DEPRECATED QgsComposerMap * composerMap() const
Returns the map used by the atlas.
void readXMLMapSettings(const QDomElement &elem, const QDomDocument &doc)
Reads old (pre 2.2) map related atlas settings from xml.
Q_DECL_DEPRECATED void setMargin(float margin)
Sets the margin for the atlas map.
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
AtlasScalingMode atlasScalingMode() const
Returns the current atlas scaling mode.
Class for storing a coordinate reference system (CRS)
int count(const T &value) const
QString authid() const
Returns the authority identifier for the CRS, which includes both the authority (eg EPSG) and the CRS...
const QMap< QString, QgsMapLayer * > & mapLayers()
Retrieve the mapLayers collection (mainly intended for use by projection)
void parameterChanged()
Emitted when one of the parameters changes.
int transform(const QgsCoordinateTransform &ct)
Transform this geometry as described by CoordinateTransform ct.
void numberFeaturesChanged(int numFeatures)
Is emitted when the number of features for the atlas changes.
bool atlasDriven() const
Returns whether the map extent is set to follow the current atlas feature.
int currentFeatureNumber() const
Returns the current feature number, where a value of 0 corresponds to the first feature.
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.
QDomElement createElement(const QString &tagName)
bool nextFeature(QgsFeature &f)
void composerItems(QList< T * > &itemList)
Return composer items of a specific type.
double width() const
Width of the rectangle.
Definition: qgsrectangle.h:207
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
int size() const
Represents a vector layer which manages a vector based data sets.
int fieldNameIndex(const QString &fieldName) const
Returns the index of a field name or -1 if the field does not exist.
QString toString() const
void statusMsgChanged(const QString &message)
Is emitted when the atlas has an updated status bar message for the composer window.
iterator begin()
void setPredefinedScales(const QVector< qreal > &scales)
Sets the list of predefined scales for the atlas.
bool operator()(const QPair< QgsFeatureId, QString > &id1, const QPair< QgsFeatureId, QString > &id2)
double height() const
Height of the rectangle.
Definition: qgsrectangle.h:212
QRectF rect() const
const T value(const Key &key) const
static QgsExpressionContextScope * compositionScope(const QgsComposition *composition)
Creates a new scope which contains variables and functions relating to a QgsComposition.