Skip to content

Commit 298d047

Browse files
committed
[FEATURE] Expression functions for offset_curve and single_sided_buffer
Especially useful with geometry generators!
1 parent d008d31 commit 298d047

File tree

5 files changed

+89
-3
lines changed

5 files changed

+89
-3
lines changed
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "offset_curve",
3+
"type": "function",
4+
"description": "Returns a geometry formed by offseting a linestring geometry to the side. Distances are in the Spatial Reference System of this geometry.",
5+
"arguments": [ {"arg":"geometry","description":"a (multi)linestring geometry"},
6+
{"arg":"distance","description":"offset distance. Positive values will be buffered to the left of lines, negative values to the right"},
7+
{"arg":"segments","optional":true,"default":"8","description":"number of segments to use to represent a quarter circle when a round join style is used. A larger number results in a smoother line with more nodes."},
8+
{"arg":"join","optional":true,"default":"1","description":"join style for corners, where 1 = round, 2 = mitre and 3 = bevel"},
9+
{"arg":"mitre_limit","optional":true,"default":"2.0","description":"limit on the mitre ratio used for very sharp corners (when using mitre joins only)"}],
10+
"examples": [ { "expression":"offset_curve($geometry, 10.5)", "returns":"line offset to the left by 10.5 units"},
11+
{ "expression":"offset_curve($geometry, -10.5)", "returns":"line offset to the right by 10.5 units"},
12+
{ "expression":"offset_curve($geometry, 10.5, segments=16, join=1)", "returns":"line offset to the left by 10.5 units, using more segments to result in a smoother curve"},
13+
{ "expression":"offset_curve($geometry, 10.5, join=3)", "returns":"line offset to the left by 10.5 units, using a beveled join"}]
14+
}
15+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "single_sided_buffer",
3+
"type": "function",
4+
"description": "Returns a geometry formed by buffering out just one side of a linestring geometry. Distances are in the Spatial Reference System of this geometry.",
5+
"arguments": [ {"arg":"geometry","description":"a (multi)linestring geometry"},
6+
{"arg":"distance","description":"buffer distance. Positive values will be buffered to the left of lines, negative values to the right"},
7+
{"arg":"segments","optional":true,"default":"8","description":"number of segments to use to represent a quarter circle when a round join style is used. A larger number results in a smoother buffer with more nodes."},
8+
{"arg":"join","optional":true,"default":"1","description":"join style for corners, where 1 = round, 2 = mitre and 3 = bevel"},
9+
{"arg":"mitre_limit","optional":true,"default":"2.0","description":"limit on the mitre ratio used for very sharp corners (when using mitre joins only)"}],
10+
"examples": [ { "expression":"single_sided_buffer($geometry, 10.5)", "returns":"line buffered to the left by 10.5 units"},
11+
{ "expression":"single_sided_buffer($geometry, -10.5)", "returns":"line buffered to the right by 10.5 units"},
12+
{ "expression":"single_sided_buffer($geometry, 10.5, segments=16, join=1)", "returns":"line buffered to the left by 10.5 units, using more segments to result in a smoother buffer"},
13+
{ "expression":"single_sided_buffer($geometry, 10.5, join=3)", "returns":"line buffered to the left by 10.5 units, using a beveled join"}]
14+
}
15+

src/core/geometry/qgsgeometry.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ QgsGeometry QgsGeometry::buffer( double distance, int segments, EndCapStyle endC
13141314

13151315
QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, JoinStyle joinStyle, double mitreLimit ) const
13161316
{
1317-
if ( !d->geometry )
1317+
if ( !d->geometry || type() != QgsWkbTypes::LineGeometry )
13181318
{
13191319
return QgsGeometry();
13201320
}
@@ -1353,7 +1353,7 @@ QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, JoinStyle j
13531353

13541354
QgsGeometry QgsGeometry::singleSidedBuffer( double distance, int segments, BufferSide side , JoinStyle joinStyle, double mitreLimit ) const
13551355
{
1356-
if ( !d->geometry )
1356+
if ( !d->geometry || type() != QgsWkbTypes::LineGeometry )
13571357
{
13581358
return QgsGeometry();
13591359
}

src/core/qgsexpression.cpp

+45-1
Original file line numberDiff line numberDiff line change
@@ -2212,6 +2212,37 @@ static QVariant fcnBuffer( const QVariantList& values, const QgsExpressionContex
22122212
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
22132213
return result;
22142214
}
2215+
2216+
static QVariant fcnOffsetCurve( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
2217+
{
2218+
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
2219+
double dist = getDoubleValue( values.at( 1 ), parent );
2220+
int segments = getIntValue( values.at( 2 ), parent );
2221+
QgsGeometry::JoinStyle join = static_cast< QgsGeometry::JoinStyle >( getIntValue( values.at( 3 ), parent ) );
2222+
if ( join < QgsGeometry::JoinStyleRound || join > QgsGeometry::JoinStyleBevel )
2223+
return QVariant();
2224+
double mitreLimit = getDoubleValue( values.at( 3 ), parent );
2225+
2226+
QgsGeometry geom = fGeom.offsetCurve( dist, segments, join, mitreLimit );
2227+
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
2228+
return result;
2229+
}
2230+
2231+
static QVariant fcnSingleSidedBuffer( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
2232+
{
2233+
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
2234+
double dist = getDoubleValue( values.at( 1 ), parent );
2235+
int segments = getIntValue( values.at( 2 ), parent );
2236+
QgsGeometry::JoinStyle join = static_cast< QgsGeometry::JoinStyle >( getIntValue( values.at( 3 ), parent ) );
2237+
if ( join < QgsGeometry::JoinStyleRound || join > QgsGeometry::JoinStyleBevel )
2238+
return QVariant();
2239+
double mitreLimit = getDoubleValue( values.at( 3 ), parent );
2240+
2241+
QgsGeometry geom = fGeom.singleSidedBuffer( dist, segments, QgsGeometry::SideLeft, join, mitreLimit );
2242+
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
2243+
return result;
2244+
}
2245+
22152246
static QVariant fcnTranslate( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
22162247
{
22172248
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
@@ -3084,7 +3115,8 @@ const QStringList& QgsExpression::BuiltinFunctions()
30843115
<< "geom_from_gml" << "geomFromGML" << "intersects_bbox" << "bbox"
30853116
<< "disjoint" << "intersects" << "touches" << "crosses" << "contains"
30863117
<< "relate"
3087-
<< "overlaps" << "within" << "buffer" << "centroid" << "bounds" << "reverse" << "exterior_ring"
3118+
<< "overlaps" << "within" << "buffer" << "offset_curve" << "single_sided_buffer"
3119+
<< "centroid" << "bounds" << "reverse" << "exterior_ring"
30883120
<< "boundary" << "line_merge"
30893121
<< "bounds_width" << "bounds_height" << "is_closed" << "convex_hull" << "difference"
30903122
<< "distance" << "intersection" << "sym_difference" << "combine"
@@ -3271,6 +3303,18 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
32713303
<< new StaticFunction( "within", 2, fcnWithin, "GeometryGroup" )
32723304
<< new StaticFunction( "translate", 3, fcnTranslate, "GeometryGroup" )
32733305
<< new StaticFunction( "buffer", -1, fcnBuffer, "GeometryGroup" )
3306+
<< new StaticFunction( "offset_curve", ParameterList() << Parameter( "geometry" )
3307+
<< Parameter( "distance" )
3308+
<< Parameter( "segments", true, 8.0 )
3309+
<< Parameter( "join", true, QgsGeometry::JoinStyleRound )
3310+
<< Parameter( "mitre_limit", true, 2.0 ),
3311+
fcnOffsetCurve, "GeometryGroup" )
3312+
<< new StaticFunction( "single_sided_buffer", ParameterList() << Parameter( "geometry" )
3313+
<< Parameter( "distance" )
3314+
<< Parameter( "segments", true, 8.0 )
3315+
<< Parameter( "join", true, QgsGeometry::JoinStyleRound )
3316+
<< Parameter( "mitre_limit", true, 2.0 ),
3317+
fcnSingleSidedBuffer, "GeometryGroup" )
32743318
<< new StaticFunction( "centroid", 1, fcnCentroid, "GeometryGroup" )
32753319
<< new StaticFunction( "point_on_surface", 1, fcnPointOnSurface, "GeometryGroup" )
32763320
<< new StaticFunction( "reverse", 1, fcnReverse, "GeometryGroup" )

tests/src/core/testqgsexpression.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -677,6 +677,18 @@ class TestQgsExpression: public QObject
677677
QTest::newRow( "line_merge point" ) << "line_merge(geom_from_wkt('POINT(1 2)'))" << false << QVariant();
678678
QTest::newRow( "line_merge line" ) << "geom_to_wkt(line_merge(geometry:=geom_from_wkt('LineString(0 0, 10 10)')))" << false << QVariant( "LineString (0 0, 10 10)" );
679679
QTest::newRow( "line_merge multiline" ) << "geom_to_wkt(line_merge(geom_from_wkt('MultiLineString((0 0, 10 10),(10 10, 20 20))')))" << false << QVariant( "LineString (0 0, 10 10, 20 20)" );
680+
QTest::newRow( "offset_curve not geom" ) << "offset_curve('g', 5)" << true << QVariant();
681+
QTest::newRow( "offset_curve null" ) << "offset_curve(NULL, 5)" << false << QVariant();
682+
QTest::newRow( "offset_curve point" ) << "offset_curve(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant();
683+
QTest::newRow( "offset_curve line" ) << "geom_to_wkt(offset_curve(geom_from_wkt('LineString(0 0, 10 0)'),1,segments:=4))" << false << QVariant( "LineString (0 1, 10 1)" );
684+
QTest::newRow( "offset_curve line mitre" ) << "geom_to_wkt(offset_curve(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),distance:=-1,join:=2,mitre_limit:=1))" << false << QVariant( "LineString (10 -1, 0 -1)" );
685+
QTest::newRow( "offset_curve line bevel" ) << "geom_to_wkt(offset_curve(geometry:=geom_from_wkt('LineString(0 0, 10 0, 10 10)'),distance:=1,join:=3))" << false << QVariant( "LineString (0 1, 9 1, 9 10)" );
686+
QTest::newRow( "single_sided_buffer not geom" ) << "single_sided_buffer('g', 5)" << true << QVariant();
687+
QTest::newRow( "single_sided_buffer null" ) << "single_sided_buffer(NULL, 5)" << false << QVariant();
688+
QTest::newRow( "single_sided_buffer point" ) << "single_sided_buffer(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant();
689+
QTest::newRow( "single_sided_buffer line" ) << "geom_to_wkt(single_sided_buffer(geom_from_wkt('LineString(0 0, 10 0)'),1,segments:=4))" << false << QVariant( "Polygon ((10 0, 0 0, 0 1, 10 1, 10 0))" );
690+
QTest::newRow( "single_sided_buffer line mitre" ) << "geom_to_wkt(single_sided_buffer(geometry:=geom_from_wkt('LineString(0 0, 10 0)'),distance:=-1,join:=2,mitre_limit:=1))" << false << QVariant( "Polygon ((0 0, 10 0, 10 -1, 0 -1, 0 0))" );
691+
QTest::newRow( "single_sided_buffer line bevel" ) << "geom_to_wkt(single_sided_buffer(geometry:=geom_from_wkt('LineString(0 0, 10 0, 10 10)'),distance:=1,join:=3))" << false << QVariant( "Polygon ((10 10, 10 0, 0 0, 0 1, 9 1, 9 10, 10 10))" );
680692
QTest::newRow( "start_point point" ) << "geom_to_wkt(start_point(geom_from_wkt('POINT(2 0)')))" << false << QVariant( "Point (2 0)" );
681693
QTest::newRow( "start_point multipoint" ) << "geom_to_wkt(start_point(geom_from_wkt('MULTIPOINT((3 3), (1 1), (2 2))')))" << false << QVariant( "Point (3 3)" );
682694
QTest::newRow( "start_point line" ) << "geom_to_wkt(start_point(geom_from_wkt('LINESTRING(4 1, 1 1, 2 2)')))" << false << QVariant( "Point (4 1)" );

0 commit comments

Comments
 (0)