Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wedge buffers support start and end angles #58473

Merged
merged 26 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions src/core/geometry/qgsgeometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,28 @@ QgsGeometry QgsGeometry::collectGeometry( const QVector< QgsGeometry > &geometri
return collected;
}

QgsGeometry QgsGeometry::createWedgeBuffer( const QgsPoint &center, const double azimuth, const double angularWidth, const double outerRadius, const double innerRadius )
QgsGeometry QgsGeometry::createWedgeBuffer( const QgsPoint &center, const double azimuth, const double angularWidth, const double outerRadius, const double innerRadius, const QgsCoordinateReferenceSystem &crs )
{
const double startAngle = azimuth - angularWidth * 0.5;
const double endAngle = azimuth + angularWidth * 0.5;

return createWedgeBufferFromAngles( center, startAngle, endAngle, outerRadius, innerRadius, crs );
}

QgsGeometry QgsGeometry::createWedgeBufferAngles( const QgsPoint &center, double startAngle, double endAngle, double outerRadius, double innerRadius, const QgsCoordinateReferenceSystem &crs )
{
std::unique_ptr< QgsCompoundCurve > wedge = std::make_unique< QgsCompoundCurve >();

startAngle = QgsGeometryUtils::normalizedAngle( startAngle * M_PI / 180 ) * 180 / M_PI;
endAngle = QgsGeometryUtils::normalizedAngle( endAngle * M_PI / 180 ) * 180 / M_PI;

double angularWidth = endAngle - startAngle;
double averageAngle = QgsGeometryUtils::averageAngle( endAngle * M_PI / 180, startAngle * M_PI / 180 ) * 180 / M_PI;

bool useShortestArc = angularWidth >= 0 && angularWidth <= 180.0 || angularWidth <= 180.0 && angularWidth >= -360.0;
if ( !useShortestArc )
averageAngle += 180;

if ( std::abs( angularWidth ) >= 360.0 )
{
std::unique_ptr< QgsCompoundCurve > outerCc = std::make_unique< QgsCompoundCurve >();
Expand All @@ -432,29 +452,54 @@ QgsGeometry QgsGeometry::createWedgeBuffer( const QgsPoint &center, const double
return QgsGeometry( std::move( cp ) );
}

std::unique_ptr< QgsCompoundCurve > wedge = std::make_unique< QgsCompoundCurve >();

const double startAngle = azimuth - angularWidth * 0.5;
const double endAngle = azimuth + angularWidth * 0.5;

const QgsPoint outerP1 = center.project( outerRadius, startAngle );
const QgsPoint outerP2 = center.project( outerRadius, endAngle );

const bool useShortestArc = angularWidth <= 180.0;
QgsDistanceArea da;
if ( crs.isValid() && crs.isGeographic() )
{
da.setSourceCrs( crs, QgsProject::instance()->transformContext() );
da.setEllipsoid( crs.ellipsoidAcronym() );
}

wedge->addCurve( new QgsCircularString( QgsCircularString::fromTwoPointsAndCenter( outerP1, outerP2, center, useShortestArc ) ) );
QgsPoint outerP1;
QgsPoint outerP2;
QgsPoint outerP3;
if ( crs.isGeographic() )
{
outerP1 = QgsPoint( da.computeSpheroidProject( center, outerRadius, startAngle * M_PI / 180 ) );
outerP2 = QgsPoint( da.computeSpheroidProject( center, outerRadius, averageAngle * M_PI / 180 ) );
outerP3 = QgsPoint( da.computeSpheroidProject( center, outerRadius, endAngle * M_PI / 180 ) );
}
else
{
outerP1 = center.project( outerRadius, startAngle );
outerP2 = center.project( outerRadius, averageAngle );
outerP3 = center.project( outerRadius, endAngle );
}
wedge->addCurve( new QgsCircularString( outerP1, outerP2, outerP3 ) );

if ( !qgsDoubleNear( innerRadius, 0.0 ) && innerRadius > 0 )
{
const QgsPoint innerP1 = center.project( innerRadius, startAngle );
const QgsPoint innerP2 = center.project( innerRadius, endAngle );
wedge->addCurve( new QgsLineString( outerP2, innerP2 ) );
wedge->addCurve( new QgsCircularString( QgsCircularString::fromTwoPointsAndCenter( innerP2, innerP1, center, useShortestArc ) ) );
QgsPoint innerP1;
QgsPoint innerP2;
QgsPoint innerP3;
if ( crs.isGeographic() )
{
innerP1 = QgsPoint( da.computeSpheroidProject( center, innerRadius, startAngle * M_PI / 180 ) );
innerP2 = QgsPoint( da.computeSpheroidProject( center, innerRadius, averageAngle * M_PI / 180 ) );
innerP3 = QgsPoint( da.computeSpheroidProject( center, innerRadius, endAngle * M_PI / 180 ) );
}
else
{
innerP1 = center.project( innerRadius, startAngle );
innerP2 = center.project( innerRadius, averageAngle );
innerP3 = center.project( innerRadius, endAngle );
}
wedge->addCurve( new QgsLineString( outerP3, innerP3 ) );
wedge->addCurve( new QgsCircularString( innerP3, innerP2, innerP1 ) );
wedge->addCurve( new QgsLineString( innerP1, outerP1 ) );
}
else
{
wedge->addCurve( new QgsLineString( outerP2, center ) );
wedge->addCurve( new QgsLineString( outerP3, center ) );
wedge->addCurve( new QgsLineString( center, outerP1 ) );
}

Expand Down Expand Up @@ -488,7 +533,6 @@ Qgis::WkbType QgsGeometry::wkbType() const
}
}


Qgis::GeometryType QgsGeometry::type() const
{
if ( !d->geometry )
Expand Down
23 changes: 22 additions & 1 deletion src/core/geometry/qgsgeometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ email : morb at ozemail dot com dot au

#include "qgis_core.h"
#include "qgis_sip.h"
#include "qgscoordinatereferencesystem.h"

#include "qgsabstractgeometry.h"
#include "qgspointxy.h"
Expand Down Expand Up @@ -337,13 +338,33 @@ class CORE_EXPORT QgsGeometry
* The outer radius of the buffer is specified via \a outerRadius, and optionally an
* \a innerRadius can also be specified.
*
* If the \a crs is given and is geographic, the wedge is projected along the ellipsoïd.
*
* The returned geometry will be a CurvePolygon geometry containing circular strings. It may
* need to be segmentized to convert to a standard Polygon geometry.
*
* \since QGIS 3.2
*/
static QgsGeometry createWedgeBuffer( const QgsPoint &center, double azimuth, double angularWidth,
double outerRadius, double innerRadius = 0 );
double outerRadius, double innerRadius = 0, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );

/**
* Creates a wedge shaped buffer from a \a center point.
*
* The wedges goes from the \a startAngle to \a endAngle in degrees.
*
* The outer radius of the buffer is specified via \a outerRadius, and optionally an
* \a innerRadius can also be specified.
*
* If the \a crs is given and is geographic, the wedge is projected along the ellipsoïd.
*
* The returned geometry will be a CurvePolygon geometry containing circular strings. It may
* need to be segmentized to convert to a standard Polygon geometry.
*
* \since QGIS 3.40
*/
static QgsGeometry createWedgeBufferFromAngles( const QgsPoint &center, double startAngle, double endAngle,
double outerRadius, double innerRadius = 0, const QgsCoordinateReferenceSystem &crs = QgsCoordinateReferenceSystem() );

/**
* Set the geometry, feeding in the buffer containing OGC Well-Known Binary and the buffer's length.
Expand Down
51 changes: 31 additions & 20 deletions tests/src/python/test_qgsgeometry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""QGIS Unit tests for QgsGeometry.
"""QGIS Unit tests for QgsGeometry.

.. note:: 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
Expand Down Expand Up @@ -6198,40 +6198,51 @@ def testClipped(self):
f"clipped: mismatch Expected:\n{exp}\nGot:\n{result}\n")

def testCreateWedgeBuffer(self):
tests = [[QgsPoint(1, 11), 0, 45, 2, 0,
invalidCrs = QgsCoordinateReferenceSystem()
wgs84 = QgsCoordinateReferenceSystem.fromOgcWmsCrs("EPSG:4326")
tests = [[QgsPoint(1, 11), 0, 45, -22.5, 22.5, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (0.23463313526982044 12.84775906502257392, 1 13, 1.76536686473017967 12.84775906502257392),(1.76536686473017967 12.84775906502257392, 1 11),(1 11, 0.23463313526982044 12.84775906502257392)))'],
[QgsPoint(1, 11), 90, 45, 2, 0,
[QgsPoint(1, 11), 90, 45, 67.5, 112.5, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (2.84775906502257348 11.76536686473017923, 3 11, 2.84775906502257348 10.23463313526982077),(2.84775906502257348 10.23463313526982077, 1 11),(1 11, 2.84775906502257348 11.76536686473017923)))'],
[QgsPoint(1, 11), 180, 90, 2, 0,
[QgsPoint(1, 11), 180, 90, 135, 225, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (2.41421356237309492 9.58578643762690419, 1.00000000000000022 9, -0.41421356237309492 9.58578643762690419),(-0.41421356237309492 9.58578643762690419, 1 11),(1 11, 2.41421356237309492 9.58578643762690419)))'],
[QgsPoint(1, 11), 0, 200, 2, 0,
[QgsPoint(1, 11), 0, 200, -100, 100, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (-0.96961550602441604 10.65270364466613984, 0.99999999999999956 13, 2.96961550602441626 10.65270364466613984),(2.96961550602441626 10.65270364466613984, 1 11),(1 11, -0.96961550602441604 10.65270364466613984)))'],
[QgsPoint(1, 11), 0, 45, 2, 1,
[QgsPoint(1, 11), 0, 45, -22.5, 22.5, 2, 1, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (0.23463313526982044 12.84775906502257392, 1 13, 1.76536686473017967 12.84775906502257392),(1.76536686473017967 12.84775906502257392, 1.38268343236508984 11.92387953251128607),CircularString (1.38268343236508984 11.92387953251128607, 0.99999999999999978 12, 0.61731656763491016 11.92387953251128607),(0.61731656763491016 11.92387953251128607, 0.23463313526982044 12.84775906502257392)))'],
[QgsPoint(1, 11), 0, 200, 2, 1,
[QgsPoint(1, 11), 0, 200, -100, 100, 2, 1, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (-0.96961550602441604 10.65270364466613984, 0.99999999999999956 13, 2.96961550602441626 10.65270364466613984),(2.96961550602441626 10.65270364466613984, 1.98480775301220813 10.82635182233306992),CircularString (1.98480775301220813 10.82635182233306992, 0.99999999999999978 12, 0.01519224698779198 10.82635182233306992),(0.01519224698779198 10.82635182233306992, -0.96961550602441604 10.65270364466613984)))'],
[QgsPoint(1, 11, 3), 0, 45, 2, 0,
[QgsPoint(1, 11, 3), 0, 45, -22.5, 22.5, 2, 0, invalidCrs,
'CurvePolygonZ (CompoundCurveZ (CircularStringZ (0.23463313526982044 12.84775906502257392 3, 1 13 3, 1.76536686473017967 12.84775906502257392 3),(1.76536686473017967 12.84775906502257392 3, 1 11 3),(1 11 3, 0.23463313526982044 12.84775906502257392 3)))'],
[QgsPoint(1, 11, m=3), 0, 45, 2, 0,
[QgsPoint(1, 11, m=3), 0, 45, -22.5, 22.5, 2, 0, invalidCrs,
'CurvePolygonM (CompoundCurveM (CircularStringM (0.23463313526982044 12.84775906502257392 3, 1 13 3, 1.76536686473017967 12.84775906502257392 3),(1.76536686473017967 12.84775906502257392 3, 1 11 3),(1 11 3, 0.23463313526982044 12.84775906502257392 3)))'],
[QgsPoint(1, 11), 0, 360, 2, 0,
[QgsPoint(1, 11), 0, 360, -180, 180, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (1 13, 3 11, 1 9, -1 11, 1 13)))'],
[QgsPoint(1, 11), 0, -1000, 2, 0,
[QgsPoint(1, 11), 0, -1000, 0, 360, 2, 0, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (1 13, 3 11, 1 9, -1 11, 1 13)))'],
[QgsPoint(1, 11), 0, 360, 2, 1,
[QgsPoint(1, 11), 0, 360, -180, 180, 2, 1, invalidCrs,
'CurvePolygon (CompoundCurve (CircularString (1 13, 3 11, 1 9, -1 11, 1 13)),CompoundCurve (CircularString (1 12, 2 11, 1 10, 0 11, 1 12)))'],
[QgsPoint(1, 11), 10, 40, 350, 30, 2, 1, wgs84,
'CurvePolygon (CompoundCurve (CircularString (1 13, 3 11, 1 9, -1 11, 1 13)),CompoundCurve (CircularString (1 12, 2 11, 1 10, 0 11, 1 12)))']
]
for t in tests:
input = t[0]
point = t[0]
azimuth = t[1]
width = t[2]
outer = t[3]
inner = t[4]
o = QgsGeometry.createWedgeBuffer(input, azimuth, width, outer, inner)
exp = t[5]
result = o.asWkt()
self.assertTrue(compareWkt(result, exp, 0.01),
f"wedge buffer: mismatch Expected:\n{exp}\nGot:\n{result}\n")
startAngle = t[3]
endAngle = t[4]
outer = t[5]
inner = t[6]
crs = t[7]
o1 = QgsGeometry.createWedgeBuffer(point, azimuth, width, outer, inner, crs)
o2 = QgsGeometry.createWedgeBufferFromAngles(point, azimuth, width, outer, inner, crs)
exp = t[8]
result1 = o1.asWkt()
result2 = o2.asWkt()
self.assertTrue(compareWkt(result1, exp, 0.01),
f"wedge buffer from azimuth: mismatch Expected:\n{exp}\nGot:\n{result1}\n")
self.assertTrue(compareWkt(result2, exp, 0.01),
f"wedge buffer from angles: mismatch Expected:\n{exp}\nGot:\n{result2}\n")

def testTaperedBuffer(self):
tests = [['LineString (6 2, 9 2, 9 3, 11 5)', 1, 2, 3,
Expand Down
Loading