Skip to content

Commit 6ab718d

Browse files
committed
QgsGeometryUtils fixes
- add QgsGeometryUtils::normalizedAngle for restricting an angle to [0, 2PI) - fix calculations of QgsGeometryUtils::averageAngle, was returning perpendicular angle and angles > 2Pi - improve docs and add unit tests
1 parent a0405b8 commit 6ab718d

File tree

3 files changed

+96
-30
lines changed

3 files changed

+96
-30
lines changed

src/core/geometry/qgsgeometryutils.cpp

+21-26
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,20 @@ QString QgsGeometryUtils::pointsToJSON( const QList<QgsPointV2>& points, int pre
613613
return json;
614614
}
615615

616+
double QgsGeometryUtils::normalizedAngle( double angle )
617+
{
618+
double clippedAngle = angle;
619+
if ( clippedAngle >= M_PI * 2 || clippedAngle <= -2 * M_PI )
620+
{
621+
clippedAngle = fmod( clippedAngle, 2 * M_PI );
622+
}
623+
if ( clippedAngle < 0.0 )
624+
{
625+
clippedAngle += 2 * M_PI;
626+
}
627+
return clippedAngle;
628+
}
629+
616630
QPair<QgsWKBTypes::Type, QString> QgsGeometryUtils::wktReadBlock( const QString &wkt )
617631
{
618632
QgsWKBTypes::Type wkbType = QgsWKBTypes::parseType( wkt );
@@ -662,38 +676,28 @@ double QgsGeometryUtils::lineAngle( double x1, double y1, double x2, double y2 )
662676
{
663677
double at = atan2( y2 - y1, x2 - x1 );
664678
double a = -at + M_PI / 2.0;
665-
if ( a < 0 )
666-
{
667-
a = 2 * M_PI + a;
668-
}
669-
if ( a >= 2 * M_PI )
670-
{
671-
a -= 2 * M_PI;
672-
}
673-
return a;
679+
return normalizedAngle( a );
674680
}
675681

676682
double QgsGeometryUtils::linePerpendicularAngle( double x1, double y1, double x2, double y2 )
677683
{
678684
double a = lineAngle( x1, y1, x2, y2 );
679685
a += ( M_PI / 2.0 );
680-
if ( a >= 2 * M_PI )
681-
{
682-
a -= ( 2 * M_PI );
683-
}
684-
return a;
686+
return normalizedAngle( a );
685687
}
686688

687689
double QgsGeometryUtils::averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 )
688690
{
689691
// calc average angle between the previous and next point
690-
double a1 = linePerpendicularAngle( x1, y1, x2, y2 );
691-
double a2 = linePerpendicularAngle( x2, y2, x3, y3 );
692+
double a1 = lineAngle( x1, y1, x2, y2 );
693+
double a2 = lineAngle( x2, y2, x3, y3 );
692694
return averageAngle( a1, a2 );
693695
}
694696

695697
double QgsGeometryUtils::averageAngle( double a1, double a2 )
696698
{
699+
a1 = normalizedAngle( a1 );
700+
a2 = normalizedAngle( a2 );
697701
double clockwiseDiff = 0.0;
698702
if ( a2 >= a1 )
699703
{
@@ -714,14 +718,5 @@ double QgsGeometryUtils::averageAngle( double a1, double a2 )
714718
{
715719
resultAngle = a1 - counterClockwiseDiff / 2.0;
716720
}
717-
718-
if ( resultAngle >= 2 * M_PI )
719-
{
720-
resultAngle -= 2 * M_PI;
721-
}
722-
else if ( resultAngle < 0 )
723-
{
724-
resultAngle = 2 * M_PI - resultAngle;
725-
}
726-
return resultAngle;
721+
return normalizedAngle( resultAngle );
727722
}

src/core/geometry/qgsgeometryutils.h

+30-4
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,41 @@ class CORE_EXPORT QgsGeometryUtils
151151
static QDomElement pointsToGML3( const QList<QgsPointV2>& points, QDomDocument &doc, int precision, const QString& ns, bool is3D );
152152
/** Returns a geoJSON coordinates string */
153153
static QString pointsToJSON( const QList<QgsPointV2>& points, int precision );
154-
/** Calculates direction of line (clockwise from north direction) in radians*/
154+
155+
/** Ensures that an angle is in the range 0 <= angle < 2 pi.
156+
* @param angle angle in radians
157+
* @returns equivalent angle within the range [0, 2 pi)
158+
*/
159+
static double normalizedAngle( double angle );
160+
161+
/** Calculates the direction of line joining two points in radians, clockwise from the north direction.
162+
* @param x1 x-coordinate of line start
163+
* @param y1 y-coordinate of line start
164+
* @param x2 x-coordinate of line end
165+
* @param y2 y-coordinate of line end
166+
* @returns angle in radians. Returned value is undefined if start and end point are the same.
167+
*/
155168
static double lineAngle( double x1, double y1, double x2, double y2 );
156-
/** Calculates angle perpendicular to line*/
169+
170+
/** Calculates the perpendicular angle to a line joining two points. Returned angle is in radians,
171+
* clockwise from the north direction.
172+
* @param x1 x-coordinate of line start
173+
* @param y1 y-coordinate of line start
174+
* @param x2 x-coordinate of line end
175+
* @param y2 y-coordinate of line end
176+
* @returns angle in radians. Returned value is undefined if start and end point are the same.
177+
*/
157178
static double linePerpendicularAngle( double x1, double y1, double x2, double y2 );
179+
158180
/** Angle between two linear segments*/
159181
static double averageAngle( double x1, double y1, double x2, double y2, double x3, double y3 );
160-
/** Averages two angles*/
161-
static double averageAngle( double a1, double a2 );
162182

183+
/** Averages two angles, correctly handling negative angles and ensuring the result is between 0 and 2 pi.
184+
* @param a1 first angle (in radians)
185+
* @param a2 second angle (in radians)
186+
* @returns average angle (in radians)
187+
*/
188+
static double averageAngle( double a1, double a2 );
163189

164190
/** Parses a WKT block of the format "TYPE( contents )" and returns a pair of geometry type to contents ("Pair(wkbType, "contents")")
165191
*/

tests/src/core/testqgsgeometry.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
//qgis includes...
2929
#include <qgsapplication.h>
3030
#include <qgsgeometry.h>
31+
#include "qgsgeometryutils.h"
3132
#include <qgspoint.h>
3233
#include "qgspointv2.h"
3334
#include "qgslinestringv2.h"
@@ -56,6 +57,7 @@ class TestQgsGeometry : public QObject
5657
void isEmpty();
5758
void pointV2(); //test QgsPointV2
5859
void lineStringV2(); //test QgsLineStringV2
60+
void utils(); //test QgsGeometryUtils
5961

6062
void fromQgsPoint();
6163
void fromQPoint();
@@ -1842,6 +1844,49 @@ void TestQgsGeometry::lineStringV2()
18421844

18431845
}
18441846

1847+
void TestQgsGeometry::utils()
1848+
{
1849+
//test normalizedAngle
1850+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 0.0 ), 0.0, 0.0001 ) );
1851+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 1.5708 ), 1.5708, 0.0001 ) );
1852+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 3.1416 ), 3.1416, 0.0001 ) );
1853+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 4.7124 ), 4.7124, 0.0001 ) );
1854+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 2 * M_PI ), 0.0, 0.0001 ) );
1855+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 6.80678 ), 0.5236, 0.0001 ) );
1856+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 12.5664 ), 0.0, 0.0001 ) );
1857+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( 12.7409 ), 0.174533, 0.0001 ) );
1858+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( -0.174533 ), 6.10865, 0.0001 ) );
1859+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( -6.28318 ), 0.0, 0.0001 ) );
1860+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( -6.45772 ), 6.10865, 0.0001 ) );
1861+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::normalizedAngle( -13.2645 ), 5.58505, 0.0001 ) );
1862+
1863+
//test lineAngle
1864+
( void )QgsGeometryUtils::lineAngle( 0.0, 0.0, 0.0, 0.0 ); //undefined, but don't want a crash
1865+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::lineAngle( 0.0, 0.0, 0.0, 1.0 ), 0.0, 0.0001 ) );
1866+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::lineAngle( 0.0, 0.0, 1.0, 1.0 ), 0.7854, 0.0001 ) );
1867+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::lineAngle( 0.0, 0.0, 1.0, 0.0 ), 1.5708, 0.0001 ) );
1868+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::lineAngle( 0.0, 0.0, 0.0, -1.0 ), 3.1416, 0.0001 ) );
1869+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::lineAngle( 0.0, 0.0, -1.0, 0.0 ), 4.7124, 0.0001 ) );
1870+
1871+
//test linePerpendicularAngle
1872+
( void )QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, 0.0, 0.0 ); //undefined, but don't want a crash
1873+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, 0.0, 1.0 ), 1.5708, 0.0001 ) );
1874+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, 1.0, 1.0 ), 2.3562, 0.0001 ) );
1875+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, 1.0, 0.0 ), 3.1416, 0.0001 ) );
1876+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, 0.0, -1.0 ), 4.7124, 0.0001 ) );
1877+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::linePerpendicularAngle( 0.0, 0.0, -1.0, 0.0 ), 0.0, 0.0001 ) );
1878+
1879+
//test averageAngle
1880+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 0.0, 0.0 ), 0.0, 0.0001 ) );
1881+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 0.0, 6.28319 ), 0.0, 0.0001 ) );
1882+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 0.0, 12.5664 ), 0.0, 0.0001 ) );
1883+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 6.28319, 0.0 ), 0.0, 0.0001 ) );
1884+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( -6.28318, 0.0 ), 0.0, 0.0001 ) );
1885+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( -6.28318, -6.28318 ), 0.0, 0.0001 ) );
1886+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 0.0, 3.141592 ), 1.5708, 0.0001 ) );
1887+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 0.0, -3.141592 ), 4.71239, 0.0001 ) );
1888+
QVERIFY( qgsDoubleNear( QgsGeometryUtils::averageAngle( 5.49779, 4.71239 ), 5.1051, 0.0001 ) );
1889+
}
18451890

18461891
void TestQgsGeometry::fromQgsPoint()
18471892
{

0 commit comments

Comments
 (0)