| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,288 @@ | ||
| /*************************************************************************** | ||
| qgsatlascompositionwidget.cpp | ||
| ----------------------------- | ||
| begin : October 2012 | ||
| copyright : (C) 2012 Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
|
|
||
| #include "qgsatlascompositionwidget.h" | ||
| #include "qgsatlascomposition.h" | ||
| #include "qgscomposition.h" | ||
| #include "qgsmaplayerregistry.h" | ||
| #include "qgsvectorlayer.h" | ||
| #include "qgsexpressionbuilderdialog.h" | ||
| #include "qgscomposermap.h" | ||
|
|
||
| QgsAtlasCompositionWidget::QgsAtlasCompositionWidget( QWidget* parent, QgsAtlasComposition* atlas, QgsComposition* c ): | ||
| QWidget( parent ), mAtlas( atlas ), mComposition( c ) | ||
| { | ||
| setupUi( this ); | ||
|
|
||
| // populate the layer list | ||
| mAtlasCoverageLayerComboBox->clear(); | ||
| QMap< QString, QgsMapLayer * >& layers = QgsMapLayerRegistry::instance()->mapLayers(); | ||
| int idx = 0; | ||
| for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it ) | ||
| { | ||
| // Only consider vector layers | ||
| if ( dynamic_cast<QgsVectorLayer*>(it.value()) ) | ||
| { | ||
| mAtlasCoverageLayerComboBox->insertItem( idx++, it.key(), /* userdata */ qVariantFromValue( (void*)it.value() ) ); | ||
| } | ||
| } | ||
|
|
||
| // Connect to addition / removal of layers | ||
| QgsMapLayerRegistry* layerRegistry = QgsMapLayerRegistry::instance(); | ||
| if ( layerRegistry ) | ||
| { | ||
| connect( layerRegistry, SIGNAL( layerWillBeRemoved( QString ) ), this, SLOT( onLayerRemoved( QString ) ) ); | ||
| connect( layerRegistry, SIGNAL( layerWasAdded( QgsMapLayer* ) ), this, SLOT( onLayerAdded( QgsMapLayer* ) ) ); | ||
| } | ||
|
|
||
| // update the composer map combo box | ||
| // populate the map list | ||
| mComposerMapComboBox->clear(); | ||
| QList<const QgsComposerMap*> availableMaps = mComposition->composerMapItems(); | ||
| QList<const QgsComposerMap*>::const_iterator mapItemIt = availableMaps.constBegin(); | ||
| for ( ; mapItemIt != availableMaps.constEnd(); ++mapItemIt ) | ||
| { | ||
| mComposerMapComboBox->addItem( tr( "Map %1" ).arg(( *mapItemIt )->id() ), qVariantFromValue( (void*)*mapItemIt ) ); | ||
| } | ||
|
|
||
| // Connect to addition / removal of maps | ||
| connect( mComposition, SIGNAL( composerMapAdded( QgsComposerMap* ) ), this, SLOT( onComposerMapAdded( QgsComposerMap* ) ) ); | ||
| connect( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SLOT( onItemRemoved( QgsComposerItem* ) ) ); | ||
|
|
||
| // connect to updates | ||
| connect( mAtlas, SIGNAL( parameterChanged() ), this, SLOT( updateGuiElements() ) ); | ||
|
|
||
| updateGuiElements(); | ||
| } | ||
|
|
||
| QgsAtlasCompositionWidget::~QgsAtlasCompositionWidget() | ||
| { | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mUseAtlasCheckBox_stateChanged( int state ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( state == Qt::Checked ) | ||
| { | ||
| atlasMap->setEnabled( true ); | ||
| mAtlasFrame->setEnabled( true ); | ||
| } | ||
| else | ||
| { | ||
| atlasMap->setEnabled( false ); | ||
| mAtlasFrame->setEnabled( false ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::onLayerRemoved( QString layerName ) | ||
| { | ||
| // update the atlas coverage layer combo box | ||
| for ( int i = 0; i < mAtlasCoverageLayerComboBox->count(); ++i ) | ||
| { | ||
| if ( mAtlasCoverageLayerComboBox->itemText( i ) == layerName ) | ||
| { | ||
| mAtlasCoverageLayerComboBox->removeItem( i ); | ||
| break; | ||
| } | ||
| } | ||
| if ( mAtlasCoverageLayerComboBox->count() == 0 ) | ||
| { | ||
| mAtlas->setCoverageLayer( 0 ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::onLayerAdded( QgsMapLayer* map ) | ||
| { | ||
| // update the atlas coverage layer combo box | ||
| QgsVectorLayer* vectorLayer = dynamic_cast<QgsVectorLayer*>( map ); | ||
| if ( vectorLayer ) | ||
| { | ||
| mAtlasCoverageLayerComboBox->addItem( map->id(), qVariantFromValue( (void*)map ) ); | ||
| } | ||
| if ( mAtlasCoverageLayerComboBox->count() == 1 ) | ||
| { | ||
| mAtlas->setCoverageLayer( vectorLayer ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::onComposerMapAdded( QgsComposerMap* map ) | ||
| { | ||
| mComposerMapComboBox->addItem( tr( "Map %1" ).arg( map->id() ), qVariantFromValue( (void*)map ) ); | ||
| if ( mComposerMapComboBox->count() == 1 ) | ||
| { | ||
| mAtlas->setComposerMap( map ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::onItemRemoved( QgsComposerItem* item ) | ||
| { | ||
| QgsComposerMap* map = dynamic_cast<QgsComposerMap*>( item ); | ||
| if ( map ) | ||
| { | ||
| int idx = mComposerMapComboBox->findData( qVariantFromValue( (void*)map ) ); | ||
| if ( idx != -1 ) | ||
| { | ||
| mComposerMapComboBox->removeItem( idx ); | ||
| } | ||
| } | ||
| if ( mComposerMapComboBox->count() == 0 ) | ||
| { | ||
| mAtlas->setComposerMap( 0 ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasCoverageLayerComboBox_currentIndexChanged( int index ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
| if ( index == -1 ) | ||
| { | ||
| atlasMap->setCoverageLayer( 0 ); | ||
| } | ||
| else | ||
| { | ||
| QgsVectorLayer* layer = reinterpret_cast<QgsVectorLayer*>(mAtlasCoverageLayerComboBox->itemData( index ).value<void*>()); | ||
| atlasMap->setCoverageLayer( layer ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mComposerMapComboBox_currentIndexChanged( int index ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
| if ( index == -1 ) | ||
| { | ||
| atlasMap->setComposerMap( 0 ); | ||
| } | ||
| else | ||
| { | ||
| QgsComposerMap* map = reinterpret_cast<QgsComposerMap*>(mComposerMapComboBox->itemData( index ).value<void*>()); | ||
| atlasMap->setComposerMap( map ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasFilenamePatternEdit_textChanged( const QString& text ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| atlasMap->setFilenamePattern( text ); | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasFilenameExpressionButton_clicked() | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap || !atlasMap->coverageLayer() ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| QgsExpressionBuilderDialog exprDlg( atlasMap->coverageLayer(), mAtlasFilenamePatternEdit->text(), this ); | ||
| exprDlg.setWindowTitle( tr( "Expression based filename" ) ); | ||
| if ( exprDlg.exec() == QDialog::Accepted ) | ||
| { | ||
| QString expression = exprDlg.expressionText(); | ||
| if ( !expression.isEmpty() ) | ||
| { | ||
| // will emit a textChanged signal | ||
| mAtlasFilenamePatternEdit->setText( expression ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasHideCoverageCheckBox_stateChanged( int state ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
| atlasMap->setHideCoverage( state == Qt::Checked ); | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasFixedScaleCheckBox_stateChanged( int state ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
| atlasMap->setFixedScale( state == Qt::Checked ); | ||
|
|
||
| // in fixed scale mode, the margin is meaningless | ||
| if ( state == Qt::Checked ) | ||
| { | ||
| mAtlasMarginSpinBox->setEnabled( false ); | ||
| } | ||
| else | ||
| { | ||
| mAtlasMarginSpinBox->setEnabled( true ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::on_mAtlasSingleFileCheckBox_stateChanged( int state ) | ||
| { | ||
| QgsAtlasComposition* atlasMap = mAtlas; | ||
| if ( !atlasMap ) | ||
| { | ||
| return; | ||
| } | ||
| atlasMap->setSingleFile( state == Qt::Checked ); | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::updateGuiElements() | ||
| { | ||
| if ( mAtlas->enabled() ) | ||
| { | ||
| mUseAtlasCheckBox->setCheckState( Qt::Checked ); | ||
| } | ||
| else | ||
| { | ||
| mUseAtlasCheckBox->setCheckState( Qt::Unchecked ); | ||
| } | ||
|
|
||
| int idx = mAtlasCoverageLayerComboBox->findData( qVariantFromValue( (void*)mAtlas->coverageLayer() )); | ||
| if ( idx != -1 ) | ||
| { | ||
| mAtlasCoverageLayerComboBox->setCurrentIndex( idx ); | ||
| } | ||
| idx = mComposerMapComboBox->findData( qVariantFromValue( (void*)mAtlas->composerMap() )); | ||
| if ( idx != -1 ) | ||
| { | ||
| mComposerMapComboBox->setCurrentIndex( idx ); | ||
| } | ||
|
|
||
| mAtlasMarginSpinBox->setValue( static_cast<int>(mAtlas->margin() * 100) ); | ||
| mAtlasFilenamePatternEdit->setText( mAtlas->filenamePattern() ); | ||
| mAtlasFixedScaleCheckBox->setCheckState( mAtlas->fixedScale() ? Qt::Checked : Qt::Unchecked ); | ||
| mAtlasHideCoverageCheckBox->setCheckState( mAtlas->hideCoverage() ? Qt::Checked : Qt::Unchecked ); | ||
| mAtlasSingleFileCheckBox->setCheckState( mAtlas->singleFile() ? Qt::Checked : Qt::Unchecked ); | ||
| } | ||
|
|
||
| void QgsAtlasCompositionWidget::blockAllSignals( bool b ) | ||
| { | ||
| mUseAtlasCheckBox->blockSignals( b ); | ||
| mAtlasFrame->blockSignals( b ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /*************************************************************************** | ||
| qgsatlascompositionwidget.h | ||
| --------------------------- | ||
| begin : October 2012 | ||
| copyright : (C) 2012 Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
|
|
||
| #include "ui_qgsatlascompositionwidgetbase.h" | ||
|
|
||
| class QgsComposition; | ||
| class QgsAtlasComposition; | ||
| class QgsMapLayer; | ||
| class QgsComposerMap; | ||
| class QgsComposerItem; | ||
|
|
||
| /** \ingroup MapComposer | ||
| * Input widget for QgsAtlasComposition | ||
| */ | ||
| class QgsAtlasCompositionWidget: | ||
| public QWidget, | ||
| private Ui::QgsAtlasCompositionWidgetBase | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| QgsAtlasCompositionWidget( QWidget* parent, QgsAtlasComposition* atlas, QgsComposition* c ); | ||
| ~QgsAtlasCompositionWidget(); | ||
|
|
||
| public slots: | ||
| void on_mUseAtlasCheckBox_stateChanged( int state ); | ||
| void on_mComposerMapComboBox_currentIndexChanged( int index ); | ||
| void on_mAtlasCoverageLayerComboBox_currentIndexChanged( int index ); | ||
| void on_mAtlasFilenamePatternEdit_textChanged( const QString& text ); | ||
| void on_mAtlasFilenameExpressionButton_clicked(); | ||
| void on_mAtlasHideCoverageCheckBox_stateChanged( int state ); | ||
| void on_mAtlasFixedScaleCheckBox_stateChanged( int state ); | ||
| void on_mAtlasSingleFileCheckBox_stateChanged( int state ); | ||
|
|
||
| private slots: | ||
| void onLayerRemoved( QString ); | ||
| void onLayerAdded( QgsMapLayer* ); | ||
| void onComposerMapAdded( QgsComposerMap* ); | ||
| void onItemRemoved( QgsComposerItem* ); | ||
|
|
||
| void updateGuiElements(); | ||
|
|
||
| private: | ||
| QgsAtlasComposition* mAtlas; | ||
| QgsComposition* mComposition; | ||
|
|
||
| void blockAllSignals( bool b ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,337 @@ | ||
| /*************************************************************************** | ||
| qgsatlascomposition.cpp | ||
| ----------------------- | ||
| begin : October 2012 | ||
| copyright : (C) 2005 by Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
|
|
||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
| #include <stdexcept> | ||
|
|
||
| #include "qgsatlascomposition.h" | ||
| #include "qgsvectorlayer.h" | ||
| #include "qgscomposermap.h" | ||
| #include "qgsvectordataprovider.h" | ||
| #include "qgsexpression.h" | ||
| #include "qgsgeometry.h" | ||
| #include "qgscomposerlabel.h" | ||
| #include "qgsmaplayerregistry.h" | ||
|
|
||
| QgsAtlasComposition::QgsAtlasComposition( QgsComposition* composition ) : | ||
| mComposition( composition ), | ||
| mEnabled( false ), | ||
| mComposerMap( 0 ), | ||
| mHideCoverage( false ), mFixedScale( false ), mMargin( 0.10 ), mFilenamePattern( "'output_'||$feature" ), | ||
| mCoverageLayer( 0 ), mSingleFile( false ) | ||
| { | ||
|
|
||
| // declare special columns with a default value | ||
| QgsExpression::setSpecialColumn( "$page", QVariant(( int )1 ) ); | ||
| QgsExpression::setSpecialColumn( "$feature", QVariant(( int )0 ) ); | ||
| QgsExpression::setSpecialColumn( "$numpages", QVariant(( int )1 ) ); | ||
| QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )0 ) ); | ||
| } | ||
|
|
||
| QgsAtlasComposition::~QgsAtlasComposition() | ||
| { | ||
| } | ||
|
|
||
| void QgsAtlasComposition::setCoverageLayer( QgsVectorLayer* layer ) | ||
| { | ||
| mCoverageLayer = layer; | ||
|
|
||
| if ( mCoverageLayer != 0 ) | ||
| { | ||
| // update the number of features | ||
| QgsVectorDataProvider* provider = mCoverageLayer->dataProvider(); | ||
| QgsExpression::setSpecialColumn( "$numfeatures", QVariant( (int)provider->featureCount() ) ); | ||
| } | ||
| } | ||
|
|
||
| void QgsAtlasComposition::beginRender() | ||
| { | ||
| if ( !mComposerMap || !mCoverageLayer ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| const QgsCoordinateReferenceSystem& coverage_crs = mCoverageLayer->crs(); | ||
| const QgsCoordinateReferenceSystem& destination_crs = mComposerMap->mapRenderer()->destinationCrs(); | ||
| // transformation needed for feature geometries | ||
| mTransform.setSourceCrs( coverage_crs ); | ||
| mTransform.setDestCRS( destination_crs ); | ||
|
|
||
| QgsVectorDataProvider* provider = mCoverageLayer->dataProvider(); | ||
|
|
||
| QgsFieldMap fieldmap = provider->fields(); | ||
|
|
||
| if ( mFilenamePattern.size() > 0 ) | ||
| { | ||
| mFilenameExpr = std::auto_ptr<QgsExpression>( new QgsExpression( mFilenamePattern ) ); | ||
| // expression used to evaluate each filename | ||
| // test for evaluation errors | ||
| if ( mFilenameExpr->hasParserError() ) | ||
| { | ||
| throw std::runtime_error( "Filename parsing error: " + mFilenameExpr->parserErrorString().toStdString() ); | ||
| } | ||
|
|
||
| // prepare the filename expression | ||
| mFilenameExpr->prepare( fieldmap ); | ||
| } | ||
|
|
||
| // select all features with all attributes | ||
| provider->select( provider->attributeIndexes() ); | ||
|
|
||
| // We cannot use nextFeature() directly since the feature pointer is rewinded by the rendering process | ||
| // We thus store the feature ids for future extraction | ||
| QgsFeature feat; | ||
| while ( provider->nextFeature( feat ) ) | ||
| { | ||
| mFeatureIds.push_back( feat.id() ); | ||
| } | ||
|
|
||
| mOrigExtent = mComposerMap->extent(); | ||
|
|
||
| mRestoreLayer = false; | ||
| QStringList& layerSet = mComposition->mapRenderer()->layerSet(); | ||
| if ( mHideCoverage ) | ||
| { | ||
| // look for the layer in the renderer's set | ||
| int removeAt = layerSet.indexOf( mCoverageLayer->id() ); | ||
| if ( removeAt != -1 ) | ||
| { | ||
| mRestoreLayer = true; | ||
| layerSet.removeAt( removeAt ); | ||
| } | ||
| } | ||
|
|
||
| // special columns for expressions | ||
| QgsExpression::setSpecialColumn( "$numpages", QVariant( mComposition->numPages() ) ); | ||
| QgsExpression::setSpecialColumn( "$numfeatures", QVariant(( int )provider->featureCount() ) ); | ||
| } | ||
|
|
||
| void QgsAtlasComposition::endRender() | ||
| { | ||
| if ( !mComposerMap || !mCoverageLayer ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| // reset label expression contexts | ||
| QList<QgsComposerLabel*> labels; | ||
| mComposition->composerItems( labels ); | ||
| for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit ) | ||
| { | ||
| ( *lit )->setExpressionContext( 0, 0 ); | ||
| } | ||
|
|
||
| // restore the coverage visibility | ||
| if ( mRestoreLayer ) | ||
| { | ||
| QStringList& layerSet = mComposition->mapRenderer()->layerSet(); | ||
|
|
||
| layerSet.push_back( mCoverageLayer->id() ); | ||
| mComposerMap->cache(); | ||
| mComposerMap->update(); | ||
| } | ||
| mComposerMap->setNewExtent( mOrigExtent ); | ||
| } | ||
|
|
||
| size_t QgsAtlasComposition::numFeatures() const | ||
| { | ||
| if ( mCoverageLayer ) | ||
| { | ||
| return mCoverageLayer->dataProvider()->featureCount(); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| void QgsAtlasComposition::prepareForFeature( size_t featureI ) | ||
| { | ||
| if ( !mComposerMap || !mCoverageLayer ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| QgsVectorDataProvider* provider = mCoverageLayer->dataProvider(); | ||
| // retrieve the next feature, based on its id | ||
| provider->featureAtId( mFeatureIds[ featureI ], mCurrentFeature, /* fetchGeometry = */ true, provider->attributeIndexes() ); | ||
|
|
||
| if ( mFilenamePattern.size() > 0 ) | ||
| { | ||
| QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) ); | ||
| QVariant filenameRes = mFilenameExpr->evaluate( &mCurrentFeature ); | ||
| if ( mFilenameExpr->hasEvalError() ) | ||
| { | ||
| throw std::runtime_error( "Filename eval error: " + mFilenameExpr->evalErrorString().toStdString() ); | ||
| } | ||
|
|
||
| mCurrentFilename = filenameRes.toString(); | ||
| } | ||
|
|
||
| // | ||
| // compute the new extent | ||
| // keep the original aspect ratio | ||
| // and apply a margin | ||
|
|
||
| // QgsGeometry::boundingBox is expressed in the geometry"s native CRS | ||
| // We have to transform the grometry to the destination CRS and ask for the bounding box | ||
| // Note: we cannot directly take the transformation of the bounding box, since transformations are not linear | ||
|
|
||
| QgsGeometry tgeom( *mCurrentFeature.geometry() ); | ||
| tgeom.transform( mTransform ); | ||
| QgsRectangle geom_rect = tgeom.boundingBox(); | ||
|
|
||
| double xa1 = geom_rect.xMinimum(); | ||
| double xa2 = geom_rect.xMaximum(); | ||
| double ya1 = geom_rect.yMinimum(); | ||
| double ya2 = geom_rect.yMaximum(); | ||
| QgsRectangle new_extent = geom_rect; | ||
|
|
||
| // restore the original extent | ||
| // (successive calls to setNewExtent tend to deform the original rectangle) | ||
| mComposerMap->setNewExtent( mOrigExtent ); | ||
|
|
||
| if ( mFixedScale ) | ||
| { | ||
| // only translate, keep the original scale (i.e. width x height) | ||
|
|
||
| double geom_center_x = ( xa1 + xa2 ) / 2.0; | ||
| double geom_center_y = ( ya1 + ya2 ) / 2.0; | ||
| double xx = geom_center_x - mOrigExtent.width() / 2.0; | ||
| double yy = geom_center_y - mOrigExtent.height() / 2.0; | ||
| new_extent = QgsRectangle( xx, | ||
| yy, | ||
| xx + mOrigExtent.width(), | ||
| yy + mOrigExtent.height() ); | ||
| } | ||
| else | ||
| { | ||
| // auto scale | ||
|
|
||
| double geom_ratio = geom_rect.width() / geom_rect.height(); | ||
| double map_ratio = mOrigExtent.width() / mOrigExtent.height(); | ||
|
|
||
| // geometry height is too big | ||
| if ( geom_ratio < map_ratio ) | ||
| { | ||
| new_extent = QgsRectangle(( xa1 + xa2 + map_ratio * ( ya1 - ya2 ) ) / 2.0, | ||
| ya1, | ||
| xa1 + map_ratio * ( ya2 - ya1 ), | ||
| ya2 ); | ||
| } | ||
| // geometry width is too big | ||
| else if ( geom_ratio > map_ratio ) | ||
| { | ||
| new_extent = QgsRectangle( xa1, | ||
| ( ya1 + ya2 + ( xa1 - xa2 ) / map_ratio ) / 2.0, | ||
| xa2, | ||
| ya1 + ( xa2 - xa1 ) / map_ratio ); | ||
| } | ||
| if ( mMargin > 0.0 ) | ||
| { | ||
| new_extent.scale( 1 + mMargin ); | ||
| } | ||
| } | ||
|
|
||
| // evaluate label expressions | ||
| QList<QgsComposerLabel*> labels; | ||
| mComposition->composerItems( labels ); | ||
| QgsExpression::setSpecialColumn( "$feature", QVariant(( int )featureI + 1 ) ); | ||
|
|
||
| for ( QList<QgsComposerLabel*>::iterator lit = labels.begin(); lit != labels.end(); ++lit ) | ||
| { | ||
| ( *lit )->setExpressionContext( &mCurrentFeature, mCoverageLayer ); | ||
| } | ||
|
|
||
| // set the new extent (and render) | ||
| mComposerMap->setNewExtent( new_extent ); | ||
| } | ||
|
|
||
| const QString& QgsAtlasComposition::currentFilename() const | ||
| { | ||
| return mCurrentFilename; | ||
| } | ||
|
|
||
| void QgsAtlasComposition::writeXML( QDomElement& elem, QDomDocument& doc ) const | ||
| { | ||
| QDomElement atlasElem = doc.createElement( "Atlas" ); | ||
| atlasElem.setAttribute( "enabled", mEnabled ? "true" : "false" ); | ||
| if ( !mEnabled ) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| if ( mCoverageLayer ) | ||
| { | ||
| atlasElem.setAttribute( "coverageLayer", mCoverageLayer->id() ); | ||
| } | ||
| else | ||
| { | ||
| atlasElem.setAttribute( "coverageLayer", "" ); | ||
| } | ||
| if ( mComposerMap ) | ||
| { | ||
| atlasElem.setAttribute( "composerMap", mComposerMap->id() ); | ||
| } | ||
| else | ||
| { | ||
| atlasElem.setAttribute( "composerMap", "" ); | ||
| } | ||
| atlasElem.setAttribute( "hideCoverage", mHideCoverage ? "true" : "false" ); | ||
| atlasElem.setAttribute( "fixedScale", mFixedScale ? "true" : "false" ); | ||
| atlasElem.setAttribute( "singleFile", mSingleFile ? "true" : "false" ); | ||
| atlasElem.setAttribute( "margin", QString::number(mMargin) ); | ||
| atlasElem.setAttribute( "filenamePattern", mFilenamePattern ); | ||
|
|
||
| elem.appendChild( atlasElem ); | ||
| } | ||
|
|
||
| void QgsAtlasComposition::readXML( const QDomElement& atlasElem, const QDomDocument& ) | ||
| { | ||
| mEnabled = atlasElem.attribute( "enabled", "false" ) == "true" ? true : false; | ||
| if ( !mEnabled ) | ||
| { | ||
| emit parameterChanged(); | ||
| return; | ||
| } | ||
|
|
||
| // look for stored layer name | ||
| mCoverageLayer = 0; | ||
| QMap<QString, QgsMapLayer*> layers = QgsMapLayerRegistry::instance()->mapLayers(); | ||
| for ( QMap<QString, QgsMapLayer*>::const_iterator it = layers.begin(); it != layers.end(); ++it ) | ||
| { | ||
| if ( it.key() == atlasElem.attribute("coverageLayer") ) | ||
| { | ||
| mCoverageLayer = dynamic_cast<QgsVectorLayer*>(it.value()); | ||
| break; | ||
| } | ||
| } | ||
| // look for stored composer map | ||
| mComposerMap = 0; | ||
| QList<const QgsComposerMap*> maps = mComposition->composerMapItems(); | ||
| for ( QList<const QgsComposerMap*>::const_iterator it = maps.begin(); it != maps.end(); ++it ) | ||
| { | ||
| if ( (*it)->id() == atlasElem.attribute( "composerMap" ).toInt() ) | ||
| { | ||
| mComposerMap = const_cast<QgsComposerMap*>( *it ); | ||
| break; | ||
| } | ||
| } | ||
| mMargin = atlasElem.attribute( "margin", "0.0" ).toDouble(); | ||
| mHideCoverage = atlasElem.attribute( "hideCoverage", "false" ) == "true" ? true : false; | ||
| mFixedScale = atlasElem.attribute( "fixedScale", "false" ) == "true" ? true : false; | ||
| mSingleFile = atlasElem.attribute( "singleFile", "false" ) == "true" ? true : false; | ||
| mFilenamePattern = atlasElem.attribute( "filenamePattern", "" ); | ||
|
|
||
| std::cout << "emit parameter changed this = " << this << std::endl; | ||
| emit parameterChanged(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| /*************************************************************************** | ||
| qgsatlascomposermap.h | ||
| --------------------- | ||
| begin : October 2012 | ||
| copyright : (C) 2005 by Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
| #ifndef QGSATLASCOMPOSITION_H | ||
| #define QGSATLASCOMPOSITION_H | ||
|
|
||
| #include "qgscoordinatetransform.h" | ||
| #include "qgsfeature.h" | ||
|
|
||
| #include <memory> | ||
| #include <QString> | ||
| #include <QDomElement> | ||
| #include <QDomDocument> | ||
|
|
||
| class QgsComposerMap; | ||
| class QgsComposition; | ||
| class QgsVectorLayer; | ||
| class QgsExpression; | ||
|
|
||
| /** \ingroup MapComposer | ||
| * Class used to render an Atlas, iterating over geometry features. | ||
| * prepareForFeature() modifies the atlas map's extent to zoom on the given feature. | ||
| * This class is used for printing, exporting to PDF and images. | ||
| * */ | ||
| class CORE_EXPORT QgsAtlasComposition : public QObject | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| QgsAtlasComposition( QgsComposition* composition ); | ||
| ~QgsAtlasComposition(); | ||
|
|
||
| /** Is the atlas generation enabled ? */ | ||
| bool enabled() const { return mEnabled; } | ||
| void setEnabled( bool e ) { mEnabled = e; } | ||
|
|
||
| QgsComposerMap* composerMap() const { return mComposerMap; } | ||
| void setComposerMap( QgsComposerMap* map ) { mComposerMap = map; } | ||
|
|
||
| bool hideCoverage() const { return mHideCoverage; } | ||
| void setHideCoverage( bool hide ) { mHideCoverage = hide; } | ||
|
|
||
| bool fixedScale() const { return mFixedScale; } | ||
| void setFixedScale( bool fixed ) { mFixedScale = fixed; } | ||
|
|
||
| float margin() const { return mMargin; } | ||
| void setMargin( float margin ) { mMargin = margin; } | ||
|
|
||
| QString filenamePattern() const { return mFilenamePattern; } | ||
| void setFilenamePattern( const QString& pattern ) { mFilenamePattern = pattern; } | ||
|
|
||
| QgsVectorLayer* coverageLayer() const { return mCoverageLayer; } | ||
| void setCoverageLayer( QgsVectorLayer* lmap ); | ||
|
|
||
| bool singleFile() const { return mSingleFile; } | ||
| void setSingleFile( bool single ) { mSingleFile = single; } | ||
|
|
||
| /** Begins the rendering. */ | ||
| void beginRender(); | ||
| /** Ends the rendering. Restores original extent */ | ||
| void endRender(); | ||
|
|
||
| /** Returns the number of features in the coverage layer */ | ||
| size_t numFeatures() const; | ||
|
|
||
| /** Prepare the atlas map for the given feature. Sets the extent and context variables */ | ||
| void prepareForFeature( size_t i ); | ||
|
|
||
| /** Returns the current filename. Must be called after prepareForFeature( i ) */ | ||
| const QString& currentFilename() const; | ||
|
|
||
| void writeXML( QDomElement& elem, QDomDocument& doc ) const; | ||
| void readXML( const QDomElement& elem, const QDomDocument& doc ); | ||
|
|
||
| QgsComposition* composition() { return mComposition; } | ||
|
|
||
| signals: | ||
| /** emitted when one of the parameters changes */ | ||
| void parameterChanged(); | ||
|
|
||
| private: | ||
| QgsComposition* mComposition; | ||
|
|
||
| bool mEnabled; | ||
| QgsComposerMap* mComposerMap; | ||
| bool mHideCoverage; | ||
| bool mFixedScale; | ||
| double mMargin; | ||
| QString mFilenamePattern; | ||
| QgsVectorLayer* mCoverageLayer; | ||
| bool mSingleFile; | ||
|
|
||
| QgsCoordinateTransform mTransform; | ||
| QString mCurrentFilename; | ||
| std::vector<QgsFeatureId> mFeatureIds; | ||
| QgsFeature mCurrentFeature; | ||
| QgsRectangle mOrigExtent; | ||
| bool mRestoreLayer; | ||
| std::auto_ptr<QgsExpression> mFilenameExpr; | ||
| }; | ||
|
|
||
| #endif | ||
|
|
||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,198 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <ui version="4.0"> | ||
| <class>QgsAtlasCompositionWidgetBase</class> | ||
| <widget class="QWidget" name="QgsAtlasCompositionWidgetBase"> | ||
| <property name="geometry"> | ||
| <rect> | ||
| <x>0</x> | ||
| <y>0</y> | ||
| <width>450</width> | ||
| <height>501</height> | ||
| </rect> | ||
| </property> | ||
| <property name="sizePolicy"> | ||
| <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> | ||
| <horstretch>0</horstretch> | ||
| <verstretch>0</verstretch> | ||
| </sizepolicy> | ||
| </property> | ||
| <property name="windowTitle"> | ||
| <string>Atlas generation</string> | ||
| </property> | ||
| <layout class="QGridLayout" name="gridLayout_8"> | ||
| <property name="margin"> | ||
| <number>0</number> | ||
| </property> | ||
| <item row="0" column="0"> | ||
| <widget class="QToolBox" name="toolBox"> | ||
| <property name="lineWidth"> | ||
| <number>0</number> | ||
| </property> | ||
| <property name="currentIndex"> | ||
| <number>0</number> | ||
| </property> | ||
| <widget class="QWidget" name="page_4"> | ||
| <property name="geometry"> | ||
| <rect> | ||
| <x>0</x> | ||
| <y>0</y> | ||
| <width>450</width> | ||
| <height>470</height> | ||
| </rect> | ||
| </property> | ||
| <attribute name="label"> | ||
| <string>Atlas options</string> | ||
| </attribute> | ||
| <layout class="QGridLayout" name="gridLayout_9"> | ||
| <item row="2" column="0"> | ||
| <widget class="QFrame" name="mAtlasFrame"> | ||
| <property name="enabled"> | ||
| <bool>false</bool> | ||
| </property> | ||
| <property name="sizePolicy"> | ||
| <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> | ||
| <horstretch>0</horstretch> | ||
| <verstretch>1</verstretch> | ||
| </sizepolicy> | ||
| </property> | ||
| <property name="autoFillBackground"> | ||
| <bool>false</bool> | ||
| </property> | ||
| <property name="frameShape"> | ||
| <enum>QFrame::NoFrame</enum> | ||
| </property> | ||
| <property name="frameShadow"> | ||
| <enum>QFrame::Raised</enum> | ||
| </property> | ||
| <property name="lineWidth"> | ||
| <number>0</number> | ||
| </property> | ||
| <layout class="QVBoxLayout" name="verticalLayout"> | ||
| <property name="spacing"> | ||
| <number>6</number> | ||
| </property> | ||
| <property name="margin"> | ||
| <number>0</number> | ||
| </property> | ||
| <item> | ||
| <layout class="QGridLayout" name="gridLayout_7" rowstretch="0,0,0,0,0,0,0,0,0" columnstretch="0,0,0"> | ||
| <item row="2" column="0" colspan="2"> | ||
| <widget class="QCheckBox" name="mAtlasHideCoverageCheckBox"> | ||
| <property name="toolTip"> | ||
| <string>Hide the coverage layer when generating the output</string> | ||
| </property> | ||
| <property name="text"> | ||
| <string>Hidden coverage layer</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="4" column="0"> | ||
| <widget class="QLabel" name="label"> | ||
| <property name="text"> | ||
| <string>Margin around coverage</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="7" column="0"> | ||
| <widget class="QLabel" name="label_5"> | ||
| <property name="text"> | ||
| <string>Output filename expression</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="4" column="1"> | ||
| <widget class="QSpinBox" name="mAtlasMarginSpinBox"> | ||
| <property name="suffix"> | ||
| <string> %</string> | ||
| </property> | ||
| <property name="maximum"> | ||
| <number>100</number> | ||
| </property> | ||
| <property name="value"> | ||
| <number>10</number> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="7" column="2"> | ||
| <widget class="QToolButton" name="mAtlasFilenameExpressionButton"> | ||
| <property name="text"> | ||
| <string>...</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="7" column="1"> | ||
| <widget class="QLineEdit" name="mAtlasFilenamePatternEdit"/> | ||
| </item> | ||
| <item row="1" column="0"> | ||
| <widget class="QLabel" name="label_6"> | ||
| <property name="text"> | ||
| <string>Coverage layer</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="1" column="1"> | ||
| <widget class="QComboBox" name="mAtlasCoverageLayerComboBox"> | ||
| <property name="contextMenuPolicy"> | ||
| <enum>Qt::NoContextMenu</enum> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="5" column="0" colspan="2"> | ||
| <widget class="QCheckBox" name="mAtlasFixedScaleCheckBox"> | ||
| <property name="text"> | ||
| <string>Fixed scale</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="8" column="0" colspan="2"> | ||
| <widget class="QCheckBox" name="mAtlasSingleFileCheckBox"> | ||
| <property name="text"> | ||
| <string>Single file export when possible</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| <item row="0" column="1"> | ||
| <widget class="QComboBox" name="mComposerMapComboBox"/> | ||
| </item> | ||
| <item row="0" column="0"> | ||
| <widget class="QLabel" name="label_2"> | ||
| <property name="text"> | ||
| <string>Composer map to use</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| </layout> | ||
| </item> | ||
| </layout> | ||
| </widget> | ||
| </item> | ||
| <item row="5" column="0"> | ||
| <spacer name="verticalSpacer_5"> | ||
| <property name="orientation"> | ||
| <enum>Qt::Vertical</enum> | ||
| </property> | ||
| <property name="sizeHint" stdset="0"> | ||
| <size> | ||
| <width>20</width> | ||
| <height>76</height> | ||
| </size> | ||
| </property> | ||
| </spacer> | ||
| </item> | ||
| <item row="0" column="0"> | ||
| <widget class="QCheckBox" name="mUseAtlasCheckBox"> | ||
| <property name="text"> | ||
| <string>Generate an atlas</string> | ||
| </property> | ||
| </widget> | ||
| </item> | ||
| </layout> | ||
| </widget> | ||
| </widget> | ||
| </item> | ||
| </layout> | ||
| </widget> | ||
| <layoutdefault spacing="6" margin="11"/> | ||
| <resources/> | ||
| <connections/> | ||
| </ui> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| /*************************************************************************** | ||
| testqgsatlascomposition.cpp | ||
| --------------------------- | ||
| begin : Sept 2012 | ||
| copyright : (C) 2012 by Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
|
|
||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
|
|
||
| #include "qgsapplication.h" | ||
| #include "qgscomposition.h" | ||
| #include "qgscompositionchecker.h" | ||
| #include "qgscomposermap.h" | ||
| #include "qgsatlascomposition.h" | ||
| #include "qgscomposerlabel.h" | ||
| #include "qgsmaplayerregistry.h" | ||
| #include "qgsmaprenderer.h" | ||
| #include "qgsvectorlayer.h" | ||
| #include "qgsvectordataprovider.h" | ||
| #include "qgssymbolv2.h" | ||
| #include "qgssinglesymbolrendererv2.h" | ||
| #include <QObject> | ||
| #include <QtTest> | ||
|
|
||
| class TestQgsAtlasComposition: public QObject | ||
| { | ||
| Q_OBJECT; | ||
| private slots: | ||
| void initTestCase();// will be called before the first testfunction is executed. | ||
| void cleanupTestCase();// will be called after the last testfunction was executed. | ||
| void init();// will be called before each testfunction is executed. | ||
| void cleanup();// will be called after every testfunction. | ||
|
|
||
| // test filename pattern evaluation | ||
| void filename(); | ||
| // test rendering with an autoscale atlas | ||
| void autoscale_render(); | ||
| // test rendering with a fixed scale atlas | ||
| void fixedscale_render(); | ||
| // test rendering with a hidden coverage | ||
| void hiding_render(); | ||
| private: | ||
| QgsComposition* mComposition; | ||
| QgsComposerLabel* mLabel1; | ||
| QgsComposerLabel* mLabel2; | ||
| QgsComposerMap* mAtlasMap; | ||
| QgsComposerMap* mOverview; | ||
| QgsMapRenderer* mMapRenderer; | ||
| QgsVectorLayer* mVectorLayer; | ||
| QgsAtlasComposition* mAtlas; | ||
| }; | ||
|
|
||
| void TestQgsAtlasComposition::initTestCase() | ||
| { | ||
| QgsApplication::init(); | ||
| QgsApplication::initQgis(); | ||
|
|
||
| //create maplayers from testdata and add to layer registry | ||
| QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + QDir::separator() + "france_parts.shp" ); | ||
| mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(), | ||
| vectorFileInfo.completeBaseName(), | ||
| "ogr" ); | ||
|
|
||
| QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << mVectorLayer ); | ||
|
|
||
| //create composition with composer map | ||
| mMapRenderer = new QgsMapRenderer(); | ||
| mMapRenderer->setLayerSet( QStringList() << mVectorLayer->id() ); | ||
| mMapRenderer->setProjectionsEnabled( true ); | ||
|
|
||
| // select epsg:2154 | ||
| QgsCoordinateReferenceSystem crs; | ||
| crs.createFromSrid( 2154 ); | ||
| mMapRenderer->setDestinationCrs( crs ); | ||
| mComposition = new QgsComposition( mMapRenderer ); | ||
| mComposition->setPaperSize( 297, 210 ); //A4 landscape | ||
|
|
||
| // fix the renderer, fill with green | ||
| QgsStringMap props; | ||
| props.insert( "color", "0,127,0" ); | ||
| QgsFillSymbolV2* fillSymbol = QgsFillSymbolV2::createSimple( props ); | ||
| QgsSingleSymbolRendererV2* renderer = new QgsSingleSymbolRendererV2( fillSymbol ); | ||
| mVectorLayer->setRendererV2( renderer ); | ||
|
|
||
| // the atlas map | ||
| mAtlasMap = new QgsComposerMap( mComposition, 20, 20, 130, 130 ); | ||
| mAtlasMap->setFrameEnabled( true ); | ||
| mComposition->addComposerMap( mAtlasMap ); | ||
|
|
||
| mAtlas = new QgsAtlasComposition( mComposition ); | ||
| mAtlas->setCoverageLayer( mVectorLayer ); | ||
| mAtlas->setComposerMap( mAtlasMap ); | ||
|
|
||
| // an overview | ||
| mOverview = new QgsComposerMap( mComposition, 180, 20, 50, 50 ); | ||
| mOverview->setFrameEnabled( true ); | ||
| mOverview->setOverviewFrameMap( mAtlasMap->id() ); | ||
| mComposition->addComposerMap( mOverview ); | ||
| mOverview->setNewExtent( QgsRectangle( 49670.718, 6415139.086, 699672.519, 7065140.887 ) ); | ||
|
|
||
| // set the fill symbol of the overview map | ||
| QgsStringMap props2; | ||
| props2.insert( "color", "127,0,0,127" ); | ||
| QgsFillSymbolV2* fillSymbol2 = QgsFillSymbolV2::createSimple( props2 ); | ||
| mOverview->setOverviewFrameMapSymbol( fillSymbol2 ); | ||
|
|
||
| // header label | ||
| mLabel1 = new QgsComposerLabel( mComposition ); | ||
| mComposition->addComposerLabel( mLabel1 ); | ||
| mLabel1->setText( "[% \"NAME_1\" %] area" ); | ||
| mLabel1->adjustSizeToText(); | ||
| mLabel1->setItemPosition( 150, 5 ); | ||
|
|
||
| // feature number label | ||
| mLabel2 = new QgsComposerLabel( mComposition ); | ||
| mComposition->addComposerLabel( mLabel2 ); | ||
| mLabel2->setText( "# [%$feature || ' / ' || $numfeatures%]" ); | ||
| mLabel2->adjustSizeToText(); | ||
| mLabel2->setItemPosition( 150, 200 ); | ||
| } | ||
|
|
||
| void TestQgsAtlasComposition::cleanupTestCase() | ||
| { | ||
| delete mComposition; | ||
| delete mMapRenderer; | ||
| delete mVectorLayer; | ||
| } | ||
|
|
||
| void TestQgsAtlasComposition::init() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| void TestQgsAtlasComposition::cleanup() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| void TestQgsAtlasComposition::filename() | ||
| { | ||
| mAtlas->setFilenamePattern( "'output_' || $feature" ); | ||
| mAtlas->beginRender(); | ||
| for ( size_t fi = 0; fi < mAtlas->numFeatures(); ++fi ) | ||
| { | ||
| mAtlas->prepareForFeature( fi ); | ||
| QString expected = QString( "output_%1" ).arg( (int)(fi+1) ); | ||
| QCOMPARE( mAtlas->currentFilename(), expected ); | ||
| } | ||
| mAtlas->endRender(); | ||
| } | ||
|
|
||
|
|
||
| void TestQgsAtlasComposition::autoscale_render() | ||
| { | ||
| mAtlas->setFixedScale( false ); | ||
| mAtlas->setMargin( 0.10 ); | ||
|
|
||
| mAtlas->beginRender(); | ||
|
|
||
| for ( size_t fit = 0; fit < 2; ++fit ) | ||
| { | ||
| mAtlas->prepareForFeature( fit ); | ||
| mLabel1->adjustSizeToText(); | ||
|
|
||
| QgsCompositionChecker checker( "Atlas autoscale test", mComposition, | ||
| QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() + | ||
| "expected_composermapatlas" + QDir::separator() + | ||
| QString( "autoscale_%1.png" ).arg((int)fit) ); | ||
| QVERIFY( checker.testComposition( 0 ) ); | ||
| } | ||
| mAtlas->endRender(); | ||
| } | ||
|
|
||
| void TestQgsAtlasComposition::fixedscale_render() | ||
| { | ||
| mAtlasMap->setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) ); | ||
| mAtlas->setFixedScale( true ); | ||
|
|
||
| mAtlas->beginRender(); | ||
|
|
||
| for ( size_t fit = 0; fit < 2; ++fit ) | ||
| { | ||
| mAtlas->prepareForFeature( fit ); | ||
| mLabel1->adjustSizeToText(); | ||
|
|
||
| QgsCompositionChecker checker( "Atlas fixedscale test", mComposition, | ||
| QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() + | ||
| "expected_composermapatlas" + QDir::separator() + | ||
| QString( "fixedscale_%1.png" ).arg((int)fit) ); | ||
| QVERIFY( checker.testComposition( 0 ) ); | ||
| } | ||
| mAtlas->endRender(); | ||
|
|
||
| } | ||
|
|
||
| void TestQgsAtlasComposition::hiding_render() | ||
| { | ||
| mAtlasMap->setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) ); | ||
| mAtlas->setFixedScale( true ); | ||
| mAtlas->setHideCoverage( true ); | ||
|
|
||
| mAtlas->beginRender(); | ||
|
|
||
| for ( size_t fit = 0; fit < 2; ++fit ) | ||
| { | ||
| mAtlas->prepareForFeature( fit ); | ||
| mLabel1->adjustSizeToText(); | ||
|
|
||
| QgsCompositionChecker checker( "Atlas hidden test", mComposition, | ||
| QString( TEST_DATA_DIR ) + QDir::separator() + "control_images" + QDir::separator() + | ||
| "expected_composermapatlas" + QDir::separator() + | ||
| QString( "hiding_%1.png" ).arg((int)fit) ); | ||
| QVERIFY( checker.testComposition( 0 ) ); | ||
| } | ||
| mAtlas->endRender(); | ||
| } | ||
|
|
||
| QTEST_MAIN( TestQgsAtlasComposition ) | ||
| #include "moc_testqgsatlascomposition.cxx" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| /*************************************************************************** | ||
| testqgscomposerlabel.cpp | ||
| ---------------------- | ||
| begin : Sept 2012 | ||
| copyright : (C) 2012 by Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| ***************************************************************************/ | ||
|
|
||
| /*************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
|
|
||
| #include "qgsapplication.h" | ||
| #include "qgscomposition.h" | ||
| #include "qgscomposerlabel.h" | ||
| #include "qgsmaplayerregistry.h" | ||
| #include "qgsmaprenderer.h" | ||
| #include "qgsvectorlayer.h" | ||
| #include "qgsvectordataprovider.h" | ||
| #include <QObject> | ||
| #include <QtTest> | ||
|
|
||
| class TestQgsComposerLabel: public QObject | ||
| { | ||
| Q_OBJECT; | ||
| private slots: | ||
| void initTestCase();// will be called before the first testfunction is executed. | ||
| void cleanupTestCase();// will be called after the last testfunction was executed. | ||
| void init();// will be called before each testfunction is executed. | ||
| void cleanup();// will be called after every testfunction. | ||
|
|
||
| // test simple expression evaluation | ||
| void evaluation(); | ||
| // test expression evaluation when a feature is set | ||
| void feature_evaluation(); | ||
| // test "$page" expressions | ||
| void page_evaluation(); | ||
| private: | ||
| QgsComposition* mComposition; | ||
| QgsComposerLabel* mComposerLabel; | ||
| QgsMapRenderer* mMapRenderer; | ||
| QgsVectorLayer* mVectorLayer; | ||
| }; | ||
|
|
||
| void TestQgsComposerLabel::initTestCase() | ||
| { | ||
| QgsApplication::init(); | ||
| QgsApplication::initQgis(); | ||
|
|
||
| //create maplayers from testdata and add to layer registry | ||
| QFileInfo vectorFileInfo( QString( TEST_DATA_DIR ) + QDir::separator() + "france_parts.shp" ); | ||
| mVectorLayer = new QgsVectorLayer( vectorFileInfo.filePath(), | ||
| vectorFileInfo.completeBaseName(), | ||
| "ogr" ); | ||
| QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << mVectorLayer ); | ||
|
|
||
| //create composition with composer map | ||
| mMapRenderer = new QgsMapRenderer(); | ||
| mMapRenderer->setLayerSet( QStringList() << mVectorLayer->id() ); | ||
| mMapRenderer->setProjectionsEnabled( false ); | ||
| mComposition = new QgsComposition( mMapRenderer ); | ||
| mComposition->setPaperSize( 297, 210 ); //A4 landscape | ||
|
|
||
| mComposerLabel = new QgsComposerLabel( mComposition ); | ||
| mComposition->addComposerLabel( mComposerLabel ); | ||
| } | ||
|
|
||
| void TestQgsComposerLabel::cleanupTestCase() | ||
| { | ||
| delete mComposition; | ||
| delete mMapRenderer; | ||
| delete mVectorLayer; | ||
| } | ||
|
|
||
| void TestQgsComposerLabel::init() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| void TestQgsComposerLabel::cleanup() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| void TestQgsComposerLabel::evaluation() | ||
| { | ||
| { | ||
| // $CURRENT_DATE evaluation | ||
| QString expected = "__" + QDate::currentDate().toString() + "__"; | ||
| mComposerLabel->setText( "__$CURRENT_DATE__" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| { | ||
| // $CURRENT_DATE() evaluation | ||
| QDateTime now = QDateTime::currentDateTime(); | ||
| QString expected = "__" + now.toString( "dd" ) + "(ok)__"; | ||
| mComposerLabel->setText( "__$CURRENT_DATE(dd)(ok)__" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| { | ||
| // $CURRENT_DATE() evaluation (inside an expression) | ||
| QDate now = QDate::currentDate(); | ||
| int dd = now.day(); | ||
|
|
||
| QString expected = "__" + QString("%1").arg(dd+1) + "(ok)__"; | ||
| mComposerLabel->setText( "__[%$CURRENT_DATE(dd) + 1%](ok)__" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| { | ||
| // expression evaluation (without feature) | ||
| QString expected = "__[NAME_1]42__"; | ||
| mComposerLabel->setText( "__[%\"NAME_1\"%][%21*2%]__" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| } | ||
|
|
||
| void TestQgsComposerLabel::feature_evaluation() | ||
| { | ||
| QgsVectorDataProvider* provider = mVectorLayer->dataProvider(); | ||
|
|
||
| QgsAttributeList allAttrs = provider->attributeIndexes(); | ||
| provider->select( allAttrs ); | ||
| QgsFeature feat; | ||
|
|
||
| provider->nextFeature( feat ); | ||
| { | ||
| // evaluation with a feature | ||
| mComposerLabel->setExpressionContext( &feat, mVectorLayer ); | ||
| mComposerLabel->setText( "[%\"NAME_1\"||'_ok'%]" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QString expected = "Basse-Normandie_ok"; | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| provider->nextFeature( feat ); | ||
| { | ||
| // evaluation with a feature | ||
| mComposerLabel->setExpressionContext( &feat, mVectorLayer ); | ||
| mComposerLabel->setText( "[%\"NAME_1\"||'_ok'%]" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QString expected = "Bretagne_ok"; | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| { | ||
| // evaluation with a feature and local variables | ||
| QMap<QString, QVariant> locals; | ||
| locals.insert( "$test", "OK" ); | ||
|
|
||
| mComposerLabel->setExpressionContext( &feat, mVectorLayer, locals ); | ||
| mComposerLabel->setText( "[%\"NAME_1\"||$test%]" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QString expected = "BretagneOK"; | ||
| QCOMPARE( evaluated, expected ); | ||
| } | ||
| } | ||
|
|
||
| void TestQgsComposerLabel::page_evaluation() | ||
| { | ||
| mComposition->setNumPages( 2 ); | ||
| { | ||
| mComposerLabel->setText( "[%$page||'/'||$numpages%]" ); | ||
| QString evaluated = mComposerLabel->displayText(); | ||
| QString expected = "1/2"; | ||
| QCOMPARE( evaluated, expected ); | ||
|
|
||
| // move to the second page and re-evaluate | ||
| mComposerLabel->setItemPosition( 0, 320 ); | ||
| QCOMPARE( mComposerLabel->displayText(), QString("2/2") ); | ||
| } | ||
| } | ||
|
|
||
| QTEST_MAIN( TestQgsComposerLabel ) | ||
| #include "moc_testqgscomposerlabel.cxx" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,169 @@ | ||
| # -*- coding: utf-8 -*- | ||
| ''' | ||
| test_qgsatlascomposition.py | ||
| -------------------------------------- | ||
| Date : Oct 2012 | ||
| Copyright : (C) 2012 by Dr. Hugo Mercier | ||
| email : hugo dot mercier at oslandia dot com | ||
| *************************************************************************** | ||
| * * | ||
| * This program is free software; you can redistribute it and/or modify * | ||
| * it under the terms of the GNU General Public License as published by * | ||
| * the Free Software Foundation; either version 2 of the License, or * | ||
| * (at your option) any later version. * | ||
| * * | ||
| ***************************************************************************/ | ||
| ''' | ||
| import unittest | ||
| from utilities import * | ||
| from PyQt4.QtCore import * | ||
| from PyQt4.QtGui import * | ||
| from PyQt4.QtXml import * | ||
| from qgis.core import * | ||
| from qgscompositionchecker import QgsCompositionChecker | ||
|
|
||
| QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp() | ||
|
|
||
| class TestQgsAtlasComposition(unittest.TestCase): | ||
|
|
||
| def testCase(self): | ||
| self.TEST_DATA_DIR = unitTestDataPath() | ||
| vectorFileInfo = QFileInfo( self.TEST_DATA_DIR + QDir().separator().toAscii() + "france_parts.shp") | ||
| mVectorLayer = QgsVectorLayer( vectorFileInfo.filePath(), vectorFileInfo.completeBaseName(), "ogr" ) | ||
|
|
||
| QgsMapLayerRegistry.instance().addMapLayer( mVectorLayer ) | ||
|
|
||
| # create composition with composer map | ||
| mMapRenderer = QgsMapRenderer() | ||
| layerStringList = QStringList() | ||
| layerStringList.append( mVectorLayer.id() ) | ||
| mMapRenderer.setLayerSet( layerStringList ) | ||
| mMapRenderer.setProjectionsEnabled( True ) | ||
|
|
||
| # select epsg:2154 | ||
| crs = QgsCoordinateReferenceSystem() | ||
| crs.createFromSrid( 2154 ) | ||
| mMapRenderer.setDestinationCrs( crs ) | ||
|
|
||
| self.mComposition = QgsComposition( mMapRenderer ) | ||
| self.mComposition.setPaperSize( 297, 210 ) | ||
|
|
||
| # fix the renderer, fill with green | ||
| props = { "color": "0,127,0" } | ||
| fillSymbol = QgsFillSymbolV2.createSimple( props ) | ||
| renderer = QgsSingleSymbolRendererV2( fillSymbol ) | ||
| mVectorLayer.setRendererV2( renderer ) | ||
|
|
||
| # the atlas map | ||
| self.mAtlasMap = QgsComposerMap( self.mComposition, 20, 20, 130, 130 ) | ||
| self.mAtlasMap.setFrameEnabled( True ) | ||
| self.mComposition.addComposerMap( self.mAtlasMap ) | ||
|
|
||
| # the atlas | ||
| self.mAtlas = QgsAtlasComposition( self.mComposition ) | ||
| self.mAtlas.setCoverageLayer( mVectorLayer ) | ||
| self.mAtlas.setComposerMap( self.mAtlasMap ) | ||
|
|
||
| # an overview | ||
| mOverview = QgsComposerMap( self.mComposition, 180, 20, 50, 50 ) | ||
| mOverview.setFrameEnabled( True ) | ||
| mOverview.setOverviewFrameMap( self.mAtlasMap.id() ) | ||
| self.mComposition.addComposerMap( mOverview ) | ||
| nextent = QgsRectangle( 49670.718, 6415139.086, 699672.519, 7065140.887 ) | ||
| mOverview.setNewExtent( nextent ) | ||
|
|
||
| # set the fill symbol of the overview map | ||
| props2 = { "color": "127,0,0,127" } | ||
| fillSymbol2 = QgsFillSymbolV2.createSimple( props2 ) | ||
| mOverview.setOverviewFrameMapSymbol( fillSymbol2 ); | ||
|
|
||
| # header label | ||
| self.mLabel1 = QgsComposerLabel( self.mComposition ) | ||
| self.mComposition.addComposerLabel( self.mLabel1 ) | ||
| self.mLabel1.setText( "[% \"NAME_1\" %] area" ) | ||
| self.mLabel1.adjustSizeToText() | ||
| self.mLabel1.setItemPosition( 150, 5 ) | ||
|
|
||
| # feature number label | ||
| self.mLabel2 = QgsComposerLabel( self.mComposition ) | ||
| self.mComposition.addComposerLabel( self.mLabel2 ) | ||
| self.mLabel2.setText( "# [%$feature || ' / ' || $numfeatures%]" ) | ||
| self.mLabel2.adjustSizeToText() | ||
| self.mLabel2.setItemPosition( 150, 200 ) | ||
|
|
||
| self.filename_test() | ||
| self.autoscale_render_test() | ||
| self.fixedscale_render_test() | ||
| self.hidden_render_test() | ||
|
|
||
| def filename_test( self ): | ||
|
|
||
| self.mAtlas.setFilenamePattern( "'output_' || $feature" ) | ||
| self.mAtlas.beginRender() | ||
| for i in range(0, self.mAtlas.numFeatures()): | ||
| self.mAtlas.prepareForFeature( i ) | ||
| expected = QString( "output_%1" ).arg(i+1) | ||
| assert self.mAtlas.currentFilename() == expected | ||
| self.mAtlas.endRender() | ||
|
|
||
| def autoscale_render_test( self ): | ||
| self.mAtlas.setFixedScale( False ) | ||
| self.mAtlas.setMargin( 0.10 ) | ||
|
|
||
| self.mAtlas.beginRender() | ||
|
|
||
| for i in range(0, 2): | ||
| self.mAtlas.prepareForFeature( i ) | ||
| self.mLabel1.adjustSizeToText() | ||
|
|
||
| checker = QgsCompositionChecker() | ||
| res = checker.testComposition( "Atlas autoscale test", self.mComposition, \ | ||
| QString( self.TEST_DATA_DIR ) + QDir.separator() + \ | ||
| "control_images" + QDir.separator() + \ | ||
| "expected_composermapatlas" + QDir.separator() + \ | ||
| QString( "autoscale_%1.png" ).arg( i ) ) | ||
| assert res[0] == True | ||
| self.mAtlas.endRender() | ||
|
|
||
| def fixedscale_render_test( self ): | ||
| self.mAtlasMap.setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) ); | ||
| self.mAtlas.setFixedScale( True ) | ||
|
|
||
| self.mAtlas.beginRender() | ||
|
|
||
| for i in range(0, 2): | ||
| self.mAtlas.prepareForFeature( i ) | ||
| self.mLabel1.adjustSizeToText() | ||
|
|
||
| checker = QgsCompositionChecker() | ||
| res = checker.testComposition( "Atlas fixed scale test", self.mComposition, \ | ||
| QString( self.TEST_DATA_DIR ) + QDir.separator() + \ | ||
| "control_images" + QDir.separator() + \ | ||
| "expected_composermapatlas" + QDir.separator() + \ | ||
| QString( "fixedscale_%1.png" ).arg( i ) ) | ||
| assert res[0] == True | ||
| self.mAtlas.endRender() | ||
|
|
||
| def hidden_render_test( self ): | ||
| self.mAtlasMap.setNewExtent( QgsRectangle( 209838.166, 6528781.020, 610491.166, 6920530.620 ) ); | ||
| self.mAtlas.setFixedScale( True ) | ||
| self.mAtlas.setHideCoverage( True ) | ||
|
|
||
| self.mAtlas.beginRender() | ||
|
|
||
| for i in range(0, 2): | ||
| self.mAtlas.prepareForFeature( i ) | ||
| self.mLabel1.adjustSizeToText() | ||
|
|
||
| checker = QgsCompositionChecker() | ||
| res = checker.testComposition( "Atlas hidden test", self.mComposition, \ | ||
| QString( self.TEST_DATA_DIR ) + QDir.separator() + \ | ||
| "control_images" + QDir.separator() + \ | ||
| "expected_composermapatlas" + QDir.separator() + \ | ||
| QString( "hiding_%1.png" ).arg( i ) ) | ||
| assert res[0] == True | ||
| self.mAtlas.endRender() | ||
|
|
||
| if __name__ == '__main__': | ||
| unittest.main() | ||
|
|