Skip to content

Commit

Permalink
[FEATURE] Georeference outputs (eg PDF) from composer
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed May 28, 2016
1 parent bc779a0 commit bae8a0e
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 61 deletions.
12 changes: 12 additions & 0 deletions python/core/composer/qgscomposition.sip
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,18 @@ class QgsComposition : QGraphicsScene
*/
void renderRect( QPainter* p, const QRectF& rect );

/** Georeferences a file (image of PDF) exported from the composition.
* @param file filename of exported file
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
* currently defined worldFileMap().
* @param exportRegion set to a valid rectangle to indicate that only part of the composition was
* exported
* @param dpi set to DPI of exported file, or leave as -1 to use composition's DPI.
* @note added in QGIS 2.16
*/
void georeferenceOutput( const QString& file, QgsComposerMap* referenceMap = nullptr,
const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;

/** Compute world file parameters. Assumes the whole page containing the associated map item
* will be exported.
*/
Expand Down
65 changes: 39 additions & 26 deletions src/app/composer/qgscomposer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,7 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode )
}
mComposition->doPrint( multiFilePrinter, painter );
painter.end();
mComposition->georeferenceOutput( outputFileName );
}
else
{
Expand All @@ -1801,6 +1802,8 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode )
else
{
bool exportOk = mComposition->exportAsPDF( outputFileName );
mComposition->georeferenceOutput( outputFileName );

if ( !exportOk )
{
QMessageBox::warning( this, tr( "Atlas processing error" ),
Expand Down Expand Up @@ -2049,7 +2052,7 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )
mView->setPaintingEnabled( false );

int worldFilePageNo = -1;
if ( mComposition->generateWorldFile() && mComposition->worldFileMap() )
if ( mComposition->worldFileMap() )
{
worldFilePageNo = mComposition->worldFileMap()->page() - 1;
}
Expand Down Expand Up @@ -2129,20 +2132,25 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )

if ( i == worldFilePageNo )
{
// should generate world file for this page
double a, b, c, d, e, f;
if ( bounds.isValid() )
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
else
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
mComposition->georeferenceOutput( outputFilePath, nullptr, bounds, imageDlg.resolution() );

if ( mComposition->generateWorldFile() )
{
// should generate world file for this page
double a, b, c, d, e, f;
if ( bounds.isValid() )
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
else
mComposition->computeWorldFileParameters( a, b, c, d, e, f );

QFileInfo fi( outputFilePath );
// build the world file name
QString outputSuffix = fi.suffix();
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';
QFileInfo fi( outputFilePath );
// build the world file name
QString outputSuffix = fi.suffix();
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';

writeWorldFile( worldFileName, a, b, c, d, e, f );
writeWorldFile( worldFileName, a, b, c, d, e, f );
}
}
}

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

int worldFilePageNo = -1;
if ( mComposition->generateWorldFile() && mComposition->worldFileMap() )
if ( mComposition->worldFileMap() )
{
worldFilePageNo = mComposition->worldFileMap()->page() - 1;
}
Expand Down Expand Up @@ -2350,20 +2358,25 @@ void QgsComposer::exportCompositionAsImage( QgsComposer::OutputMode mode )

if ( i == worldFilePageNo )
{
// should generate world file for this page
double a, b, c, d, e, f;
if ( bounds.isValid() )
mComposition->computeWorldFileParameters( bounds, a, b, c, d, e, f );
else
mComposition->computeWorldFileParameters( a, b, c, d, e, f );
mComposition->georeferenceOutput( imageFilename, nullptr, bounds, imageDlg.resolution() );

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

writeWorldFile( worldFileName, a, b, c, d, e, f );
QFileInfo fi( imageFilename );
// build the world file name
QString outputSuffix = fi.suffix();
QString worldFileName = fi.absolutePath() + '/' + fi.baseName() + '.'
+ outputSuffix.at( 0 ) + outputSuffix.at( fi.suffix().size() - 1 ) + 'w';

writeWorldFile( worldFileName, a, b, c, d, e, f );
}
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions src/app/composer/qgscompositionwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ QgsCompositionWidget::QgsCompositionWidget( QWidget* parent, QgsComposition* c )

// world file generation
mGenerateWorldFileCheckBox->setChecked( mComposition->generateWorldFile() );
mWorldFileMapComboBox->setEnabled( mComposition->generateWorldFile() );

// populate the map list
mWorldFileMapComboBox->setComposition( mComposition );
Expand Down Expand Up @@ -658,7 +657,6 @@ void QgsCompositionWidget::on_mGenerateWorldFileCheckBox_toggled( bool state )
}

mComposition->setGenerateWorldFile( state );
mWorldFileMapComboBox->setEnabled( state );
}

void QgsCompositionWidget::worldFileMapChanged( QgsComposerItem* item )
Expand Down
111 changes: 111 additions & 0 deletions src/core/composer/qgscomposition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
#include <QDir>

#include <limits>
#include "gdal.h"
#include "cpl_conv.h"

QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer )
: QGraphicsScene( nullptr )
Expand Down Expand Up @@ -2886,6 +2888,34 @@ bool QgsComposition::exportAsPDF( const QString& file )
return print( printer );
}

void QgsComposition::georeferenceOutput( const QString& file, QgsComposerMap* map,
const QRectF& exportRegion, double dpi ) const
{
if ( dpi < 0 )
dpi = printResolution();

double* t = computeGeoTransform( map, exportRegion, dpi );
if ( !t )
return;

// important - we need to manually specify the DPI in advance, as GDAL will otherwise
// assume a DPI of 150
CPLSetConfigOption( "GDAL_PDF_DPI", QString::number( dpi ).toLocal8Bit().constData() );
GDALDatasetH outputDS = GDALOpen( file.toLocal8Bit().constData(), GA_Update );
if ( outputDS )
{
GDALSetGeoTransform( outputDS, t );
#if 0
//TODO - metadata can be set here, eg:
GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
#endif
GDALSetProjection( outputDS, mMapSettings.destinationCrs().toWkt().toLocal8Bit().constData() );
GDALClose( outputDS );
}
CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
delete[] t;
}

void QgsComposition::doPrint( QPrinter& printer, QPainter& p, bool startNewPage )
{
if ( ddPageSizeActive() )
Expand Down Expand Up @@ -3086,6 +3116,87 @@ void QgsComposition::renderRect( QPainter* p, const QRectF& rect )
mPlotStyle = savedPlotStyle;
}

double* QgsComposition::computeGeoTransform( const QgsComposerMap* map, const QRectF& region , double dpi ) const
{
if ( !map )
map = worldFileMap();

if ( !map )
return nullptr;

if ( dpi < 0 )
dpi = printResolution();

// calculate region of composition to export (in mm)
QRectF exportRegion = region;
if ( !exportRegion.isValid() )
{
int pageNumber = map->page() - 1;
double pageY = pageNumber * ( mPageHeight + mSpaceBetweenPages );
exportRegion = QRectF( 0, pageY, mPageWidth, mPageHeight );
}

// map rectangle (in mm)
QRectF mapItemSceneRect = map->mapRectToScene( map->rect() );

// destination width/height in mm
double outputHeightMM = exportRegion.height();
double outputWidthMM = exportRegion.width();

// map properties
QgsRectangle mapExtent = *map->currentMapExtent();
double mapXCenter = mapExtent.center().x();
double mapYCenter = mapExtent.center().y();
double alpha = - map->mapRotation() / 180 * M_PI;
double sinAlpha = sin( alpha );
double cosAlpha = cos( alpha );

// get the extent (in map units) for the exported region
QPointF mapItemPos = map->pos();
//adjust item position so it is relative to export region
mapItemPos.rx() -= exportRegion.left();
mapItemPos.ry() -= exportRegion.top();

// calculate extent of entire page in map units
double xRatio = mapExtent.width() / mapItemSceneRect.width();
double yRatio = mapExtent.height() / mapItemSceneRect.height();
double xmin = mapExtent.xMinimum() - mapItemPos.x() * xRatio;
double ymax = mapExtent.yMaximum() + mapItemPos.y() * yRatio;
QgsRectangle paperExtent( xmin, ymax - outputHeightMM * yRatio, xmin + outputWidthMM * xRatio, ymax );

// calculate origin of page
double X0 = paperExtent.xMinimum();
double Y0 = paperExtent.yMaximum();

if ( !qgsDoubleNear( alpha, 0.0 ) )
{
// translate origin to account for map rotation
double X1 = X0 - mapXCenter;
double Y1 = Y0 - mapYCenter;
double X2 = X1 * cosAlpha + Y1 * sinAlpha;
double Y2 = -X1 * sinAlpha + Y1 * cosAlpha;
X0 = X2 + mapXCenter;
Y0 = Y2 + mapYCenter;
}

// calculate scaling of pixels
int pageWidthPixels = static_cast< int >( dpi * outputWidthMM / 25.4 );
int pageHeightPixels = static_cast< int >( dpi * outputHeightMM / 25.4 );
double pixelWidthScale = paperExtent.width() / pageWidthPixels;
double pixelHeightScale = paperExtent.height() / pageHeightPixels;

// transform matrix
double* t = new double[6];
t[0] = X0;
t[1] = cosAlpha * pixelWidthScale;
t[2] = -sinAlpha * pixelWidthScale;
t[3] = Y0;
t[4] = -sinAlpha * pixelHeightScale;
t[5] = -cosAlpha * pixelHeightScale;

return t;
}

QString QgsComposition::encodeStringForXML( const QString& str )
{
QString modifiedStr( str );
Expand Down
24 changes: 24 additions & 0 deletions src/core/composer/qgscomposition.h
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,18 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
*/
void renderRect( QPainter* p, const QRectF& rect );

/** Georeferences a file (image of PDF) exported from the composition.
* @param file filename of exported file
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
* currently defined worldFileMap().
* @param exportRegion set to a valid rectangle to indicate that only part of the composition was
* exported
* @param dpi set to DPI of exported file, or leave as -1 to use composition's DPI.
* @note added in QGIS 2.16
*/
void georeferenceOutput( const QString& file, QgsComposerMap* referenceMap = nullptr,
const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;

/** Compute world file parameters. Assumes the whole page containing the associated map item
* will be exported.
*/
Expand Down Expand Up @@ -1075,6 +1087,17 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene
*/
bool ddPageSizeActive() const;

/** Computes a GDAL style geotransform for georeferencing a composition.
* @param referenceMap map item to use for georeferencing, or leave as nullptr to use the
* currently defined worldFileMap().
* @param exportRegion set to a valid rectangle to indicate that only part of the composition is
* being exported
* @param dpi allows overriding the default composition DPI, or leave as -1 to use composition's DPI.
* @note added in QGIS 2.16
*/
double* computeGeoTransform( const QgsComposerMap* referenceMap = nullptr, const QRectF& exportRegion = QRectF(), double dpi = -1 ) const;


private slots:
/*Prepares all data defined expressions*/
void prepareAllDataDefinedExpressions();
Expand Down Expand Up @@ -1123,6 +1146,7 @@ class CORE_EXPORT QgsComposition : public QGraphicsScene

friend class QgsComposerObject; //for accessing dataDefinedEvaluate, readDataDefinedPropertyMap and writeDataDefinedPropertyMap
friend class QgsComposerModel; //for accessing updateZValues (should not be public)
friend class TestQgsComposition;
};

template<class T> void QgsComposition::composerItems( QList<T*>& itemList )
Expand Down
Loading

0 comments on commit bae8a0e

Please sign in to comment.