Skip to content

Commit

Permalink
Add argument to QgsCoordinateTransform::transformBoundingBox to flag …
Browse files Browse the repository at this point in the history
…that

handling of a bounding box which crosses the 180 degree longitude line
is required. Fix composer map reprojected grids which cross 180 degree line.
  • Loading branch information
nyalldawson committed Oct 1, 2014
1 parent d66e4dc commit a2d5acb
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 7 deletions.
4 changes: 3 additions & 1 deletion python/core/qgscoordinatetransform.sip
Expand Up @@ -114,9 +114,11 @@ class QgsCoordinateTransform : QObject
* returned rectangle.
* @param theRect rect to transform
* @param direction TransformDirection (defaults to ForwardTransform)
* @param handle180Crossover set to true if destination crs is geographic and handling of extents crossing the 180 degree
* longitude line is required
* @return QgsRectangle in Destination Coordinate System
*/
QgsRectangle transformBoundingBox( const QgsRectangle theRect, TransformDirection direction = ForwardTransform ) const throw (QgsCsException);
QgsRectangle transformBoundingBox( const QgsRectangle theRect, TransformDirection direction = ForwardTransform, const bool handle180Crossover = false ) const throw (QgsCsException);

// Same as for the other transform() functions, but alters the x
// and y variables in place. The second one works with good old-fashioned
Expand Down
61 changes: 58 additions & 3 deletions src/core/composer/qgscomposermapgrid.cpp
Expand Up @@ -25,6 +25,7 @@
#include "qgsrendercontext.h"
#include "qgssymbollayerv2utils.h"
#include "qgssymbolv2.h"
#include "qgscoordinatereferencesystem.h"

#include <QPainter>
#include <QPen>
Expand Down Expand Up @@ -1356,6 +1357,15 @@ int QgsComposerMapGrid::xGridLinesCRSTransform( const QgsRectangle& bbox, const
double maxX = bbox.xMaximum();
double step = ( maxX - minX ) / 20;

bool crosses180 = false;
bool crossed180 = false;
if ( mCRS.geographicFlag() && ( minX > maxX ) )
{
//handle 180 degree longitude crossover
crosses180 = true;
step = ( maxX + 360.0 - minX ) / 20;
}

int gridLineCount = 0;
while ( currentLevel >= bbox.yMinimum() && gridLineCount < MAX_GRID_LINES )
{
Expand All @@ -1364,15 +1374,21 @@ int QgsComposerMapGrid::xGridLinesCRSTransform( const QgsRectangle& bbox, const
bool cont = true;
while ( cont )
{
if ( currentX > maxX )
if (( !crosses180 || crossed180 ) && ( currentX > maxX ) )
{
cont = false;
}

QgsPoint mapPoint = t.transform( currentX, currentLevel ); //transform back to map crs
gridLine.append( mComposerMap->mapToItemCoords( QPointF( mapPoint.x(), mapPoint.y() ) ) ); //transform back to composer coords
currentX += step;
if ( crosses180 && currentX > 180.0 )
{
currentX -= 360.0;
crossed180 = true;
}
}
crossed180 = false;

gridLine = trimLineToMap( gridLine, QgsRectangle( mComposerMap->rect() ) );
if ( gridLine.size() > 0 )
Expand Down Expand Up @@ -1401,8 +1417,16 @@ int QgsComposerMapGrid::yGridLinesCRSTransform( const QgsRectangle& bbox, const
double maxY = bbox.yMaximum();
double step = ( maxY - minY ) / 20;

bool crosses180 = false;
bool crossed180 = false;
if ( mCRS.geographicFlag() && ( bbox.xMinimum() > bbox.xMaximum() ) )
{
//handle 180 degree longitude crossover
crosses180 = true;
}

int gridLineCount = 0;
while ( currentLevel <= bbox.xMaximum() && gridLineCount < MAX_GRID_LINES )
while (( currentLevel <= bbox.xMaximum() || ( crosses180 && !crossed180 ) ) && gridLineCount < MAX_GRID_LINES )
{
QPolygonF gridLine;
double currentY = minY;
Expand All @@ -1427,6 +1451,11 @@ int QgsComposerMapGrid::yGridLinesCRSTransform( const QgsRectangle& bbox, const
gridLineCount++;
}
currentLevel += mGridIntervalX;
if ( crosses180 && currentLevel > 180.0 )
{
currentLevel -= 360.0;
crossed180 = true;
}
}

return 0;
Expand Down Expand Up @@ -1735,7 +1764,33 @@ int QgsComposerMapGrid::crsGridParams( QgsRectangle& crsRect, QgsCoordinateTrans
QPolygonF mapPolygon = mComposerMap->transformedMapPolygon();
QRectF mbr = mapPolygon.boundingRect();
QgsRectangle mapBoundingRect( mbr.left(), mbr.bottom(), mbr.right(), mbr.top() );
crsRect = tr.transformBoundingBox( mapBoundingRect );


if ( mCRS.geographicFlag() )
{
//handle crossing the 180 degree longitude line
QgsPoint lowerLeft( mapBoundingRect.xMinimum(), mapBoundingRect.yMinimum() );
QgsPoint upperRight( mapBoundingRect.xMaximum(), mapBoundingRect.yMaximum() );

lowerLeft = tr.transform( lowerLeft.x(), lowerLeft.y() );
upperRight = tr.transform( upperRight.x(), upperRight.y() );

if ( lowerLeft.x() > upperRight.x() )
{
//we've crossed the line
crsRect = tr.transformBoundingBox( mapBoundingRect, QgsCoordinateTransform::ForwardTransform, true );
}
else
{
//didn't cross the line
crsRect = tr.transformBoundingBox( mapBoundingRect );
}
}
else
{
crsRect = tr.transformBoundingBox( mapBoundingRect );
}

inverseTransform.setSourceCrs( mCRS );
inverseTransform.setDestCRS( mComposerMap->composition()->mapSettings().destinationCrs() );
return 0;
Expand Down
25 changes: 23 additions & 2 deletions src/core/qgscoordinatetransform.cpp
Expand Up @@ -520,7 +520,7 @@ void QgsCoordinateTransform::transformInPlace(
#endif //ANDROID


QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle rect, TransformDirection direction ) const
QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle rect, TransformDirection direction, const bool handle180Crossover ) const
{
// Calculate the bounding box of a QgsRectangle in the source CRS
// when projected to the destination CRS (or the inverse).
Expand Down Expand Up @@ -592,8 +592,29 @@ QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle re

for ( int i = 0; i < numP * numP; i++ )
{
if ( qIsFinite( x[i] ) && qIsFinite( y[i] ) )
if ( !qIsFinite( x[i] ) || !qIsFinite( y[i] ) )
{
continue;
}

if ( handle180Crossover )
{
//if crossing the date line, temporarily add 360 degrees to -ve longitudes
bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
}
else
{
bb_rect.combineExtentWith( x[i], y[i] );
}
}

if ( handle180Crossover )
{
//subtract temporary addition of 360 degrees from longitudes
if ( bb_rect.xMinimum() > 180.0 )
bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
if ( bb_rect.xMaximum() > 180.0 )
bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
}

QgsDebugMsg( "Projected extent: " + bb_rect.toString() );
Expand Down
4 changes: 3 additions & 1 deletion src/core/qgscoordinatetransform.h
Expand Up @@ -148,9 +148,11 @@ class CORE_EXPORT QgsCoordinateTransform : public QObject
* returned rectangle.
* @param theRect rect to transform
* @param direction TransformDirection (defaults to ForwardTransform)
* @param handle180Crossover set to true if destination crs is geographic and handling of extents crossing the 180 degree
* longitude line is required
* @return QgsRectangle in Destination Coordinate System
*/
QgsRectangle transformBoundingBox( const QgsRectangle theRect, TransformDirection direction = ForwardTransform ) const;
QgsRectangle transformBoundingBox( const QgsRectangle theRect, TransformDirection direction = ForwardTransform, const bool handle180Crossover = false ) const;

// Same as for the other transform() functions, but alters the x
// and y variables in place. The second one works with good old-fashioned
Expand Down
1 change: 1 addition & 0 deletions tests/src/core/CMakeLists.txt
Expand Up @@ -98,6 +98,7 @@ ADD_QGIS_TEST(blendmodestest testqgsblendmodes.cpp)
ADD_QGIS_TEST(geometrytest testqgsgeometry.cpp)
ADD_QGIS_TEST(geometryimporttest testqgsgeometryimport.cpp)
ADD_QGIS_TEST(coordinatereferencesystemtest testqgscoordinatereferencesystem.cpp)
ADD_QGIS_TEST(coordinatetransformtest testqgscoordinatetransform.cpp)
ADD_DEPENDENCIES(qgis_coordinatereferencesystemtest synccrsdb)
ADD_QGIS_TEST(pointtest testqgspoint.cpp)
ADD_QGIS_TEST(vectordataprovidertest testqgsvectordataprovider.cpp)
Expand Down
65 changes: 65 additions & 0 deletions tests/src/core/testqgscoordinatetransform.cpp
@@ -0,0 +1,65 @@
/***************************************************************************
testqgscoordinatetransform.cpp
-----------------------
begin : October 2014
copyright : (C) 2014 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgscoordinatetransform.h"
#include "qgsapplication.h"
#include <QObject>
#include <QtTest>

class TestQgsCoordinateTransform: public QObject
{
Q_OBJECT

private slots:
void initTestCase();
void transformBoundingBox();

private:

};


void TestQgsCoordinateTransform::initTestCase()
{
QgsApplication::init();
QgsApplication::initQgis();

}

void TestQgsCoordinateTransform::transformBoundingBox()
{
//test transforming a bounding box which crosses the 180 degree longitude line
QgsCoordinateReferenceSystem sourceSrs;
sourceSrs.createFromSrid( 3994 );
QgsCoordinateReferenceSystem destSrs;
destSrs.createFromSrid( 4326 );

QgsCoordinateTransform tr( sourceSrs, destSrs );
QgsRectangle crossingRect( 6374985, -3626584, 7021195, -3272435 );
QgsRectangle resultRect = tr.transformBoundingBox( crossingRect, QgsCoordinateTransform::ForwardTransform, true );
QgsRectangle expectedRect;
expectedRect.setXMinimum( 175.771 );
expectedRect.setYMinimum( -39.7222 );
expectedRect.setXMaximum( -176.549 );
expectedRect.setYMaximum( -36.3951 );
QVERIFY( qgsDoubleNear( resultRect.xMinimum(), expectedRect.xMinimum(), 0.001 ) );
QVERIFY( qgsDoubleNear( resultRect.yMinimum(), expectedRect.yMinimum(), 0.001 ) );
QVERIFY( qgsDoubleNear( resultRect.xMaximum(), expectedRect.xMaximum(), 0.001 ) );
QVERIFY( qgsDoubleNear( resultRect.yMaximum(), expectedRect.yMaximum(), 0.001 ) );
}

QTEST_MAIN( TestQgsCoordinateTransform )
#include "moc_testqgscoordinatetransform.cxx"

0 comments on commit a2d5acb

Please sign in to comment.