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