Skip to content

Commit

Permalink
Port georeferencing from compositions
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Dec 17, 2017
1 parent 94b63d1 commit afbd140
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 3 deletions.
19 changes: 19 additions & 0 deletions python/core/layout/qgslayoutexporter.sip
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,25 @@ Resolution to export layout at
:rtype: ExportResult
%End

bool georeferenceOutput( const QString &file, QgsLayoutItemMap *referenceMap = 0,
const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;
%Docstring
Georeferences a ``file`` (image of PDF) exported from the layout.

The ``referenceMap`` argument specifies a map item to use for georeferencing. If left as None, the
default layout QgsLayout.referenceMap() will be used.

The ``exportRegion`` argument can be set to a valid rectangle to indicate that only part of the layout was
exported.

Similarly, the ``dpi`` can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.

The function will return true if the output was successfully georeferenced.

.. seealso:: :py:func:`computeGeoTransform()`
:rtype: bool
%End

};


Expand Down
128 changes: 125 additions & 3 deletions src/core/layout/qgslayoutexporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@
#include "qgslayout.h"
#include "qgslayoutitemmap.h"
#include "qgslayoutpagecollection.h"
#include "qgsogrutils.h"
#include <QImageWriter>
#include <QSize>

#include "gdal.h"
#include "cpl_conv.h"

QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout )
: mLayout( layout )
{
Expand Down Expand Up @@ -206,10 +210,10 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( const QString
return FileError;
}

#if 0 //TODO
if ( page == worldFilePageNo )
{
mLayout->georeferenceOutput( outputFilePath, nullptr, bounds, imageDlg.resolution() );
#if 0
georeferenceOutput( outputFilePath, nullptr, bounds, imageDlg.resolution() );

if ( settings.generateWorldFile )
{
Expand All @@ -228,12 +232,130 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( const QString

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

}
return Success;
}

double *QgsLayoutExporter::computeGeoTransform( const QgsLayoutItemMap *map, const QRectF &region, double dpi ) const
{
if ( !map )
map = mLayout->referenceMap();

if ( !map )
return nullptr;

if ( dpi < 0 )
dpi = mLayout->context().dpi();

// calculate region of composition to export (in mm)
QRectF exportRegion = region;
if ( !exportRegion.isValid() )
{
int pageNumber = map->page();

QgsLayoutItemPage *page = mLayout->pageCollection()->page( pageNumber );
double pageY = page->pos().y();
QSizeF pageSize = page->rect().size();
exportRegion = QRectF( 0, pageY, pageSize.width(), pageSize.height() );
}

// 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->extent();
double mapXCenter = mapExtent.center().x();
double mapYCenter = mapExtent.center().y();
double alpha = - map->mapRotation() / 180 * M_PI;
double sinAlpha = std::sin( alpha );
double cosAlpha = std::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;
}

bool QgsLayoutExporter::georeferenceOutput( const QString &file, QgsLayoutItemMap *map, const QRectF &exportRegion, double dpi ) const
{
if ( !map )
map = mLayout->referenceMap();

if ( !map )
return false; // no reference map

if ( dpi < 0 )
dpi = mLayout->context().dpi();

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

// 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() );
gdal::dataset_unique_ptr outputDS( GDALOpen( file.toLocal8Bit().constData(), GA_Update ) );
if ( outputDS )
{
GDALSetGeoTransform( outputDS.get(), t );
#if 0
//TODO - metadata can be set here, e.g.:
GDALSetMetadataItem( outputDS, "AUTHOR", "me", nullptr );
#endif
GDALSetProjection( outputDS.get(), map->crs().toWkt().toLocal8Bit().constData() );
}
CPLSetConfigOption( "GDAL_PDF_DPI", nullptr );
delete[] t;
return true;
}

QImage QgsLayoutExporter::createImage( const QgsLayoutExporter::ImageExportSettings &settings, int page, QRectF &bounds, bool &skipPage ) const
{
bounds = QRectF();
Expand Down
37 changes: 37 additions & 0 deletions src/core/layout/qgslayoutexporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
#include "qgsmargins.h"
#include <QPointer>
#include <QSize>
#include <QRectF>

class QgsLayout;
class QPainter;
class QgsLayoutItemMap;

/**
* \ingroup core
Expand Down Expand Up @@ -166,6 +168,24 @@ class CORE_EXPORT QgsLayoutExporter
*/
ExportResult exportToImage( const QString &filePath, const ImageExportSettings &settings );

/**
* Georeferences a \a file (image of PDF) exported from the layout.
*
* The \a referenceMap argument specifies a map item to use for georeferencing. If left as nullptr, the
* default layout QgsLayout::referenceMap() will be used.
*
* The \a exportRegion argument can be set to a valid rectangle to indicate that only part of the layout was
* exported.
*
* Similarly, the \a dpi can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*
* The function will return true if the output was successfully georeferenced.
*
* \see computeGeoTransform()
*/
bool georeferenceOutput( const QString &file, QgsLayoutItemMap *referenceMap = nullptr,
const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;

private:

QPointer< QgsLayout > mLayout;
Expand All @@ -179,6 +199,23 @@ class CORE_EXPORT QgsLayoutExporter
*/
static bool saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat );

/**
* Computes a GDAL style geotransform for georeferencing a layout.
*
* The \a referenceMap argument specifies a map item to use for georeferencing. If left as nullptr, the
* default layout QgsLayout::referenceMap() will be used.
*
* The \a exportRegion argument can be set to a valid rectangle to indicate that only part of the layout was
* exported.
*
* Similarly, the \a dpi can be set to the actual DPI of exported file, or left as -1 to use the layout's default DPI.
*
* \see georeferenceOutput()
*/
double *computeGeoTransform( const QgsLayoutItemMap *referenceMap = nullptr, const QRectF &exportRegion = QRectF(), double dpi = -1 ) const;

friend class TestQgsLayout;

};

#endif //QGSLAYOUTEXPORTER_H
Expand Down
70 changes: 70 additions & 0 deletions tests/src/core/testqgslayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class TestQgsLayout: public QObject
void shouldExportPage();
void pageIsEmpty();
void clear();
void georeference();

private:
QString mReport;
Expand Down Expand Up @@ -724,6 +725,75 @@ void TestQgsLayout::clear()
QCOMPARE( l.undoStack()->stack()->count(), 0 );
}

void TestQgsLayout::georeference()
{
QgsRectangle extent( 2000, 2800, 2500, 2900 );
QgsLayout l( QgsProject::instance() );
l.initializeDefaults();

QgsLayoutExporter exporter( &l );

// no map
double *t = exporter.computeGeoTransform( nullptr );
QVERIFY( !t );

QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
map->setExtent( extent );
l.addLayoutItem( map );

t = exporter.computeGeoTransform( map );
QGSCOMPARENEAR( t[0], 1925.0, 1.0 );
QGSCOMPARENEAR( t[1], 0.211719, 0.0001 );
QGSCOMPARENEAR( t[2], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[3], 3050, 1 );
QGSCOMPARENEAR( t[4], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[5], -0.211694, 0.0001 );
delete[] t;

// don't specify map
l.setReferenceMap( map );
t = exporter.computeGeoTransform();
QGSCOMPARENEAR( t[0], 1925.0, 1.0 );
QGSCOMPARENEAR( t[1], 0.211719, 0.0001 );
QGSCOMPARENEAR( t[2], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[3], 3050, 1 );
QGSCOMPARENEAR( t[4], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[5], -0.211694, 0.0001 );
delete[] t;

// specify extent
t = exporter.computeGeoTransform( map, QRectF( 70, 100, 50, 60 ) );
QGSCOMPARENEAR( t[0], 2100.0, 1.0 );
QGSCOMPARENEAR( t[1], 0.211864, 0.0001 );
QGSCOMPARENEAR( t[2], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[3], 2800, 1 );
QGSCOMPARENEAR( t[4], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[5], -0.211864, 0.0001 );
delete[] t;

// specify dpi
t = exporter.computeGeoTransform( map, QRectF(), 75 );
QGSCOMPARENEAR( t[0], 1925.0, 1 );
QGSCOMPARENEAR( t[1], 0.847603, 0.0001 );
QGSCOMPARENEAR( t[2], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[3], 3050.0, 1 );
QGSCOMPARENEAR( t[4], 0.0, 4 * DBL_EPSILON );
QGSCOMPARENEAR( t[5], -0.846774, 0.0001 );
delete[] t;

// rotation
map->setMapRotation( 45 );
t = exporter.computeGeoTransform( map );
QGSCOMPARENEAR( t[0], 1878.768940, 1 );
QGSCOMPARENEAR( t[1], 0.149708, 0.0001 );
QGSCOMPARENEAR( t[2], 0.149708, 0.0001 );
QGSCOMPARENEAR( t[3], 2761.611652, 1 );
QGSCOMPARENEAR( t[4], 0.14969, 0.0001 );
QGSCOMPARENEAR( t[5], -0.14969, 0.0001 );
delete[] t;
}


QGSTEST_MAIN( TestQgsLayout )
#include "testqgslayout.moc"

0 comments on commit afbd140

Please sign in to comment.