mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
[FEATURE] Expose GEOS single sided buffer through QgsGeometry
Makes it easy for PyQGIS code to perform a single sided buffer operation
This commit is contained in:
parent
616a80f48e
commit
e3f0d3d88f
@ -447,27 +447,67 @@ class QgsGeometry
|
||||
* @note added in 1.5 */
|
||||
bool crosses( const QgsGeometry& geometry ) const;
|
||||
|
||||
//! Side of line to buffer
|
||||
enum BufferSide
|
||||
{
|
||||
SideLeft, //!< Buffer to left of line
|
||||
SideRight, //!< Buffer to right of line
|
||||
};
|
||||
|
||||
//! End cap styles for buffers
|
||||
enum EndCapStyle
|
||||
{
|
||||
CapRound, //!< Round cap
|
||||
CapFlat, //!< Flat cap (in line with start/end of line)
|
||||
CapSquare, //!< Square cap (extends past start/end of line by buffer distance)
|
||||
};
|
||||
|
||||
//! Join styles for buffers
|
||||
enum JoinStyle
|
||||
{
|
||||
JoinStyleRound, //!< Use rounded joins
|
||||
JoinStyleMitre, //!< Use mitred joins
|
||||
JoinStyleBevel, //!< Use beveled joins
|
||||
};
|
||||
|
||||
/** Returns a buffer region around this geometry having the given width and with a specified number
|
||||
of segments used to approximate curves */
|
||||
QgsGeometry buffer( double distance, int segments ) const;
|
||||
|
||||
/** Returns a buffer region around the geometry, with additional style options.
|
||||
* @param distance buffer distance
|
||||
* @param segments For round joins, number of segments to approximate quarter-circle
|
||||
* @param endCapStyle Round (1) / Flat (2) / Square (3) end cap style
|
||||
* @param joinStyle Round (1) / Mitre (2) / Bevel (3) join style
|
||||
* @param mitreLimit Limit on the mitre ratio used for very sharp corners
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param endCapStyle end cap style
|
||||
* @param joinStyle join style for corners in geometry
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners (JoinStyleMitre only)
|
||||
* @note added in 2.4
|
||||
* @note needs GEOS >= 3.3 - otherwise always returns 0
|
||||
*/
|
||||
QgsGeometry buffer( double distance, int segments, int endCapStyle, int joinStyle, double mitreLimit ) const;
|
||||
QgsGeometry buffer( double distance, int segments, EndCapStyle endCapStyle, JoinStyle joinStyle, double mitreLimit ) const;
|
||||
|
||||
/** Returns an offset line at a given distance and side from an input line.
|
||||
* See buffer() method for details on parameters.
|
||||
* @param distance buffer distance
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param joinStyle join style for corners in geometry
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners (JoinStyleMitre only)
|
||||
* @note added in 2.4
|
||||
* @note needs GEOS >= 3.3 - otherwise always returns 0
|
||||
*/
|
||||
QgsGeometry offsetCurve( double distance, int segments, int joinStyle, double mitreLimit ) const;
|
||||
QgsGeometry offsetCurve( double distance, int segments, JoinStyle joinStyle, double mitreLimit ) const;
|
||||
|
||||
/**
|
||||
* Returns a single sided buffer for a (multi)line geometry. The buffer is only
|
||||
* applied to one side of the line.
|
||||
* @param distance buffer distance
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param side side of geometry to buffer
|
||||
* @param joinStyle join style for corners
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners
|
||||
* @return buffered geometry, or an empty geometry if buffer could not be
|
||||
* calculated
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
QgsGeometry singleSidedBuffer( double distance, int segments, BufferSide side,
|
||||
JoinStyle joinStyle = JoinStyleRound,
|
||||
double mitreLimit = 2.0 ) const;
|
||||
|
||||
/** Returns a simplified version of this geometry using a specified tolerance value */
|
||||
QgsGeometry simplify( double tolerance ) const;
|
||||
|
@ -1296,7 +1296,7 @@ QgsGeometry QgsGeometry::buffer( double distance, int segments ) const
|
||||
return QgsGeometry( geom );
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::buffer( double distance, int segments, int endCapStyle, int joinStyle, double mitreLimit ) const
|
||||
QgsGeometry QgsGeometry::buffer( double distance, int segments, EndCapStyle endCapStyle, JoinStyle joinStyle, double mitreLimit ) const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
{
|
||||
@ -1312,7 +1312,7 @@ QgsGeometry QgsGeometry::buffer( double distance, int segments, int endCapStyle,
|
||||
return QgsGeometry( geom );
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, int joinStyle, double mitreLimit ) const
|
||||
QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, JoinStyle joinStyle, double mitreLimit ) const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
{
|
||||
@ -1351,6 +1351,46 @@ QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, int joinSty
|
||||
}
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::singleSidedBuffer( double distance, int segments, BufferSide side , JoinStyle joinStyle, double mitreLimit ) const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
{
|
||||
return QgsGeometry();
|
||||
}
|
||||
|
||||
if ( QgsWkbTypes::isMultiType( d->geometry->wkbType() ) )
|
||||
{
|
||||
QList<QgsGeometry> parts = asGeometryCollection();
|
||||
QList<QgsGeometry> results;
|
||||
Q_FOREACH ( const QgsGeometry& part, parts )
|
||||
{
|
||||
QgsGeometry result = part.singleSidedBuffer( distance, segments, side, joinStyle, mitreLimit );
|
||||
if ( result )
|
||||
results << result;
|
||||
}
|
||||
if ( results.isEmpty() )
|
||||
return QgsGeometry();
|
||||
|
||||
QgsGeometry first = results.takeAt( 0 );
|
||||
Q_FOREACH ( const QgsGeometry& result, results )
|
||||
{
|
||||
first.addPart( & result );
|
||||
}
|
||||
return first;
|
||||
}
|
||||
else
|
||||
{
|
||||
QgsGeos geos( d->geometry );
|
||||
QgsAbstractGeometry* bufferGeom = geos.singleSidedBuffer( distance, segments, side,
|
||||
joinStyle, mitreLimit );
|
||||
if ( !bufferGeom )
|
||||
{
|
||||
return QgsGeometry();
|
||||
}
|
||||
return QgsGeometry( bufferGeom );
|
||||
}
|
||||
}
|
||||
|
||||
QgsGeometry QgsGeometry::simplify( double tolerance ) const
|
||||
{
|
||||
if ( !d->geometry )
|
||||
|
@ -484,27 +484,67 @@ class CORE_EXPORT QgsGeometry
|
||||
* @note added in 1.5 */
|
||||
bool crosses( const QgsGeometry& geometry ) const;
|
||||
|
||||
//! Side of line to buffer
|
||||
enum BufferSide
|
||||
{
|
||||
SideLeft = 0, //!< Buffer to left of line
|
||||
SideRight, //!< Buffer to right of line
|
||||
};
|
||||
|
||||
//! End cap styles for buffers
|
||||
enum EndCapStyle
|
||||
{
|
||||
CapRound = 1, //!< Round cap
|
||||
CapFlat, //!< Flat cap (in line with start/end of line)
|
||||
CapSquare, //!< Square cap (extends past start/end of line by buffer distance)
|
||||
};
|
||||
|
||||
//! Join styles for buffers
|
||||
enum JoinStyle
|
||||
{
|
||||
JoinStyleRound = 1, //!< Use rounded joins
|
||||
JoinStyleMitre, //!< Use mitred joins
|
||||
JoinStyleBevel, //!< Use beveled joins
|
||||
};
|
||||
|
||||
/** Returns a buffer region around this geometry having the given width and with a specified number
|
||||
of segments used to approximate curves */
|
||||
QgsGeometry buffer( double distance, int segments ) const;
|
||||
|
||||
/** Returns a buffer region around the geometry, with additional style options.
|
||||
* @param distance buffer distance
|
||||
* @param segments For round joins, number of segments to approximate quarter-circle
|
||||
* @param endCapStyle Round (1) / Flat (2) / Square (3) end cap style
|
||||
* @param joinStyle Round (1) / Mitre (2) / Bevel (3) join style
|
||||
* @param mitreLimit Limit on the mitre ratio used for very sharp corners
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param endCapStyle end cap style
|
||||
* @param joinStyle join style for corners in geometry
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners (JoinStyleMitre only)
|
||||
* @note added in 2.4
|
||||
* @note needs GEOS >= 3.3 - otherwise always returns 0
|
||||
*/
|
||||
QgsGeometry buffer( double distance, int segments, int endCapStyle, int joinStyle, double mitreLimit ) const;
|
||||
QgsGeometry buffer( double distance, int segments, EndCapStyle endCapStyle, JoinStyle joinStyle, double mitreLimit ) const;
|
||||
|
||||
/** Returns an offset line at a given distance and side from an input line.
|
||||
* See buffer() method for details on parameters.
|
||||
* @param distance buffer distance
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param joinStyle join style for corners in geometry
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners (JoinStyleMitre only)
|
||||
* @note added in 2.4
|
||||
* @note needs GEOS >= 3.3 - otherwise always returns 0
|
||||
*/
|
||||
QgsGeometry offsetCurve( double distance, int segments, int joinStyle, double mitreLimit ) const;
|
||||
QgsGeometry offsetCurve( double distance, int segments, JoinStyle joinStyle, double mitreLimit ) const;
|
||||
|
||||
/**
|
||||
* Returns a single sided buffer for a (multi)line geometry. The buffer is only
|
||||
* applied to one side of the line.
|
||||
* @param distance buffer distance
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param side side of geometry to buffer
|
||||
* @param joinStyle join style for corners
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners
|
||||
* @return buffered geometry, or an empty geometry if buffer could not be
|
||||
* calculated
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
QgsGeometry singleSidedBuffer( double distance, int segments, BufferSide side,
|
||||
JoinStyle joinStyle = JoinStyleRound,
|
||||
double mitreLimit = 2.0 ) const;
|
||||
|
||||
/** Returns a simplified version of this geometry using a specified tolerance value */
|
||||
QgsGeometry simplify( double tolerance ) const;
|
||||
|
@ -1679,6 +1679,33 @@ QgsAbstractGeometry* QgsGeos::offsetCurve( double distance, int segments, int jo
|
||||
return offsetGeom;
|
||||
}
|
||||
|
||||
QgsAbstractGeometry* QgsGeos::singleSidedBuffer( double distance, int segments, int side, int joinStyle, double mitreLimit, QString* errorMsg ) const
|
||||
{
|
||||
if ( !mGeos )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GEOSGeomScopedPtr geos;
|
||||
try
|
||||
{
|
||||
GEOSBufferParams* bp = GEOSBufferParams_create_r( geosinit.ctxt );
|
||||
GEOSBufferParams_setSingleSided_r( geosinit.ctxt, bp, 1 );
|
||||
GEOSBufferParams_setQuadrantSegments_r( geosinit.ctxt, bp, segments );
|
||||
GEOSBufferParams_setJoinStyle_r( geosinit.ctxt, bp, joinStyle );
|
||||
GEOSBufferParams_setMitreLimit_r( geosinit.ctxt, bp, mitreLimit );
|
||||
|
||||
if ( side == 1 )
|
||||
{
|
||||
distance = -distance;
|
||||
}
|
||||
geos.reset( GEOSBufferWithParams_r( geosinit.ctxt, mGeos, bp, distance ) );
|
||||
GEOSBufferParams_destroy_r( geosinit.ctxt, bp );
|
||||
}
|
||||
CATCH_GEOS_WITH_ERRMSG( nullptr );
|
||||
return fromGeos( geos.get() );
|
||||
}
|
||||
|
||||
QgsAbstractGeometry* QgsGeos::reshapeGeometry( const QgsLineString& reshapeWithLine, int* errorCode, QString* errorMsg ) const
|
||||
{
|
||||
if ( !mGeos || reshapeWithLine.numPoints() < 2 || mGeometry->dimension() == 0 )
|
||||
|
@ -85,6 +85,24 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
|
||||
QString* errorMsg = nullptr ) const override;
|
||||
|
||||
QgsAbstractGeometry* offsetCurve( double distance, int segments, int joinStyle, double mitreLimit, QString* errorMsg = nullptr ) const override;
|
||||
|
||||
/**
|
||||
* Returns a single sided buffer for a geometry. The buffer is only
|
||||
* applied to one side of the geometry.
|
||||
* @param distance buffer distance
|
||||
* @param segments for round joins, number of segments to approximate quarter-circle
|
||||
* @param side side of geometry to buffer (0 = left, 1 = right)
|
||||
* @param joinStyle join style for corners ( Round (1) / Mitre (2) / Bevel (3) )
|
||||
* @param mitreLimit limit on the mitre ratio used for very sharp corners
|
||||
* @return buffered geometry, or an nullptr if buffer could not be
|
||||
* calculated
|
||||
* @note added in QGIS 3.0
|
||||
*/
|
||||
QgsAbstractGeometry* singleSidedBuffer( double distance, int segments, int side,
|
||||
int joinStyle, double mitreLimit,
|
||||
QString* errorMsg = nullptr ) const;
|
||||
|
||||
|
||||
QgsAbstractGeometry* reshapeGeometry( const QgsLineString& reshapeWithLine, int* errorCode, QString* errorMsg = nullptr ) const;
|
||||
|
||||
/** Merges any connected lines in a LineString/MultiLineString geometry and
|
||||
|
@ -718,9 +718,10 @@ QList<QPolygonF> offsetLine( QPolygonF polyline, double dist, QgsWkbTypes::Geome
|
||||
double mitreLimit = 2.0; // the default value in GEOS (5.0) allows for fairly sharp endings
|
||||
QgsGeometry offsetGeom;
|
||||
if ( geometryType == QgsWkbTypes::PolygonGeometry )
|
||||
offsetGeom = tempGeometry.buffer( -dist, quadSegments, GEOSBUF_CAP_FLAT, GEOSBUF_JOIN_MITRE, mitreLimit );
|
||||
offsetGeom = tempGeometry.buffer( -dist, quadSegments, QgsGeometry::CapFlat,
|
||||
QgsGeometry::JoinStyleMitre, mitreLimit );
|
||||
else
|
||||
offsetGeom = tempGeometry.offsetCurve( dist, quadSegments, GEOSBUF_JOIN_MITRE, mitreLimit );
|
||||
offsetGeom = tempGeometry.offsetCurve( dist, quadSegments, QgsGeometry::JoinStyleMitre, mitreLimit );
|
||||
|
||||
if ( !offsetGeom.isEmpty() )
|
||||
{
|
||||
|
@ -3314,6 +3314,36 @@ class TestQgsGeometry(unittest.TestCase):
|
||||
expected_wkt = "CurvePolygon (CompoundCurve (CircularString (0 0, 1 1, 2 0),(2 0, 0 0)))"
|
||||
self.assertEqual(geom.exportToWkt(), QgsGeometry.fromWkt(expected_wkt).exportToWkt())
|
||||
|
||||
def testSingleSidedBuffer(self):
|
||||
|
||||
wkt = "LineString( 0 0, 10 0)"
|
||||
geom = QgsGeometry.fromWkt(wkt)
|
||||
out = geom.singleSidedBuffer(1, 8, QgsGeometry.SideLeft)
|
||||
result = out.exportToWkt()
|
||||
expected_wkt = "Polygon ((10 0, 0 0, 0 1, 10 1, 10 0))"
|
||||
self.assertTrue(compareWkt(result, expected_wkt, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(expected_wkt, result))
|
||||
|
||||
wkt = "LineString( 0 0, 10 0)"
|
||||
geom = QgsGeometry.fromWkt(wkt)
|
||||
out = geom.singleSidedBuffer(1, 8, QgsGeometry.SideRight)
|
||||
result = out.exportToWkt()
|
||||
expected_wkt = "Polygon ((0 0, 10 0, 10 -1, 0 -1, 0 0))"
|
||||
self.assertTrue(compareWkt(result, expected_wkt, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(expected_wkt, result))
|
||||
|
||||
wkt = "LineString( 0 0, 10 0, 10 10)"
|
||||
geom = QgsGeometry.fromWkt(wkt)
|
||||
out = geom.singleSidedBuffer(1, 8, QgsGeometry.SideRight, QgsGeometry.JoinStyleMitre)
|
||||
result = out.exportToWkt()
|
||||
expected_wkt = "Polygon ((0 0, 10 0, 10 10, 11 10, 11 -1, 0 -1, 0 0))"
|
||||
self.assertTrue(compareWkt(result, expected_wkt, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(expected_wkt, result))
|
||||
|
||||
wkt = "LineString( 0 0, 10 0, 10 10)"
|
||||
geom = QgsGeometry.fromWkt(wkt)
|
||||
out = geom.singleSidedBuffer(1, 8, QgsGeometry.SideRight, QgsGeometry.JoinStyleBevel)
|
||||
result = out.exportToWkt()
|
||||
expected_wkt = "Polygon ((0 0, 10 0, 10 10, 11 10, 11 0, 10 -1, 0 -1, 0 0))"
|
||||
self.assertTrue(compareWkt(result, expected_wkt, 0.00001), "Merge lines: mismatch Expected:\n{}\nGot:\n{}\n".format(expected_wkt, result))
|
||||
|
||||
def testMisc(self):
|
||||
|
||||
# Test that we cannot add a CurvePolygon in a MultiPolygon
|
||||
|
Loading…
x
Reference in New Issue
Block a user