QGIS API Documentation  2.0.1-Dufour
 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 
19 #include "qgsatlascomposition.h"
20 #include "qgsvectorlayer.h"
21 #include "qgscomposermap.h"
22 #include "qgscomposition.h"
23 #include "qgsvectordataprovider.h"
24 #include "qgsexpression.h"
25 #include "qgsgeometry.h"
26 #include "qgscomposerlabel.h"
27 #include "qgsmaplayerregistry.h"
28 
30  mComposition( composition ),
31  mEnabled( false ),
32  mComposerMap( 0 ),
33  mHideCoverage( false ), mFixedScale( false ), mMargin( 0.10 ), mFilenamePattern( "'output_'||$feature" ),
34  mCoverageLayer( 0 ), mSingleFile( false ),
35  mSortFeatures( false ), mSortAscending( true ),
36  mFilterFeatures( false ), mFeatureFilter( "" )
37 {
38 
39  // declare special columns with a default value
40  QgsExpression::setSpecialColumn( "$page", QVariant(( int )1 ) );
41  QgsExpression::setSpecialColumn( "$feature", QVariant(( int )0 ) );
42  QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )1 ) );
43  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )0 ) );
44 }
45 
47 {
48 }
49 
51 {
52  mCoverageLayer = layer;
53 
54  // update the number of features
55  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
56 }
57 
58 //
59 // Private class only used for the sorting of features
61 {
62  public:
63  FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
64 
65  bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
66  {
67  bool result = true;
68 
69  if ( mKeys[ id1 ].type() == QVariant::Int )
70  {
71  result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
72  }
73  else if ( mKeys[ id1 ].type() == QVariant::Double )
74  {
75  result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
76  }
77  else if ( mKeys[ id1 ].type() == QVariant::String )
78  {
79  result = ( QString::localeAwareCompare( mKeys[ id1 ].toString(), mKeys[ id2 ].toString() ) < 0 );
80  }
81 
82  return mAscending ? result : !result;
83  }
84  private:
86  bool mAscending;
87 };
88 
90 {
91  if ( !mComposerMap || !mCoverageLayer )
92  {
93  return;
94  }
95 
96  const QgsCoordinateReferenceSystem& coverage_crs = mCoverageLayer->crs();
98  // transformation needed for feature geometries
99  mTransform.setSourceCrs( coverage_crs );
100  mTransform.setDestCRS( destination_crs );
101 
102  const QgsFields& fields = mCoverageLayer->pendingFields();
103 
104  if ( !mSingleFile && mFilenamePattern.size() > 0 )
105  {
106  mFilenameExpr = std::auto_ptr<QgsExpression>( new QgsExpression( mFilenamePattern ) );
107  // expression used to evaluate each filename
108  // test for evaluation errors
109  if ( mFilenameExpr->hasParserError() )
110  {
111  throw std::runtime_error( tr( "Filename parsing error: %1" ).arg( mFilenameExpr->parserErrorString() ).toLocal8Bit().data() );
112  }
113 
114  // prepare the filename expression
115  mFilenameExpr->prepare( fields );
116  }
117 
118  // select all features with all attributes
120 
121  std::auto_ptr<QgsExpression> filterExpression;
122  if ( mFilterFeatures )
123  {
124  filterExpression = std::auto_ptr<QgsExpression>( new QgsExpression( mFeatureFilter ) );
125  if ( filterExpression->hasParserError() )
126  {
127  throw std::runtime_error( tr( "Feature filter parser error: %1" ).arg( filterExpression->parserErrorString() ).toLocal8Bit().data() );
128  }
129  }
130 
131  // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
132  // We thus store the feature ids for future extraction
133  QgsFeature feat;
134  mFeatureIds.clear();
135  mFeatureKeys.clear();
136  while ( fit.nextFeature( feat ) )
137  {
138  if ( mFilterFeatures )
139  {
140  QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
141  if ( filterExpression->hasEvalError() )
142  {
143  throw std::runtime_error( tr( "Feature filter eval error: %1" ).arg( filterExpression->evalErrorString() ).toLocal8Bit().data() );
144  }
145 
146  // skip this feature if the filter evaluation if false
147  if ( !result.toBool() )
148  {
149  continue;
150  }
151  }
152  mFeatureIds.push_back( feat.id() );
153 
154  if ( mSortFeatures )
155  {
156  mFeatureKeys.insert( std::make_pair( feat.id(), feat.attributes()[ mSortKeyAttributeIdx ] ) );
157  }
158  }
159 
160  // sort features, if asked for
161  if ( mSortFeatures )
162  {
164  qSort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
165  }
166 
168 
169  mRestoreLayer = false;
170  QStringList& layerSet = mComposition->mapRenderer()->layerSet();
171  if ( mHideCoverage )
172  {
173  // look for the layer in the renderer's set
174  int removeAt = layerSet.indexOf( mCoverageLayer->id() );
175  if ( removeAt != -1 )
176  {
177  mRestoreLayer = true;
178  layerSet.removeAt( removeAt );
179  }
180  }
181 
182  // special columns for expressions
183  QgsExpression::setSpecialColumn( "$numpages", QVariant( mComposition->numPages() ) );
184  QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
185 }
186 
188 {
189  if ( !mComposerMap || !mCoverageLayer )
190  {
191  return;
192  }
193 
194  // reset label expression contexts
195  QList<QgsComposerLabel*> labels;
196  mComposition->composerItems( labels );
197  for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit )
198  {
199  ( *lit )->setExpressionContext( 0, 0 );
200  }
201 
202  // restore the coverage visibility
203  if ( mRestoreLayer )
204  {
205  QStringList& layerSet = mComposition->mapRenderer()->layerSet();
206 
207  layerSet.push_back( mCoverageLayer->id() );
208  mComposerMap->cache();
209  mComposerMap->update();
210  }
212 }
213 
215 {
216  return mFeatureIds.size();
217 }
218 
220 {
221  if ( !mComposerMap || !mCoverageLayer )
222  {
223  return;
224  }
225 
226  // retrieve the next feature, based on its id
228 
229  if ( !mSingleFile && mFilenamePattern.size() > 0 )
230  {
231  QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
232  QVariant filenameRes = mFilenameExpr->evaluate( &mCurrentFeature, mCoverageLayer->pendingFields() );
233  if ( mFilenameExpr->hasEvalError() )
234  {
235  throw std::runtime_error( tr( "Filename eval error: %1" ).arg( mFilenameExpr->evalErrorString() ).toLocal8Bit().data() );
236  }
237 
238  mCurrentFilename = filenameRes.toString();
239  }
240 
241  //
242  // compute the new extent
243  // keep the original aspect ratio
244  // and apply a margin
245 
246  // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
247  // We have to transform the grometry to the destination CRS and ask for the bounding box
248  // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
249 
251  tgeom.transform( mTransform );
252  QgsRectangle geom_rect = tgeom.boundingBox();
253 
254  double xa1 = geom_rect.xMinimum();
255  double xa2 = geom_rect.xMaximum();
256  double ya1 = geom_rect.yMinimum();
257  double ya2 = geom_rect.yMaximum();
258  QgsRectangle new_extent = geom_rect;
259 
260  // restore the original extent
261  // (successive calls to setNewExtent tend to deform the original rectangle)
263 
264  if ( mFixedScale )
265  {
266  // only translate, keep the original scale (i.e. width x height)
267 
268  double geom_center_x = ( xa1 + xa2 ) / 2.0;
269  double geom_center_y = ( ya1 + ya2 ) / 2.0;
270  double xx = geom_center_x - mOrigExtent.width() / 2.0;
271  double yy = geom_center_y - mOrigExtent.height() / 2.0;
272  new_extent = QgsRectangle( xx,
273  yy,
274  xx + mOrigExtent.width(),
275  yy + mOrigExtent.height() );
276  }
277  else
278  {
279  // auto scale
280 
281  double geom_ratio = geom_rect.width() / geom_rect.height();
282  double map_ratio = mOrigExtent.width() / mOrigExtent.height();
283 
284  // geometry height is too big
285  if ( geom_ratio < map_ratio )
286  {
287  // extent the bbox's width
288  double adj_width = ( map_ratio * geom_rect.height() - geom_rect.width() ) / 2.0;
289  xa1 -= adj_width;
290  xa2 += adj_width;
291  }
292  // geometry width is too big
293  else if ( geom_ratio > map_ratio )
294  {
295  // extent the bbox's height
296  double adj_height = ( geom_rect.width() / map_ratio - geom_rect.height() ) / 2.0;
297  ya1 -= adj_height;
298  ya2 += adj_height;
299  }
300  new_extent = QgsRectangle( xa1, ya1, xa2, ya2 );
301 
302  if ( mMargin > 0.0 )
303  {
304  new_extent.scale( 1 + mMargin );
305  }
306  }
307 
308  // evaluate label expressions
309  QList<QgsComposerLabel*> labels;
310  mComposition->composerItems( labels );
311  QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
312 
313  for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit )
314  {
315  ( *lit )->setExpressionContext( &mCurrentFeature, mCoverageLayer );
316  }
317 
318  // set the new extent (and render)
319  mComposerMap->setNewExtent( new_extent );
320 }
321 
323 {
324  return mCurrentFilename;
325 }
326 
327 void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const
328 {
329  QDomElement atlasElem = doc.createElement( "Atlas" );
330  atlasElem.setAttribute( "enabled", mEnabled ? "true" : "false" );
331  if ( !mEnabled )
332  {
333  return;
334  }
335 
336  if ( mCoverageLayer )
337  {
338  atlasElem.setAttribute( "coverageLayer", mCoverageLayer->id() );
339  }
340  else
341  {
342  atlasElem.setAttribute( "coverageLayer", "" );
343  }
344  if ( mComposerMap )
345  {
346  atlasElem.setAttribute( "composerMap", mComposerMap->id() );
347  }
348  else
349  {
350  atlasElem.setAttribute( "composerMap", "" );
351  }
352  atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" );
353  atlasElem.setAttribute( "fixedScale", mFixedScale ? "true" : "false" );
354  atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" );
355  atlasElem.setAttribute( "margin", QString::number( mMargin ) );
356  atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
357 
358  atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
359  if ( mSortFeatures )
360  {
361  atlasElem.setAttribute( "sortKey", QString::number( mSortKeyAttributeIdx ) );
362  atlasElem.setAttribute( "sortAscending", mSortAscending ? "true" : "false" );
363  }
364  atlasElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
365  if ( mFilterFeatures )
366  {
367  atlasElem.setAttribute( "featureFilter", mFeatureFilter );
368  }
369 
370  elem.appendChild( atlasElem );
371 }
372 
373 void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocument& )
374 {
375  mEnabled = atlasElem.attribute( "enabled", "false" ) == "true" ? true : false;
376  if ( !mEnabled )
377  {
378  emit parameterChanged();
379  return;
380  }
381 
382  // look for stored layer name
383  mCoverageLayer = 0;
384  QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
385  for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
386  {
387  if ( it.key() == atlasElem.attribute( "coverageLayer" ) )
388  {
389  mCoverageLayer = dynamic_cast<QgsVectorLayer*>( it.value() );
390  break;
391  }
392  }
393  // look for stored composer map
394  mComposerMap = 0;
395  QList<const QgsComposerMap*> maps = mComposition->composerMapItems();
396  for ( QList<const QgsComposerMap*>::const_iterator it = maps.begin(); it != maps.end(); ++it )
397  {
398  if (( *it )->id() == atlasElem.attribute( "composerMap" ).toInt() )
399  {
400  mComposerMap = const_cast<QgsComposerMap*>( *it );
401  break;
402  }
403  }
404  mMargin = atlasElem.attribute( "margin", "0.0" ).toDouble();
405  mHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false;
406  mFixedScale = atlasElem.attribute( "fixedScale", "false" ) == "true" ? true : false;
407  mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
408  mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
409 
410  mSortFeatures = atlasElem.attribute( "sortFeatures", "false" ) == "true" ? true : false;
411  if ( mSortFeatures )
412  {
413  mSortKeyAttributeIdx = atlasElem.attribute( "sortKey", "0" ).toInt();
414  mSortAscending = atlasElem.attribute( "sortAscending", "true" ) == "true" ? true : false;
415  }
416  mFilterFeatures = atlasElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
417  if ( mFilterFeatures )
418  {
419  mFeatureFilter = atlasElem.attribute( "featureFilter", "" );
420  }
421 
422  emit parameterChanged();
423 }