From 387c88de24a3363f8c29be4706a4fb1f84473f73 Mon Sep 17 00:00:00 2001 From: Hugo Mercier Date: Wed, 12 Jun 2013 13:49:58 +0200 Subject: [PATCH] Add world file generation option. Work funded by Tuscany Region - SITA. Contract Support to the use of GFOSS \(Geographic Free and Open Source Software\) Desktop tools \(CIG Z3B06FA6D7\) --- src/app/composer/qgscomposer.cpp | 68 +++++++++++++++++ src/app/composer/qgscomposer.h | 3 + src/app/composer/qgscompositionwidget.cpp | 89 +++++++++++++++++++++++ src/app/composer/qgscompositionwidget.h | 10 +++ src/core/composer/qgscomposition.cpp | 73 +++++++++++++++++++ src/core/composer/qgscomposition.h | 14 ++++ src/ui/qgscompositionwidgetbase.ui | 21 +++++- 7 files changed, 276 insertions(+), 2 deletions(-) diff --git a/src/app/composer/qgscomposer.cpp b/src/app/composer/qgscomposer.cpp index ac40aaa9588c..bcd5ff621e07 100644 --- a/src/app/composer/qgscomposer.cpp +++ b/src/app/composer/qgscomposer.cpp @@ -995,6 +995,22 @@ void QgsComposer::on_mActionExportAsImage_triggered() image.save( outputFilePath, fileNExt.second.toLocal8Bit().constData() ); } } + + // + // Write the world file if asked to + if ( mComposition->generateWorldFile() ) + { + double params[6]; + mComposition->computeWorldFileParameters( params ); + + QFileInfo fi( fileNExt.first ); + // build the world file name + QString worldFileName = fi.absolutePath() + "/" + fi.baseName() + "." + + fi.suffix()[0] + fi.suffix()[fi.suffix().size()-1] + "w"; + + writeWorldFile( worldFileName, params ); + } + mView->setPaintingEnabled( true ); } else @@ -1137,6 +1153,21 @@ void QgsComposer::on_mActionExportAsImage_triggered() image.save( outputFilePath, format.toLocal8Bit().constData() ); } } + + // + // Write the world file if asked to + if ( mComposition->generateWorldFile() ) + { + double params[6]; + mComposition->computeWorldFileParameters( params ); + + QFileInfo fi( filename ); + // build the world file name + QString worldFileName = fi.absolutePath() + "/" + fi.baseName() + "." + + fi.suffix()[0] + fi.suffix()[fi.suffix().size()-1] + "w"; + + writeWorldFile( worldFileName, params ); + } } atlasMap->endRender(); mView->setPaintingEnabled( true ); @@ -1881,6 +1912,24 @@ void QgsComposer::readXML( const QDomElement& composerElem, const QDomDocument& mComposition->addItemsFromXML( composerElem, doc, &mMapsToRestore ); } + // look for world file composer map, if needed + // Note: this must be done after maps have been added by addItemsFromXML + if ( mComposition->generateWorldFile() ) + { + QDomElement compositionElem = compositionNodeList.at( 0 ).toElement(); + QgsComposerMap* worldFileMap = 0; + QList maps = mComposition->composerMapItems(); + for ( QList::const_iterator it = maps.begin(); it != maps.end(); ++it ) + { + if (( *it )->id() == compositionElem.attribute( "worldFileMap" ).toInt() ) + { + worldFileMap = const_cast( *it ); + break; + } + } + mComposition->setWorldFileMap( worldFileMap ); + } + mComposition->sortZList(); mView->setComposition( mComposition ); @@ -2304,3 +2353,22 @@ void QgsComposer::createComposerView() mView->setVerticalRuler( mVerticalRuler ); mViewLayout->addWidget( mView, 1, 1 ); } + +void QgsComposer::writeWorldFile( QString worldFileName, double p[6] ) const +{ + QFile worldFile( worldFileName ); + if ( !worldFile.open( QIODevice::WriteOnly | QIODevice::Text ) ) + { + return; + } + QTextStream fout( &worldFile ); + + // QString::number does not use locale settings (for the decimal point) + // which is what we want here + fout << QString::number( p[0] ) << "\r\n"; + fout << QString::number( p[3] ) << "\r\n"; + fout << QString::number( p[1] ) << "\r\n"; + fout << QString::number( p[4] ) << "\r\n"; + fout << QString::number( p[2] ) << "\r\n"; + fout << QString::number( p[5] ) << "\r\n"; +} diff --git a/src/app/composer/qgscomposer.h b/src/app/composer/qgscomposer.h index 19db62c53d7d..6f20c70781b3 100644 --- a/src/app/composer/qgscomposer.h +++ b/src/app/composer/qgscomposer.h @@ -334,6 +334,9 @@ class QgsComposer: public QMainWindow, private Ui::QgsComposerBase //! Create composer view and rulers void createComposerView(); + //! Write a world file + void writeWorldFile( QString fileName, double params[6] ) const; + /**Composer title*/ QString mTitle; diff --git a/src/app/composer/qgscompositionwidget.cpp b/src/app/composer/qgscompositionwidget.cpp index a8e12c305bb0..0e87df77e7d7 100644 --- a/src/app/composer/qgscompositionwidget.cpp +++ b/src/app/composer/qgscompositionwidget.cpp @@ -17,6 +17,8 @@ #include #include "qgscompositionwidget.h" #include "qgscomposition.h" +#include "qgscomposermap.h" +#include "qgscomposeritem.h" #include #include #include //for screen resolution @@ -49,6 +51,29 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c ) //print as raster mPrintAsRasterCheckBox->setChecked( mComposition->printAsRaster() ); + // world file generation + mGenerateWorldFileCheckBox->setChecked( mComposition->generateWorldFile() ); + + // populate the map list + mWorldFileMapComboBox->clear(); + QList availableMaps = mComposition->composerMapItems(); + QList::const_iterator mapItemIt = availableMaps.constBegin(); + for ( ; mapItemIt != availableMaps.constEnd(); ++mapItemIt ) + { + mWorldFileMapComboBox->addItem( tr( "Map %1" ).arg(( *mapItemIt )->id() ), qVariantFromValue(( void* )*mapItemIt ) ); + } + + int idx = mWorldFileMapComboBox->findData( qVariantFromValue(( void* )mComposition->worldFileMap() ) ); + if ( idx != -1 ) + { + mWorldFileMapComboBox->setCurrentIndex( idx ); + } + + // Connect to addition / removal of maps + connect( mComposition, SIGNAL( composerMapAdded( QgsComposerMap* ) ), this, SLOT( onComposerMapAdded( QgsComposerMap* ) ) ); + connect( mComposition, SIGNAL( itemRemoved( QgsComposerItem* ) ), this, SLOT( onItemRemoved( QgsComposerItem* ) ) ); + + mAlignmentSnapGroupCheckBox->setChecked( mComposition->alignmentSnap() ); mAlignmentToleranceSpinBox->setValue( mComposition->alignmentSnapTolerance() ); @@ -412,6 +437,70 @@ void QgsCompositionWidget::on_mPrintAsRasterCheckBox_toggled( bool state ) mComposition->setPrintAsRaster( state ); } +void QgsCompositionWidget::on_mGenerateWorldFileCheckBox_toggled( bool state ) +{ + if ( !mComposition ) + { + return; + } + + mComposition->setGenerateWorldFile( state ); + mWorldFileMapComboBox->setEnabled( state ); +} + +void QgsCompositionWidget::onComposerMapAdded( QgsComposerMap* map ) +{ + if ( !mComposition ) + { + return; + } + + mWorldFileMapComboBox->addItem( tr( "Map %1" ).arg( map->id() ), qVariantFromValue(( void* )map ) ); + if ( mWorldFileMapComboBox->count() == 1 ) + { + mComposition->setWorldFileMap( map ); + } +} + +void QgsCompositionWidget::onItemRemoved( QgsComposerItem* item ) +{ + if ( !mComposition ) + { + return; + } + + QgsComposerMap* map = dynamic_cast( item ); + if ( map ) + { + int idx = mWorldFileMapComboBox->findData( qVariantFromValue(( void* )map ) ); + if ( idx != -1 ) + { + mWorldFileMapComboBox->removeItem( idx ); + } + } + if ( mWorldFileMapComboBox->count() == 0 ) + { + mComposition->setWorldFileMap( 0 ); + } +} + +void QgsCompositionWidget::on_mWorldFileMapComboBox_currentIndexChanged( int index ) +{ + if ( !mComposition ) + { + return; + } + if ( index == -1 ) + { + mComposition->setWorldFileMap( 0 ); + } + else + { + QgsComposerMap* map = reinterpret_cast( mWorldFileMapComboBox->itemData( index ).value() ); + mComposition->setWorldFileMap( map ); + } +} + void QgsCompositionWidget::on_mSnapToGridGroupCheckBox_toggled( bool state ) { if ( mComposition ) diff --git a/src/app/composer/qgscompositionwidget.h b/src/app/composer/qgscompositionwidget.h index 04e55e032116..c35a475ad759 100644 --- a/src/app/composer/qgscompositionwidget.h +++ b/src/app/composer/qgscompositionwidget.h @@ -17,6 +17,8 @@ #include "ui_qgscompositionwidgetbase.h" class QgsComposition; +class QgsComposerMap; +class QgsComposerItem; /** \ingroup MapComposer * Struct to hold map composer paper properties. @@ -48,6 +50,8 @@ class QgsCompositionWidget: public QWidget, private Ui::QgsCompositionWidgetBase void on_mNumPagesSpinBox_valueChanged( int value ); void on_mResolutionSpinBox_valueChanged( const int value ); void on_mPrintAsRasterCheckBox_toggled( bool state ); + void on_mGenerateWorldFileCheckBox_toggled( bool state ); + void on_mWorldFileMapComboBox_currentIndexChanged( int index ); void on_mSnapToGridGroupCheckBox_toggled( bool state ); void on_mGridResolutionSpinBox_valueChanged( double d ); @@ -65,6 +69,12 @@ class QgsCompositionWidget: public QWidget, private Ui::QgsCompositionWidgetBase /**Sets Print as raster checkbox value*/ void setPrintAsRasterCheckBox( bool state ); + private slots: + /* when a new map is added */ + void onComposerMapAdded( QgsComposerMap* ); + /* when a map is deleted */ + void onItemRemoved( QgsComposerItem* ); + private: QgsComposition* mComposition; QMap mPaperMap; diff --git a/src/core/composer/qgscomposition.cpp b/src/core/composer/qgscomposition.cpp index 7b9590613d68..6b4d937843d0 100644 --- a/src/core/composer/qgscomposition.cpp +++ b/src/core/composer/qgscomposition.cpp @@ -54,6 +54,8 @@ QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer ) , mPageHeight( 210 ) , mSpaceBetweenPages( 10 ) , mPrintAsRaster( false ) + , mGenerateWorldFile( false ) + , mWorldFileMap( 0 ) , mUseAdvancedEffects( true ) , mSelectionTolerance( 0.0 ) , mSnapToGrid( false ) @@ -81,6 +83,8 @@ QgsComposition::QgsComposition() mPageHeight( 210 ), mSpaceBetweenPages( 10 ), mPrintAsRaster( false ), + mGenerateWorldFile( false ), + mWorldFileMap( 0 ), mUseAdvancedEffects( true ), mSelectionTolerance( 0.0 ), mSnapToGrid( false ), @@ -420,6 +424,12 @@ bool QgsComposition::writeXML( QDomElement& composerElem, QDomDocument& doc ) compositionElem.setAttribute( "printResolution", mPrintResolution ); compositionElem.setAttribute( "printAsRaster", mPrintAsRaster ); + compositionElem.setAttribute( "generateWorldFile", mGenerateWorldFile ? 1 : 0 ); + if ( mGenerateWorldFile && mWorldFileMap ) + { + compositionElem.setAttribute( "worldFileMap", mWorldFileMap->id() ); + } + compositionElem.setAttribute( "alignmentSnap", mAlignmentSnap ? 1 : 0 ); compositionElem.setAttribute( "alignmentSnapTolerance", mAlignmentSnapTolerance ); @@ -505,6 +515,8 @@ bool QgsComposition::readXML( const QDomElement& compositionElem, const QDomDocu mPrintAsRaster = compositionElem.attribute( "printAsRaster" ).toInt(); mPrintResolution = compositionElem.attribute( "printResolution", "300" ).toInt(); + mGenerateWorldFile = compositionElem.attribute( "generateWorldFile", "0" ).toInt() == 1 ? true : false; + updatePaperItems(); return true; @@ -2174,3 +2186,64 @@ bool QgsComposition::nearestItem( const QMap< double, const QgsComposerItem* >& } } +void QgsComposition::computeWorldFileParameters( double p[6] ) const +{ + // + // Word file parameters : affine transformation parameters from pixel coordinates to map coordinates + + if ( !mWorldFileMap ) + { + return; + } + + QRectF brect = mWorldFileMap->boundingRect(); + QgsRectangle extent = mWorldFileMap->extent(); + + double alpha = mWorldFileMap->rotation() / 180 * M_PI; + + double xr = extent.width() / brect.width(); + double yr = extent.height() / brect.height(); + + double XC = extent.center().x(); + double YC = extent.center().y(); + + // get the extent for the page + double xmin = extent.xMinimum() - mWorldFileMap->transform().dx() * xr; + double ymax = extent.yMaximum() + mWorldFileMap->transform().dy() * yr; + QgsRectangle paperExtent( xmin, ymax - paperHeight() * yr, xmin + paperWidth() * xr, ymax ); + + double X0 = paperExtent.xMinimum(); + double Y0 = paperExtent.yMinimum(); + + int widthPx = ( int )( printResolution() * paperWidth() / 25.4 ); + int heightPx = ( int )( printResolution() * paperHeight() / 25.4 ); + + double Ww = paperExtent.width() / widthPx; + double Hh = paperExtent.height() / heightPx; + + // scaling matrix + double s[6]; + s[0] = Ww; + s[1] = 0; + s[2] = X0; + s[3] = 0; + s[4] = -Hh; + s[5] = Y0 + paperExtent.height(); + + // rotation matrix + double r[6]; + r[0] = cos( alpha ); + r[1] = -sin( alpha ); + r[2] = XC * ( 1 - cos( alpha ) ) + YC * sin( alpha ); + r[3] = sin( alpha ); + r[4] = cos( alpha ); + r[5] = - XC * sin( alpha ) + YC * ( 1 - cos( alpha ) ); + + // result = rotation x scaling = rotation(scaling(X)) + p[0] = r[0] * s[0] + r[1] * s[3]; + p[1] = r[0] * s[1] + r[1] * s[4]; + p[2] = r[0] * s[2] + r[1] * s[5] + r[2]; + p[3] = r[3] * s[0] + r[4] * s[3]; + p[4] = r[3] * s[1] + r[4] * s[4]; + p[5] = r[3] * s[2] + r[4] * s[5] + r[5]; +} diff --git a/src/core/composer/qgscomposition.h b/src/core/composer/qgscomposition.h index 7fc7a7bf25ba..581d5e369b61 100644 --- a/src/core/composer/qgscomposition.h +++ b/src/core/composer/qgscomposition.h @@ -182,6 +182,12 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene bool printAsRaster() const {return mPrintAsRaster;} void setPrintAsRaster( bool enabled ) { mPrintAsRaster = enabled; } + bool generateWorldFile() const { return mGenerateWorldFile; } + void setGenerateWorldFile( bool enabled ) { mGenerateWorldFile = enabled; } + + QgsComposerMap* worldFileMap() const { return mWorldFileMap; } + void setWorldFileMap( QgsComposerMap* map ) { mWorldFileMap = map; } + /**Returns true if a composition should use advanced effects such as blend modes @note added in 1.9*/ bool useAdvancedEffects() const {return mUseAdvancedEffects;} @@ -357,6 +363,9 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene @note added in version 1.9*/ void renderPage( QPainter* p, int page ); + /** Compute world file parameters */ + void computeWorldFileParameters( double p[6] ) const; + QgsAtlasComposition& atlasComposition() { return mAtlasComposition; } public slots: @@ -384,6 +393,11 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene /**Flag if map should be printed as a raster (via QImage). False by default*/ bool mPrintAsRaster; + /**Flag if a world file should be generated on raster export */ + bool mGenerateWorldFile; + /** Composer map to use for the world file generation */ + QgsComposerMap* mWorldFileMap; + /**Flag if advanced visual effects such as blend modes should be used. True by default*/ bool mUseAdvancedEffects; diff --git a/src/ui/qgscompositionwidgetbase.ui b/src/ui/qgscompositionwidgetbase.ui index 1bf334efa953..e86a2f04394e 100644 --- a/src/ui/qgscompositionwidgetbase.ui +++ b/src/ui/qgscompositionwidgetbase.ui @@ -243,7 +243,7 @@ - + Print as raster @@ -256,6 +256,23 @@ + + + + World file on + + + + + + + false + + + false + + + @@ -302,7 +319,7 @@ mm - 0.50000000000000 + 0.500000000000000 9999.000000000000000