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