|
Quantum GIS API Documentation
master-693a1fe
|
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 }