QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
32  mComposition( composition ),
33  mEnabled( false ),
34  mHideCoverage( false ), mFilenamePattern( "'output_'||$feature" ),
35  mCoverageLayer( 0 ), mSingleFile( false ),
36  mSortFeatures( false ), mSortAscending( true ), mCurrentFeatureNo( 0 ),
37  mFilterFeatures( false ), mFeatureFilter( "" ),
38  mFilenameParserError( QString() ),
39  mFilterParserError( QString() )
40 {
41 
42  // declare special columns with a default value
43  QgsExpression::setSpecialColumn( "$page", QVariant(( int )1 ) );
44  QgsExpression::setSpecialColumn( "$feature", QVariant(( int )0 ) );
45  QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )1 ) );
46  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )0 ) );
47  QgsExpression::setSpecialColumn( "$atlasfeatureid", QVariant(( int )0 ) );
48  QgsExpression::setSpecialColumn( "$atlasfeature", QVariant::fromValue( QgsFeature() ) );
49  QgsExpression::setSpecialColumn( "$atlasgeometry", QVariant::fromValue( QgsGeometry() ) );
50 
51  //listen out for layer removal
52  connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( removeLayers( QStringList ) ) );
53 }
54 
56 {
57 }
58 
59 void QgsAtlasComposition::setEnabled( bool enabled )
60 {
61  if ( enabled == mEnabled )
62  {
63  return;
64  }
65 
66  mEnabled = enabled;
67  mComposition->setAtlasMode( QgsComposition::AtlasOff );
68  emit toggled( enabled );
69  emit parameterChanged();
70 }
71 
72 void QgsAtlasComposition::removeLayers( QStringList layers )
73 {
74  if ( !mCoverageLayer )
75  {
76  return;
77  }
78 
79  foreach ( QString layerId, layers )
80  {
81  if ( layerId == mCoverageLayer->id() )
82  {
83  //current coverage layer removed
84  mCoverageLayer = 0;
85  setEnabled( false );
86  return;
87  }
88  }
89 }
90 
92 {
93  if ( layer == mCoverageLayer )
94  {
95  return;
96  }
97 
98  mCoverageLayer = layer;
99 
100  // update the number of features
101  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
102 
103  // Grab the first feature so that user can use it to test the style in rules.
104  if ( layer )
105  {
106  QgsFeature fet;
107  layer->getFeatures().nextFeature( fet );
108  QgsExpression::setSpecialColumn( "$atlasfeatureid", fet.id() );
109  QgsExpression::setSpecialColumn( "$atlasgeometry", QVariant::fromValue( *fet.geometry() ) );
110  QgsExpression::setSpecialColumn( "$atlasfeature", QVariant::fromValue( fet ) );
111  }
112 
113  emit coverageLayerChanged( layer );
114 }
115 
117 {
118  //deprecated method. Until removed just return the first atlas-enabled composer map
119 
120  //build a list of composer maps
121  QList<QgsComposerMap*> maps;
122  mComposition->composerItems( maps );
123  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
124  {
125  QgsComposerMap* currentMap = ( *mit );
126  if ( currentMap->atlasDriven() )
127  {
128  return currentMap;
129  }
130  }
131 
132  return 0;
133 }
134 
136 {
137  //deprecated
138 
139  if ( !map )
140  {
141  return;
142  }
143 
144  map->setAtlasDriven( true );
145 }
146 
147 
149 {
150  if ( !mCoverageLayer )
151  {
152  return -1;
153  }
154  return mCoverageLayer->fieldNameIndex( mSortKeyAttributeName );
155 }
156 
158 {
159  if ( mCoverageLayer )
160  {
161  const QgsFields fields = mCoverageLayer->pendingFields();
162  if ( idx >= 0 && idx < fields.count() )
163  {
164  mSortKeyAttributeName = fields[idx].name();
165  return;
166  }
167  }
168  mSortKeyAttributeName = "";
169 }
170 
171 //
172 // Private class only used for the sorting of features
174 {
175  public:
176  FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
177 
178  bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
179  {
180  bool result = true;
181 
182  if ( mKeys[ id1 ].type() == QVariant::Int )
183  {
184  result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
185  }
186  else if ( mKeys[ id1 ].type() == QVariant::Double )
187  {
188  result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
189  }
190  else if ( mKeys[ id1 ].type() == QVariant::String )
191  {
192  result = ( QString::localeAwareCompare( mKeys[ id1 ].toString(), mKeys[ id2 ].toString() ) < 0 );
193  }
194 
195  return mAscending ? result : !result;
196  }
197  private:
199  bool mAscending;
200 };
201 
203 {
204  //needs to be called when layer, filter, sort changes
205 
206  if ( !mCoverageLayer )
207  {
208  return 0;
209  }
210 
211  updateFilenameExpression();
212 
213  // select all features with all attributes
214  QgsFeatureIterator fit = mCoverageLayer->getFeatures();
215 
216  QScopedPointer<QgsExpression> filterExpression;
217  if ( mFilterFeatures && !mFeatureFilter.isEmpty() )
218  {
219  filterExpression.reset( new QgsExpression( mFeatureFilter ) );
220  if ( filterExpression->hasParserError() )
221  {
222  mFilterParserError = filterExpression->parserErrorString();
223  return 0;
224  }
225  }
226  mFilterParserError = QString();
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  while ( fit.nextFeature( feat ) )
235  {
236  if ( !filterExpression.isNull() )
237  {
238  QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
239  if ( filterExpression->hasEvalError() )
240  {
241  QgsMessageLog::logMessage( tr( "Atlas filter eval error: %1" ).arg( filterExpression->evalErrorString() ), tr( "Composer" ) );
242  }
243 
244  // skip this feature if the filter evaluation if false
245  if ( !result.toBool() )
246  {
247  continue;
248  }
249  }
250  mFeatureIds.push_back( feat.id() );
251 
252  if ( mSortFeatures && sortIdx != -1 )
253  {
254  mFeatureKeys.insert( feat.id(), feat.attributes()[ sortIdx ] );
255  }
256  }
257 
258  // sort features, if asked for
259  if ( mFeatureKeys.count() )
260  {
261  FieldSorter sorter( mFeatureKeys, mSortAscending );
262  qSort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
263  }
264 
265  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )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 
277 
279 {
280  if ( !mCoverageLayer )
281  {
282  return false;
283  }
284 
285  emit renderBegun();
286 
287  bool featuresUpdated = updateFeatures();
288  if ( !featuresUpdated )
289  {
290  //no matching features found
291  return false;
292  }
293 
294  // special columns for expressions
295  QgsExpression::setSpecialColumn( "$numpages", QVariant( mComposition->numPages() ) );
296  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
297 
298  return true;
299 }
300 
302 {
303  if ( !mCoverageLayer )
304  {
305  return;
306  }
307 
308  emit featureChanged( 0 );
309 
310  updateAtlasMaps();
311 
312  emit renderEnded();
313 }
314 
315 void QgsAtlasComposition::updateAtlasMaps()
316 {
317  //update atlas-enabled composer maps
318  QList<QgsComposerMap*> 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 = mFeatureIds.indexOf( feat->id() );
372  if ( featureI < 0 )
373  {
374  //feature not found
375  return false;
376  }
377 
378  return prepareForFeature( featureI );
379 }
380 
382 {
383  prepareForFeature( mCurrentFeatureNo, false );
384 }
385 
386 bool QgsAtlasComposition::prepareForFeature( const int featureI, const bool updateMaps )
387 {
388  if ( !mCoverageLayer )
389  {
390  return false;
391  }
392 
393  if ( mFeatureIds.size() == 0 )
394  {
395  emit statusMsgChanged( tr( "No matching atlas features" ) );
396  return false;
397  }
398 
399  mCurrentFeatureNo = featureI;
400 
401  // retrieve the next feature, based on its id
402  mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ] ) ).nextFeature( mCurrentFeature );
403 
404  QgsExpression::setSpecialColumn( "$atlasfeatureid", mCurrentFeature.id() );
405  QgsExpression::setSpecialColumn( "$atlasgeometry", QVariant::fromValue( *mCurrentFeature.geometry() ) );
406  QgsExpression::setSpecialColumn( "$atlasfeature", QVariant::fromValue( mCurrentFeature ) );
407  QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
408 
409  // generate filename for current feature
410  if ( !evalFeatureFilename() )
411  {
412  //error evaluating filename
413  return false;
414  }
415 
416  emit featureChanged( &mCurrentFeature );
417  emit statusMsgChanged( QString( tr( "Atlas feature %1 of %2" ) ).arg( featureI + 1 ).arg( mFeatureIds.size() ) );
418 
419  if ( !mCurrentFeature.isValid() )
420  {
421  //bad feature
422  return true;
423  }
424 
425  if ( !updateMaps )
426  {
427  //nothing more to do
428  return true;
429  }
430 
431  //update composer maps
432 
433  //build a list of atlas-enabled composer maps
434  QList<QgsComposerMap*> maps;
435  QList<QgsComposerMap*> atlasMaps;
436  mComposition->composerItems( maps );
437  if ( maps.isEmpty() )
438  {
439  return true;
440  }
441  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
442  {
443  QgsComposerMap* currentMap = ( *mit );
444  if ( !currentMap->atlasDriven() )
445  {
446  continue;
447  }
448  atlasMaps << currentMap;
449  }
450 
451  if ( atlasMaps.count() > 0 )
452  {
453  //clear the transformed bounds of the previous feature
454  mTransformedFeatureBounds = QgsRectangle();
455 
456  // compute extent of current feature in the map CRS. This should be set on a per-atlas map basis,
457  // but given that it's not currently possible to have maps with different CRSes we can just
458  // calculate it once based on the first atlas maps' CRS.
459  computeExtent( atlasMaps[0] );
460  }
461 
462  for ( QList<QgsComposerMap*>::iterator mit = maps.begin(); mit != maps.end(); ++mit )
463  {
464  if (( *mit )->atlasDriven() )
465  {
466  // map is atlas driven, so update it's bounds (causes a redraw)
467  prepareMap( *mit );
468  }
469  else
470  {
471  // map is not atlas driven, so manually force a redraw (to reflect possibly atlas
472  // dependent symbology)
473  ( *mit )->cache();
474  }
475  }
476 
477  return true;
478 }
479 
480 void QgsAtlasComposition::computeExtent( QgsComposerMap* map )
481 {
482  // compute the extent of the current feature, in the crs of the specified map
483 
484  const QgsCoordinateReferenceSystem& coverage_crs = mCoverageLayer->crs();
485  // transformation needed for feature geometries
486  const QgsCoordinateReferenceSystem& destination_crs = map->composition()->mapSettings().destinationCrs();
487  mTransform.setSourceCrs( coverage_crs );
488  mTransform.setDestCRS( destination_crs );
489 
490  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
491  // We have to transform the grometry to the destination CRS and ask for the bounding box
492  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
493  QgsGeometry tgeom( *mCurrentFeature.geometry() );
494  tgeom.transform( mTransform );
495  mTransformedFeatureBounds = tgeom.boundingBox();
496 }
497 
499 {
500  if ( !map->atlasDriven() )
501  {
502  return;
503  }
504 
505  if ( mTransformedFeatureBounds.isEmpty() )
506  {
507  //transformed extent of current feature hasn't been calculated yet. This can happen if
508  //a map has been set to be atlas controlled after prepare feature was called
509  computeExtent( map );
510  }
511 
512  double xa1 = mTransformedFeatureBounds.xMinimum();
513  double xa2 = mTransformedFeatureBounds.xMaximum();
514  double ya1 = mTransformedFeatureBounds.yMinimum();
515  double ya2 = mTransformedFeatureBounds.yMaximum();
516  QgsRectangle newExtent = mTransformedFeatureBounds;
517  QgsRectangle mOrigExtent( map->extent() );
518 
519  //sanity check - only allow fixed scale mode for point layers
520  bool isPointLayer = false;
521  switch ( mCoverageLayer->wkbType() )
522  {
523  case QGis::WKBPoint:
524  case QGis::WKBPoint25D:
525  case QGis::WKBMultiPoint:
527  isPointLayer = true;
528  break;
529  default:
530  isPointLayer = false;
531  break;
532  }
533 
534  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || map->atlasScalingMode() == QgsComposerMap::Predefined || isPointLayer )
535  {
536  QgsScaleCalculator calc;
537  calc.setMapUnits( composition()->mapSettings().mapUnits() );
538  calc.setDpi( 25.4 );
539  double originalScale = calc.calculate( mOrigExtent, map->rect().width() );
540  double geomCenterX = ( xa1 + xa2 ) / 2.0;
541  double geomCenterY = ( ya1 + ya2 ) / 2.0;
542 
543  if ( map->atlasScalingMode() == QgsComposerMap::Fixed || isPointLayer )
544  {
545  // only translate, keep the original scale (i.e. width x height)
546  double xMin = geomCenterX - mOrigExtent.width() / 2.0;
547  double yMin = geomCenterY - mOrigExtent.height() / 2.0;
548  newExtent = QgsRectangle( xMin,
549  yMin,
550  xMin + mOrigExtent.width(),
551  yMin + mOrigExtent.height() );
552 
553  //scale newExtent to match original scale of map
554  //this is required for geographic coordinate systems, where the scale varies by extent
555  double newScale = calc.calculate( newExtent, map->rect().width() );
556  newExtent.scale( originalScale / newScale );
557  }
558  else if ( map->atlasScalingMode() == QgsComposerMap::Predefined )
559  {
560  // choose one of the predefined scales
561  double newWidth = mOrigExtent.width();
562  double newHeight = mOrigExtent.height();
563  const QVector<qreal>& scales = mPredefinedScales;
564  for ( int i = 0; i < scales.size(); i++ )
565  {
566  double ratio = scales[i] / originalScale;
567  newWidth = mOrigExtent.width() * ratio;
568  newHeight = mOrigExtent.height() * ratio;
569 
570  // compute new extent, centered on feature
571  double xMin = geomCenterX - newWidth / 2.0;
572  double yMin = geomCenterY - newHeight / 2.0;
573  newExtent = QgsRectangle( xMin,
574  yMin,
575  xMin + newWidth,
576  yMin + newHeight );
577 
578  //scale newExtent to match desired map scale
579  //this is required for geographic coordinate systems, where the scale varies by extent
580  double newScale = calc.calculate( newExtent, map->rect().width() );
581  newExtent.scale( scales[i] / newScale );
582 
583  if (( newExtent.width() >= mTransformedFeatureBounds.width() ) && ( newExtent.height() >= mTransformedFeatureBounds.height() ) )
584  {
585  // this is the smallest extent that embeds the feature, stop here
586  break;
587  }
588  }
589  }
590  }
591  else if ( map->atlasScalingMode() == QgsComposerMap::Auto )
592  {
593  // auto scale
594 
595  double geomRatio = mTransformedFeatureBounds.width() / mTransformedFeatureBounds.height();
596  double mapRatio = mOrigExtent.width() / mOrigExtent.height();
597 
598  // geometry height is too big
599  if ( geomRatio < mapRatio )
600  {
601  // extent the bbox's width
602  double adjWidth = ( mapRatio * mTransformedFeatureBounds.height() - mTransformedFeatureBounds.width() ) / 2.0;
603  xa1 -= adjWidth;
604  xa2 += adjWidth;
605  }
606  // geometry width is too big
607  else if ( geomRatio > mapRatio )
608  {
609  // extent the bbox's height
610  double adjHeight = ( mTransformedFeatureBounds.width() / mapRatio - mTransformedFeatureBounds.height() ) / 2.0;
611  ya1 -= adjHeight;
612  ya2 += adjHeight;
613  }
614  newExtent = QgsRectangle( xa1, ya1, xa2, ya2 );
615 
616  if ( map->atlasMargin() > 0.0 )
617  {
618  newExtent.scale( 1 + map->atlasMargin() );
619  }
620  }
621 
622  // set the new extent (and render)
623  map->setNewAtlasFeatureExtent( newExtent );
624 }
625 
627 {
628  return mCurrentFilename;
629 }
630 
631 void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const
632 {
633  QDomElement atlasElem = doc.createElement( "Atlas" );
634  atlasElem.setAttribute( "enabled", mEnabled ? "true" : "false" );
635  if ( !mEnabled )
636  {
637  return;
638  }
639 
640  if ( mCoverageLayer )
641  {
642  atlasElem.setAttribute( "coverageLayer", mCoverageLayer->id() );
643  }
644  else
645  {
646  atlasElem.setAttribute( "coverageLayer", "" );
647  }
648 
649  atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" );
650  atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" );
651  atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
652 
653  atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
654  if ( mSortFeatures )
655  {
656  atlasElem.setAttribute( "sortKey", mSortKeyAttributeName );
657  atlasElem.setAttribute( "sortAscending", mSortAscending ? "true" : "false" );
658  }
659  atlasElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
660  if ( mFilterFeatures )
661  {
662  atlasElem.setAttribute( "featureFilter", mFeatureFilter );
663  }
664 
665  elem.appendChild( atlasElem );
666 }
667 
668 void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocument& )
669 {
670  mEnabled = atlasElem.attribute( "enabled", "false" ) == "true" ? true : false;
671  emit toggled( mEnabled );
672  if ( !mEnabled )
673  {
674  emit parameterChanged();
675  return;
676  }
677 
678  // look for stored layer name
679  mCoverageLayer = 0;
680  QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
681  for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
682  {
683  if ( it.key() == atlasElem.attribute( "coverageLayer" ) )
684  {
685  mCoverageLayer = dynamic_cast<QgsVectorLayer*>( it.value() );
686  break;
687  }
688  }
689 
690  mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
691  mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
692 
693  mSortFeatures = atlasElem.attribute( "sortFeatures", "false" ) == "true" ? true : false;
694  if ( mSortFeatures )
695  {
696  mSortKeyAttributeName = atlasElem.attribute( "sortKey", "" );
697  // since 2.3, the field name is saved instead of the field index
698  // following code keeps compatibility with version 2.2 projects
699  // to be removed in QGIS 3.0
700  bool isIndex;
701  int idx = mSortKeyAttributeName.toInt( &isIndex );
702  if ( isIndex && mCoverageLayer )
703  {
704  const QgsFields fields = mCoverageLayer->pendingFields();
705  if ( idx >= 0 && idx < fields.count() )
706  {
707  mSortKeyAttributeName = fields[idx].name();
708  }
709  }
710  mSortAscending = atlasElem.attribute( "sortAscending", "true" ) == "true" ? true : false;
711  }
712  mFilterFeatures = atlasElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
713  if ( mFilterFeatures )
714  {
715  mFeatureFilter = atlasElem.attribute( "featureFilter", "" );
716  }
717 
718  mHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false;
719 
720  emit parameterChanged();
721 }
722 
723 void QgsAtlasComposition::readXMLMapSettings( const QDomElement &elem, const QDomDocument &doc )
724 {
725  Q_UNUSED( doc );
726  //look for stored composer map, to upgrade pre 2.1 projects
727  int composerMapNo = elem.attribute( "composerMap", "-1" ).toInt();
729  if ( composerMapNo != -1 )
730  {
731  QList<QgsComposerMap*> maps;
732  mComposition->composerItems( maps );
733  for ( QList<QgsComposerMap*>::iterator it = maps.begin(); it != maps.end(); ++it )
734  {
735  if (( *it )->id() == composerMapNo )
736  {
737  composerMap = ( *it );
738  composerMap->setAtlasDriven( true );
739  break;
740  }
741  }
742  }
743 
744  //upgrade pre 2.1 projects
745  double margin = elem.attribute( "margin", "0.0" ).toDouble();
746  if ( composerMap && margin != 0 )
747  {
748  composerMap->setAtlasMargin( margin );
749  }
750  bool fixedScale = elem.attribute( "fixedScale", "false" ) == "true" ? true : false;
751  if ( composerMap && fixedScale )
752  {
754  }
755 }
756 
758 {
759  mHideCoverage = hide;
760 
761  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
762  {
763  //an atlas preview is enabled, so reflect changes in coverage layer visibility immediately
764  updateAtlasMaps();
765  mComposition->update();
766  }
767 
768 }
769 
770 bool QgsAtlasComposition::setFilenamePattern( const QString& pattern )
771 {
772  mFilenamePattern = pattern;
773  return updateFilenameExpression();
774 }
775 
776 bool QgsAtlasComposition::updateFilenameExpression()
777 {
778  if ( !mCoverageLayer )
779  {
780  return false;
781  }
782 
783  const QgsFields& fields = mCoverageLayer->pendingFields();
784 
785  if ( mFilenamePattern.size() > 0 )
786  {
787  mFilenameExpr.reset( new QgsExpression( mFilenamePattern ) );
788  // expression used to evaluate each filename
789  // test for evaluation errors
790  if ( mFilenameExpr->hasParserError() )
791  {
792  mFilenameParserError = mFilenameExpr->parserErrorString();
793  return false;
794  }
795 
796  // prepare the filename expression
797  mFilenameExpr->prepare( fields );
798  }
799 
800  //if atlas preview is currently enabled, regenerate filename for current feature
801  if ( mComposition->atlasMode() == QgsComposition::PreviewAtlas )
802  {
803  evalFeatureFilename();
804  }
805  return true;
806 }
807 
808 bool QgsAtlasComposition::evalFeatureFilename()
809 {
810  //generate filename for current atlas feature
811  if ( mFilenamePattern.size() > 0 && !mFilenameExpr.isNull() )
812  {
813  QVariant filenameRes = mFilenameExpr->evaluate( &mCurrentFeature, mCoverageLayer->pendingFields() );
814  if ( mFilenameExpr->hasEvalError() )
815  {
816  QgsMessageLog::logMessage( tr( "Atlas filename evaluation error: %1" ).arg( mFilenameExpr->evalErrorString() ), tr( "Composer" ) );
817  return false;
818  }
819 
820  mCurrentFilename = filenameRes.toString();
821  }
822  return true;
823 }
824 
825 void QgsAtlasComposition::setPredefinedScales( const QVector<qreal>& scales )
826 {
827  mPredefinedScales = scales;
828  // make sure the list is sorted
829  qSort( mPredefinedScales.begin(), mPredefinedScales.end() );
830 }
831 
834 {
835  //deprecated method. Until removed just return the property for the first atlas-enabled composer map
836  QgsComposerMap * map = composerMap();
837  if ( !map )
838  {
839  return false;
840  }
841 
842  return map->atlasFixedScale();
843 }
844 
846 {
847  //deprecated method. Until removed just set the property for the first atlas-enabled composer map
848  QgsComposerMap * map = composerMap();
849  if ( !map )
850  {
851  return;
852  }
853 
855 }
856 
858 {
859  //deprecated method. Until removed just return the property for the first atlas-enabled composer map
860  QgsComposerMap * map = composerMap();
861  if ( !map )
862  {
863  return 0;
864  }
865 
866  return map->atlasMargin();
867 }
868 
869 void QgsAtlasComposition::setMargin( float margin )
870 {
871  //deprecated method. Until removed just set the property for the first atlas-enabled composer map
872  QgsComposerMap * map = composerMap();
873  if ( !map )
874  {
875  return;
876  }
877 
878  map->setAtlasMargin(( double ) margin );
879 }