Skip to content

Commit bae8a0e

Browse files
committed
[FEATURE] Georeference outputs (eg PDF) from composer
This commit makes composer automatically georeference outputs (where output format makes this possible, eg TIF and PDF). The existing option to create a world file has been separated from the map selection for georeferencing. The new behaviour is to always georeference outputs, and only create the separate world file if that option is checked.
1 parent bc779a0 commit bae8a0e

File tree

7 files changed

+298
-61
lines changed

7 files changed

+298
-61
lines changed

python/core/composer/qgscomposition.sip

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,18 @@ class QgsComposition : QGraphicsScene
678678
*/
679679
void renderRect( QPainter* p, const QRectF& rect );
680680

681+
/** Georeferences a file (image of PDF) exported from the composition.
682+
* @param file filename of exported file
683+
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
684+
* currently defined worldFileMap().
685+
* @param exportRegion set to a valid rectangle to indicate that only part of the composition was
686+
* exported
687+
* @param dpi set to DPI of exported file, or leave as -1 to use composition's DPI.
688+
* @note added in QGIS 2.16
689+
*/
690+
void georeferenceOutput( const QString& file, QgsComposerMap* referenceMap = nullptr,
691+
const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;
692+
681693
/** Compute world file parameters. Assumes the whole page containing the associated map item
682694
* will be exported.
683695
*/

src/app/composer/qgscomposer.cpp

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1785,6 +1785,7 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode )
17851785
}
17861786
mComposition->doPrint( multiFilePrinter, painter );
17871787
painter.end();
1788+
mComposition->georeferenceOutput( outputFileName );
17881789
}
17891790
else
17901791
{
@@ -1801,6 +1802,8 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode )
18011802
else
18021803
{
18031804
bool exportOk = mComposition->exportAsPDF( outputFileName );
1805+
mComposition->georeferenceOutput( outputFileName );
1806+
18041807
if ( !exportOk )
18051808
{
18061809
QMessageBox::warning( this, tr( "Atlas processing error" ),
@@ -2049,7 +2052,7 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )
20492052
mView->setPaintingEnabled( false );
20502053

20512054
int worldFilePageNo = -1;
2052-
if ( mComposition->generateWorldFile() && mComposition->worldFileMap() )
2055+
if ( mComposition->worldFileMap() )
20532056
{
20542057
worldFilePageNo = mComposition->worldFileMap()->page() - 1;
20552058
}
@@ -2129,20 +2132,25 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )
21292132

21302133
if ( i == worldFilePageNo )
21312134
{
2132-
// should generate world file for this page
2133-
double a, b, c, d, e, f;
2134-
if ( bounds.isValid() )
2135-
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
2136-
else
2137-
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
2135+
mComposition->georeferenceOutput( outputFilePath, nullptr, bounds, imageDlg.resolution() );
2136+
2137+
if ( mComposition->generateWorldFile() )
2138+
{
2139+
// should generate world file for this page
2140+
double a, b, c, d, e, f;
2141+
if ( bounds.isValid() )
2142+
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
2143+
else
2144+
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
21382145

2139-
QFileInfo fi( outputFilePath );
2140-
// build the world file name
2141-
QString outputSuffix = fi.suffix();
2142-
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
2143-
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
2146+
QFileInfo fi( outputFilePath );
2147+
// build the world file name
2148+
QString outputSuffix = fi.suffix();
2149+
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
2150+
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
21442151

2145-
writeWorldFile( worldFileName, a, b, c, d, e, f );
2152+
writeWorldFile( worldFileName, a, b, c, d, e, f );
2153+
}
21462154
}
21472155
}
21482156

@@ -2282,7 +2290,7 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )
22822290
QString filename = QDir( dir ).filePath( atlasMap->currentFilename() ) + fileExt;
22832291

22842292
int worldFilePageNo = -1;
2285-
if ( mComposition->generateWorldFile() && mComposition->worldFileMap() )
2293+
if ( mComposition->worldFileMap() )
22862294
{
22872295
worldFilePageNo = mComposition->worldFileMap()->page() - 1;
22882296
}
@@ -2350,20 +2358,25 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )
23502358

23512359
if ( i == worldFilePageNo )
23522360
{
2353-
// should generate world file for this page
2354-
double a, b, c, d, e, f;
2355-
if ( bounds.isValid() )
2356-
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
2357-
else
2358-
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
2361+
mComposition->georeferenceOutput( imageFilename, nullptr, bounds, imageDlg.resolution() );
23592362

2360-
QFileInfo fi( imageFilename );
2361-
// build the world file name
2362-
QString outputSuffix = fi.suffix();
2363-
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
2364-
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
2363+
if ( mComposition->generateWorldFile() )
2364+
{
2365+
// should generate world file for this page
2366+
double a, b, c, d, e, f;
2367+
if ( bounds.isValid() )
2368+
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
2369+
else
2370+
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
23652371

2366-
writeWorldFile( worldFileName, a, b, c, d, e, f );
2372+
QFileInfo fi( imageFilename );
2373+
// build the world file name
2374+
QString outputSuffix = fi.suffix();
2375+
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
2376+
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
2377+
2378+
writeWorldFile( worldFileName, a, b, c, d, e, f );
2379+
}
23672380
}
23682381
}
23692382
}

src/app/composer/qgscompositionwidget.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c )
7878

7979
// world file generation
8080
mGenerateWorldFileCheckBox->setChecked( mComposition->generateWorldFile() );
81-
mWorldFileMapComboBox->setEnabled( mComposition->generateWorldFile() );
8281

8382
// populate the map list
8483
mWorldFileMapComboBox->setComposition( mComposition );
@@ -658,7 +657,6 @@ void QgsCompositionWidget::on_mGenerateWorldFileCheckBox_toggled( bool state )
658657
}
659658

660659
mComposition->setGenerateWorldFile( state );
661-
mWorldFileMapComboBox->setEnabled( state );
662660
}
663661

664662
void QgsCompositionWidget::worldFileMapChanged( QgsComposerItem* item )

src/core/composer/qgscomposition.cpp

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
#include <QDir>
5858

5959
#include <limits>
60+
#include "gdal.h"
61+
#include "cpl_conv.h"
6062

6163
QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer )
6264
: QGraphicsScene( nullptr )
@@ -2886,6 +2888,34 @@ bool QgsComposition::exportAsPDF( const QString& file )
28862888
return print( printer );
28872889
}
28882890

2891+
void QgsComposition::georeferenceOutput( const QString& file, QgsComposerMap* map,
2892+
const QRectF& exportRegion, double dpi ) const
2893+
{
2894+
if ( dpi < 0 )
2895+
dpi = printResolution();
2896+
2897+
double* t = computeGeoTransform( map, exportRegion, dpi );
2898+
if ( !t )
2899+
return;
2900+
2901+
// important - we need to manually specify the DPI in advance, as GDAL will otherwise
2902+
// assume a DPI of 150
2903+
CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
2904+
GDALDatasetH outputDS = GDALOpen( file.toLocal8Bit().constData(), GA_Update );
2905+
if ( outputDS )
2906+
{
2907+
GDALSetGeoTransform( outputDS, t );
2908+
#if 0
2909+
//TODO - metadata can be set here, eg:
2910+
GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
2911+
#endif
2912+
GDALSetProjection( outputDS, mMapSettings.destinationCrs().toWkt().toLocal8Bit().constData() );
2913+
GDALClose( outputDS );
2914+
}
2915+
CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
2916+
delete[] t;
2917+
}
2918+
28892919
void QgsComposition::doPrint( QPrinter& printer, QPainter& p, bool startNewPage )
28902920
{
28912921
if ( ddPageSizeActive() )
@@ -3086,6 +3116,87 @@ void QgsComposition::renderRect( QPainter* p, const QRectF& rect )
30863116
mPlotStyle = savedPlotStyle;
30873117
}
30883118

3119+
double* QgsComposition::computeGeoTransform( const QgsComposerMap* map, const QRectF& region , double dpi ) const
3120+
{
3121+
if ( !map )
3122+
map = worldFileMap();
3123+
3124+
if ( !map )
3125+
return nullptr;
3126+
3127+
if ( dpi < 0 )
3128+
dpi = printResolution();
3129+
3130+
// calculate region of composition to export (in mm)
3131+
QRectF exportRegion = region;
3132+
if ( !exportRegion.isValid() )
3133+
{
3134+
int pageNumber = map->page() - 1;
3135+
double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
3136+
exportRegion = QRectF( 0, pageY, mPageWidth, mPageHeight );
3137+
}
3138+
3139+
// map rectangle (in mm)
3140+
QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );
3141+
3142+
// destination width/height in mm
3143+
double outputHeightMM = exportRegion.height();
3144+
double outputWidthMM = exportRegion.width();
3145+
3146+
// map properties
3147+
QgsRectangle mapExtent = *map->currentMapExtent();
3148+
double mapXCenter = mapExtent.center().x();
3149+
double mapYCenter = mapExtent.center().y();
3150+
double alpha = - map->mapRotation() / 180 * M_PI;
3151+
double sinAlpha = sin( alpha );
3152+
double cosAlpha = cos( alpha );
3153+
3154+
// get the extent (in map units) for the exported region
3155+
QPointF mapItemPos = map->pos();
3156+
//adjust item position so it is relative to export region
3157+
mapItemPos.rx() -= exportRegion.left();
3158+
mapItemPos.ry() -= exportRegion.top();
3159+
3160+
// calculate extent of entire page in map units
3161+
double xRatio = mapExtent.width() / mapItemSceneRect.width();
3162+
double yRatio = mapExtent.height() / mapItemSceneRect.height();
3163+
double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
3164+
double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
3165+
QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );
3166+
3167+
// calculate origin of page
3168+
double X0 = paperExtent.xMinimum();
3169+
double Y0 = paperExtent.yMaximum();
3170+
3171+
if ( !qgsDoubleNear( alpha, 0.0 ) )
3172+
{
3173+
// translate origin to account for map rotation
3174+
double X1 = X0 - mapXCenter;
3175+
double Y1 = Y0 - mapYCenter;
3176+
double X2 = X1 * cosAlpha + Y1 * sinAlpha;
3177+
double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
3178+
X0 = X2 + mapXCenter;
3179+
Y0 = Y2 + mapYCenter;
3180+
}
3181+
3182+
// calculate scaling of pixels
3183+
int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
3184+
int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
3185+
double pixelWidthScale = paperExtent.width() / pageWidthPixels;
3186+
double pixelHeightScale = paperExtent.height() / pageHeightPixels;
3187+
3188+
// transform matrix
3189+
double* t = new double[6];
3190+
t[0] = X0;
3191+
t[1] = cosAlpha * pixelWidthScale;
3192+
t[2] = -sinAlpha * pixelWidthScale;
3193+
t[3] = Y0;
3194+
t[4] = -sinAlpha * pixelHeightScale;
3195+
t[5] = -cosAlpha * pixelHeightScale;
3196+
3197+
return t;
3198+
}
3199+
30893200
QString QgsComposition::encodeStringForXML( const QString& str )
30903201
{
30913202
QString modifiedStr( str );

src/core/composer/qgscomposition.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,18 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
743743
*/
744744
void renderRect( QPainter* p, const QRectF& rect );
745745

746+
/** Georeferences a file (image of PDF) exported from the composition.
747+
* @param file filename of exported file
748+
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
749+
* currently defined worldFileMap().
750+
* @param exportRegion set to a valid rectangle to indicate that only part of the composition was
751+
* exported
752+
* @param dpi set to DPI of exported file, or leave as -1 to use composition's DPI.
753+
* @note added in QGIS 2.16
754+
*/
755+
void georeferenceOutput( const QString& file, QgsComposerMap* referenceMap = nullptr,
756+
const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;
757+
746758
/** Compute world file parameters. Assumes the whole page containing the associated map item
747759
* will be exported.
748760
*/
@@ -1075,6 +1087,17 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
10751087
*/
10761088
bool ddPageSizeActive() const;
10771089

1090+
/** Computes a GDAL style geotransform for georeferencing a composition.
1091+
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
1092+
* currently defined worldFileMap().
1093+
* @param exportRegion set to a valid rectangle to indicate that only part of the composition is
1094+
* being exported
1095+
* @param dpi allows overriding the default composition DPI, or leave as -1 to use composition's DPI.
1096+
* @note added in QGIS 2.16
1097+
*/
1098+
double* computeGeoTransform( const QgsComposerMap* referenceMap = nullptr, const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;
1099+
1100+
10781101
private slots:
10791102
/*Prepares all data defined expressions*/
10801103
void prepareAllDataDefinedExpressions();
@@ -1123,6 +1146,7 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
11231146

11241147
friend class QgsComposerObject; //for accessing dataDefinedEvaluate, readDataDefinedPropertyMap and writeDataDefinedPropertyMap
11251148
friend class QgsComposerModel; //for accessing updateZValues (should not be public)
1149+
friend class TestQgsComposition;
11261150
};
11271151

11281152
template<class T> void QgsComposition::composerItems( QList<T*>& itemList )

0 commit comments

Comments
 (0)