QGIS API Documentation  master-6164ace
src/core/composer/qgsatlascomposition.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002                              qgsatlascomposition.cpp
00003                              -----------------------
00004     begin                : October 2012
00005     copyright            : (C) 2005 by Hugo Mercier
00006     email                : hugo dot mercier at oslandia dot com
00007  ***************************************************************************/
00008 
00009 /***************************************************************************
00010  *                                                                         *
00011  *   This program is free software; you can redistribute it and/or modify  *
00012  *   it under the terms of the GNU General Public License as published by  *
00013  *   the Free Software Foundation; either version 2 of the License, or     *
00014  *   (at your option) any later version.                                   *
00015  *                                                                         *
00016  ***************************************************************************/
00017 #include <stdexcept>
00018 
00019 #include "qgsatlascomposition.h"
00020 #include "qgsvectorlayer.h"
00021 #include "qgscomposermap.h"
00022 #include "qgscomposition.h"
00023 #include "qgsvectordataprovider.h"
00024 #include "qgsexpression.h"
00025 #include "qgsgeometry.h"
00026 #include "qgscomposerlabel.h"
00027 #include "qgsmaplayerregistry.h"
00028 
00029 QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition ) :
00030     mComposition( composition ),
00031     mEnabled( false ),
00032     mComposerMap( 0 ),
00033     mHideCoverage( false ), mFixedScale( false ), mMargin( 0.10 ), mFilenamePattern( "'output_'||$feature" ),
00034     mCoverageLayer( 0 ), mSingleFile( false ),
00035     mSortFeatures( false ), mSortAscending( true ),
00036     mFilterFeatures( false ), mFeatureFilter( "" )
00037 {
00038 
00039   // declare special columns with a default value
00040   QgsExpression::setSpecialColumn( "$page", QVariant(( int )1 ) );
00041   QgsExpression::setSpecialColumn( "$feature", QVariant(( int )0 ) );
00042   QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )1 ) );
00043   QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )0 ) );
00044 }
00045 
00046 QgsAtlasComposition::~QgsAtlasComposition()
00047 {
00048 }
00049 
00050 void QgsAtlasComposition::setCoverageLayer( QgsVectorLayer* layer )
00051 {
00052   mCoverageLayer = layer;
00053 
00054   // update the number of features
00055   QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
00056 }
00057 
00058 //
00059 // Private class only used for the sorting of features
00060 class FieldSorter
00061 {
00062   public:
00063     FieldSorter( QgsAtlasComposition::SorterKeys& keys, bool ascending = true ) : mKeys( keys ), mAscending( ascending ) {}
00064 
00065     bool operator()( const QgsFeatureId& id1, const QgsFeatureId& id2 )
00066     {
00067       bool result = true;
00068 
00069       if ( mKeys[ id1 ].type() == QVariant::Int )
00070       {
00071         result = mKeys[ id1 ].toInt() < mKeys[ id2 ].toInt();
00072       }
00073       else if ( mKeys[ id1 ].type() == QVariant::Double )
00074       {
00075         result = mKeys[ id1 ].toDouble() < mKeys[ id2 ].toDouble();
00076       }
00077       else if ( mKeys[ id1 ].type() == QVariant::String )
00078       {
00079         result = ( QString::localeAwareCompare( mKeys[ id1 ].toString(), mKeys[ id2 ].toString() ) < 0 );
00080       }
00081 
00082       return mAscending ? result : !result;
00083     }
00084   private:
00085     QgsAtlasComposition::SorterKeys& mKeys;
00086     bool mAscending;
00087 };
00088 
00089 void QgsAtlasComposition::beginRender()
00090 {
00091   if ( !mComposerMap || !mCoverageLayer )
00092   {
00093     return;
00094   }
00095 
00096   const QgsCoordinateReferenceSystem& coverage_crs = mCoverageLayer->crs();
00097   const QgsCoordinateReferenceSystem& destination_crs = mComposerMap->mapRenderer()->destinationCrs();
00098   // transformation needed for feature geometries
00099   mTransform.setSourceCrs( coverage_crs );
00100   mTransform.setDestCRS( destination_crs );
00101 
00102   const QgsFields& fields = mCoverageLayer->pendingFields();
00103 
00104   if ( !mSingleFile && mFilenamePattern.size() > 0 )
00105   {
00106     mFilenameExpr = std::auto_ptr<QgsExpression>( new QgsExpression( mFilenamePattern ) );
00107     // expression used to evaluate each filename
00108     // test for evaluation errors
00109     if ( mFilenameExpr->hasParserError() )
00110     {
00111       throw std::runtime_error( tr( "Filename parsing error: %1" ).arg( mFilenameExpr->parserErrorString() ).toLocal8Bit().data() );
00112     }
00113 
00114     // prepare the filename expression
00115     mFilenameExpr->prepare( fields );
00116   }
00117 
00118   // select all features with all attributes
00119   QgsFeatureIterator fit = mCoverageLayer->getFeatures();
00120 
00121   std::auto_ptr<QgsExpression> filterExpression;
00122   if ( mFilterFeatures )
00123   {
00124     filterExpression = std::auto_ptr<QgsExpression>( new QgsExpression( mFeatureFilter ) );
00125     if ( filterExpression->hasParserError() )
00126     {
00127       throw std::runtime_error( tr( "Feature filter parser error: %1" ).arg( filterExpression->parserErrorString() ).toLocal8Bit().data() );
00128     }
00129   }
00130 
00131   // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process
00132   // We thus store the feature ids for future extraction
00133   QgsFeature feat;
00134   mFeatureIds.clear();
00135   mFeatureKeys.clear();
00136   while ( fit.nextFeature( feat ) )
00137   {
00138     if ( mFilterFeatures )
00139     {
00140       QVariant result = filterExpression->evaluate( &feat, mCoverageLayer->pendingFields() );
00141       if ( filterExpression->hasEvalError() )
00142       {
00143         throw std::runtime_error( tr( "Feature filter eval error: %1" ).arg( filterExpression->evalErrorString() ).toLocal8Bit().data() );
00144       }
00145 
00146       // skip this feature if the filter evaluation if false
00147       if ( !result.toBool() )
00148       {
00149         continue;
00150       }
00151     }
00152     mFeatureIds.push_back( feat.id() );
00153 
00154     if ( mSortFeatures )
00155     {
00156       mFeatureKeys.insert( std::make_pair( feat.id(), feat.attributes()[ mSortKeyAttributeIdx ] ) );
00157     }
00158   }
00159 
00160   // sort features, if asked for
00161   if ( mSortFeatures )
00162   {
00163     FieldSorter sorter( mFeatureKeys, mSortAscending );
00164     qSort( mFeatureIds.begin(), mFeatureIds.end(), sorter );
00165   }
00166 
00167   mOrigExtent = mComposerMap->extent();
00168 
00169   mRestoreLayer = false;
00170   QStringList& layerSet = mComposition->mapRenderer()->layerSet();
00171   if ( mHideCoverage )
00172   {
00173     // look for the layer in the renderer's set
00174     int removeAt = layerSet.indexOf( mCoverageLayer->id() );
00175     if ( removeAt != -1 )
00176     {
00177       mRestoreLayer = true;
00178       layerSet.removeAt( removeAt );
00179     }
00180   }
00181 
00182   // special columns for expressions
00183   QgsExpression::setSpecialColumn( "$numpages", QVariant( mComposition->numPages() ) );
00184   QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )mFeatureIds.size() ) );
00185 }
00186 
00187 void QgsAtlasComposition::endRender()
00188 {
00189   if ( !mComposerMap || !mCoverageLayer )
00190   {
00191     return;
00192   }
00193 
00194   // reset label expression contexts
00195   QList<QgsComposerLabel*> labels;
00196   mComposition->composerItems( labels );
00197   for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit )
00198   {
00199     ( *lit )->setExpressionContext( 0, 0 );
00200   }
00201 
00202   // restore the coverage visibility
00203   if ( mRestoreLayer )
00204   {
00205     QStringList& layerSet = mComposition->mapRenderer()->layerSet();
00206 
00207     layerSet.push_back( mCoverageLayer->id() );
00208     mComposerMap->cache();
00209     mComposerMap->update();
00210   }
00211   mComposerMap->setNewExtent( mOrigExtent );
00212 }
00213 
00214 size_t QgsAtlasComposition::numFeatures() const
00215 {
00216   return mFeatureIds.size();
00217 }
00218 
00219 void QgsAtlasComposition::prepareForFeature( size_t featureI )
00220 {
00221   if ( !mComposerMap || !mCoverageLayer )
00222   {
00223     return;
00224   }
00225 
00226   // retrieve the next feature, based on its id
00227   mCoverageLayer->getFeatures( QgsFeatureRequest().setFilterFid( mFeatureIds[ featureI ] ) ).nextFeature( mCurrentFeature );
00228 
00229   if ( !mSingleFile && mFilenamePattern.size() > 0 )
00230   {
00231     QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
00232     QVariant filenameRes = mFilenameExpr->evaluate( &mCurrentFeature, mCoverageLayer->pendingFields() );
00233     if ( mFilenameExpr->hasEvalError() )
00234     {
00235       throw std::runtime_error( tr( "Filename eval error: %1" ).arg( mFilenameExpr->evalErrorString() ).toLocal8Bit().data() );
00236     }
00237 
00238     mCurrentFilename = filenameRes.toString();
00239   }
00240 
00241   //
00242   // compute the new extent
00243   // keep the original aspect ratio
00244   // and apply a margin
00245 
00246   // QgsGeometry::boundingBox is expressed in the geometry"s native CRS
00247   // We have to transform the grometry to the destination CRS and ask for the bounding box
00248   // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear
00249 
00250   QgsGeometry tgeom( *mCurrentFeature.geometry() );
00251   tgeom.transform( mTransform );
00252   QgsRectangle geom_rect = tgeom.boundingBox();
00253 
00254   double xa1 = geom_rect.xMinimum();
00255   double xa2 = geom_rect.xMaximum();
00256   double ya1 = geom_rect.yMinimum();
00257   double ya2 = geom_rect.yMaximum();
00258   QgsRectangle new_extent = geom_rect;
00259 
00260   // restore the original extent
00261   // (successive calls to setNewExtent tend to deform the original rectangle)
00262   mComposerMap->setNewExtent( mOrigExtent );
00263 
00264   if ( mFixedScale )
00265   {
00266     // only translate, keep the original scale (i.e. width x height)
00267 
00268     double geom_center_x = ( xa1 + xa2 ) / 2.0;
00269     double geom_center_y = ( ya1 + ya2 ) / 2.0;
00270     double xx = geom_center_x - mOrigExtent.width() / 2.0;
00271     double yy = geom_center_y - mOrigExtent.height() / 2.0;
00272     new_extent = QgsRectangle( xx,
00273                                yy,
00274                                xx + mOrigExtent.width(),
00275                                yy + mOrigExtent.height() );
00276   }
00277   else
00278   {
00279     // auto scale
00280 
00281     double geom_ratio = geom_rect.width() / geom_rect.height();
00282     double map_ratio = mOrigExtent.width() / mOrigExtent.height();
00283 
00284     // geometry height is too big
00285     if ( geom_ratio < map_ratio )
00286     {
00287       // extent the bbox's width
00288       double adj_width = ( map_ratio * geom_rect.height() - geom_rect.width() ) / 2.0;
00289       xa1 -= adj_width;
00290       xa2 += adj_width;
00291     }
00292     // geometry width is too big
00293     else if ( geom_ratio > map_ratio )
00294     {
00295       // extent the bbox's height
00296       double adj_height = ( geom_rect.width() / map_ratio - geom_rect.height() ) / 2.0;
00297       ya1 -= adj_height;
00298       ya2 += adj_height;
00299     }
00300     new_extent = QgsRectangle( xa1, ya1, xa2, ya2 );
00301 
00302     if ( mMargin > 0.0 )
00303     {
00304       new_extent.scale( 1 + mMargin );
00305     }
00306   }
00307 
00308   // evaluate label expressions
00309   QList<QgsComposerLabel*> labels;
00310   mComposition->composerItems( labels );
00311   QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) );
00312 
00313   for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit )
00314   {
00315     ( *lit )->setExpressionContext( &mCurrentFeature, mCoverageLayer );
00316   }
00317 
00318   // set the new extent (and render)
00319   mComposerMap->setNewExtent( new_extent );
00320 }
00321 
00322 const QString& QgsAtlasComposition::currentFilename() const
00323 {
00324   return mCurrentFilename;
00325 }
00326 
00327 void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const
00328 {
00329   QDomElement atlasElem = doc.createElement( "Atlas" );
00330   atlasElem.setAttribute( "enabled", mEnabled ? "true" : "false" );
00331   if ( !mEnabled )
00332   {
00333     return;
00334   }
00335 
00336   if ( mCoverageLayer )
00337   {
00338     atlasElem.setAttribute( "coverageLayer", mCoverageLayer->id() );
00339   }
00340   else
00341   {
00342     atlasElem.setAttribute( "coverageLayer", "" );
00343   }
00344   if ( mComposerMap )
00345   {
00346     atlasElem.setAttribute( "composerMap", mComposerMap->id() );
00347   }
00348   else
00349   {
00350     atlasElem.setAttribute( "composerMap", "" );
00351   }
00352   atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" );
00353   atlasElem.setAttribute( "fixedScale", mFixedScale ? "true" : "false" );
00354   atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" );
00355   atlasElem.setAttribute( "margin", QString::number( mMargin ) );
00356   atlasElem.setAttribute( "filenamePattern", mFilenamePattern );
00357 
00358   atlasElem.setAttribute( "sortFeatures", mSortFeatures ? "true" : "false" );
00359   if ( mSortFeatures )
00360   {
00361     atlasElem.setAttribute( "sortKey", QString::number( mSortKeyAttributeIdx ) );
00362     atlasElem.setAttribute( "sortAscending", mSortAscending ? "true" : "false" );
00363   }
00364   atlasElem.setAttribute( "filterFeatures", mFilterFeatures ? "true" : "false" );
00365   if ( mFilterFeatures )
00366   {
00367     atlasElem.setAttribute( "featureFilter", mFeatureFilter );
00368   }
00369 
00370   elem.appendChild( atlasElem );
00371 }
00372 
00373 void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocument& )
00374 {
00375   mEnabled = atlasElem.attribute( "enabled", "false" ) == "true" ? true : false;
00376   if ( !mEnabled )
00377   {
00378     emit parameterChanged();
00379     return;
00380   }
00381 
00382   // look for stored layer name
00383   mCoverageLayer = 0;
00384   QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers();
00385   for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it )
00386   {
00387     if ( it.key() == atlasElem.attribute( "coverageLayer" ) )
00388     {
00389       mCoverageLayer = dynamic_cast<QgsVectorLayer*>( it.value() );
00390       break;
00391     }
00392   }
00393   // look for stored composer map
00394   mComposerMap = 0;
00395   QList<const QgsComposerMap*> maps = mComposition->composerMapItems();
00396   for ( QList<const QgsComposerMap*>::const_iterator it = maps.begin(); it != maps.end(); ++it )
00397   {
00398     if (( *it )->id() == atlasElem.attribute( "composerMap" ).toInt() )
00399     {
00400       mComposerMap = const_cast<QgsComposerMap*>( *it );
00401       break;
00402     }
00403   }
00404   mMargin = atlasElem.attribute( "margin", "0.0" ).toDouble();
00405   mHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false;
00406   mFixedScale = atlasElem.attribute( "fixedScale", "false" ) == "true" ? true : false;
00407   mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false;
00408   mFilenamePattern = atlasElem.attribute( "filenamePattern", "" );
00409 
00410   mSortFeatures = atlasElem.attribute( "sortFeatures", "false" ) == "true" ? true : false;
00411   if ( mSortFeatures )
00412   {
00413     mSortKeyAttributeIdx = atlasElem.attribute( "sortKey", "0" ).toInt();
00414     mSortAscending = atlasElem.attribute( "sortAscending", "true" ) == "true" ? true : false;
00415   }
00416   mFilterFeatures = atlasElem.attribute( "filterFeatures", "false" ) == "true" ? true : false;
00417   if ( mFilterFeatures )
00418   {
00419     mFeatureFilter = atlasElem.attribute( "featureFilter", "" );
00420   }
00421 
00422   emit parameterChanged();
00423 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines