Skip to content
Permalink
Browse files

[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.
  • Loading branch information
nyalldawson committed May 27, 2016
1 parent bc779a0 commit bae8a0e5e0b8b86f5eeed94c323a4fffac846c46
@@ -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.
*/
@@ -1785,6 +1785,7 @@ void QgsComposer::exportCompositionAsPDF( QgsComposer::OutputMode mode )
}
mComposition->doPrint( multiFilePrinter, painter );
painter.end();
mComposition->georeferenceOutput( outputFileName );
}
else
{
@@ -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" ),
@@ -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;
}
@@ -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 );
}
}
}

@@ -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;
}
@@ -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 );
}
}
}
}
@@ -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 );
@@ -658,7 +657,6 @@ void QgsCompositionWidget::on_mGenerateWorldFileCheckBox_toggled( bool state )
}

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

void QgsCompositionWidget::worldFileMapChanged( QgsComposerItem* item )
@@ -57,6 +57,8 @@
#include <QDir>

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

QgsComposition::QgsComposition( QgsMapRenderer* mapRenderer )
: QGraphicsScene( nullptr )
@@ -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() )
@@ -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 );
@@ -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.
*/
@@ -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();
@@ -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 )

0 comments on commit bae8a0e

Please sign in to comment.
You can’t perform that action at this time.