Skip to content

Commit

Permalink
[FEATURE] Expression functions for offset_curve and single_sided_buffer
Browse files Browse the repository at this point in the history
Especially useful with geometry generators!
  • Loading branch information
nyalldawson committed Aug 16, 2016
1 parent d008d31 commit 298d047
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 3 deletions.
15 changes: 15 additions & 0 deletions resources/function_help/json/offset_curve
@@ -0,0 +1,15 @@
{
"name": "offset_curve",
"type": "function",
"description": "Returns a geometry formed by offseting a linestring geometry to the side. Distances are in the Spatial Reference System of this geometry.",
"arguments": [ {"arg":"geometry","description":"a (multi)linestring geometry"},
{"arg":"distance","description":"offset distance. Positive values will be buffered to the left of lines, negative values to the right"},
{"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."},
{"arg":"join","optional":true,"default":"1","description":"join style for corners, where 1 = round, 2 = mitre and 3 = bevel"},
{"arg":"mitre_limit","optional":true,"default":"2.0","description":"limit on the mitre ratio used for very sharp corners (when using mitre joins only)"}],
"examples": [ { "expression":"offset_curve($geometry, 10.5)", "returns":"line offset to the left by 10.5 units"},
{ "expression":"offset_curve($geometry, -10.5)", "returns":"line offset to the right by 10.5 units"},
{ "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"},
{ "expression":"offset_curve($geometry, 10.5, join=3)", "returns":"line offset to the left by 10.5 units, using a beveled join"}]
}

15 changes: 15 additions & 0 deletions resources/function_help/json/single_sided_buffer
@@ -0,0 +1,15 @@
{
"name": "single_sided_buffer",
"type": "function",
"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.",
"arguments": [ {"arg":"geometry","description":"a (multi)linestring geometry"},
{"arg":"distance","description":"buffer distance. Positive values will be buffered to the left of lines, negative values to the right"},
{"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."},
{"arg":"join","optional":true,"default":"1","description":"join style for corners, where 1 = round, 2 = mitre and 3 = bevel"},
{"arg":"mitre_limit","optional":true,"default":"2.0","description":"limit on the mitre ratio used for very sharp corners (when using mitre joins only)"}],
"examples": [ { "expression":"single_sided_buffer($geometry, 10.5)", "returns":"line buffered to the left by 10.5 units"},
{ "expression":"single_sided_buffer($geometry, -10.5)", "returns":"line buffered to the right by 10.5 units"},
{ "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"},
{ "expression":"single_sided_buffer($geometry, 10.5, join=3)", "returns":"line buffered to the left by 10.5 units, using a beveled join"}]
}

4 changes: 2 additions & 2 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1314,7 +1314,7 @@ QgsGeometry QgsGeometry::buffer( double distance, int segments, EndCapStyle endC

QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, JoinStyle joinStyle, double mitreLimit ) const
{
if ( !d->geometry )
if ( !d->geometry || type() != QgsWkbTypes::LineGeometry )
{
return QgsGeometry();
}
Expand Down Expand Up @@ -1353,7 +1353,7 @@ QgsGeometry QgsGeometry::offsetCurve( double distance, int segments, JoinStyle j

QgsGeometry QgsGeometry::singleSidedBuffer( double distance, int segments, BufferSide side , JoinStyle joinStyle, double mitreLimit ) const
{
if ( !d->geometry )
if ( !d->geometry || type() != QgsWkbTypes::LineGeometry )
{
return QgsGeometry();
}
Expand Down
46 changes: 45 additions & 1 deletion src/core/qgsexpression.cpp
Expand Up @@ -2212,6 +2212,37 @@ static QVariant fcnBuffer( const QVariantList& values, const QgsExpressionContex
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
return result;
}

static QVariant fcnOffsetCurve( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
double dist = getDoubleValue( values.at( 1 ), parent );
int segments = getIntValue( values.at( 2 ), parent );
QgsGeometry::JoinStyle join = static_cast< QgsGeometry::JoinStyle >( getIntValue( values.at( 3 ), parent ) );
if ( join < QgsGeometry::JoinStyleRound || join > QgsGeometry::JoinStyleBevel )
return QVariant();
double mitreLimit = getDoubleValue( values.at( 3 ), parent );

QgsGeometry geom = fGeom.offsetCurve( dist, segments, join, mitreLimit );
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
return result;
}

static QVariant fcnSingleSidedBuffer( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
double dist = getDoubleValue( values.at( 1 ), parent );
int segments = getIntValue( values.at( 2 ), parent );
QgsGeometry::JoinStyle join = static_cast< QgsGeometry::JoinStyle >( getIntValue( values.at( 3 ), parent ) );
if ( join < QgsGeometry::JoinStyleRound || join > QgsGeometry::JoinStyleBevel )
return QVariant();
double mitreLimit = getDoubleValue( values.at( 3 ), parent );

QgsGeometry geom = fGeom.singleSidedBuffer( dist, segments, QgsGeometry::SideLeft, join, mitreLimit );
QVariant result = !geom.isEmpty() ? QVariant::fromValue( geom ) : QVariant();
return result;
}

static QVariant fcnTranslate( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry fGeom = getGeometry( values.at( 0 ), parent );
Expand Down Expand Up @@ -3084,7 +3115,8 @@ const QStringList& QgsExpression::BuiltinFunctions()
<< "geom_from_gml" << "geomFromGML" << "intersects_bbox" << "bbox"
<< "disjoint" << "intersects" << "touches" << "crosses" << "contains"
<< "relate"
<< "overlaps" << "within" << "buffer" << "centroid" << "bounds" << "reverse" << "exterior_ring"
<< "overlaps" << "within" << "buffer" << "offset_curve" << "single_sided_buffer"
<< "centroid" << "bounds" << "reverse" << "exterior_ring"
<< "boundary" << "line_merge"
<< "bounds_width" << "bounds_height" << "is_closed" << "convex_hull" << "difference"
<< "distance" << "intersection" << "sym_difference" << "combine"
Expand Down Expand Up @@ -3271,6 +3303,18 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "within", 2, fcnWithin, "GeometryGroup" )
<< new StaticFunction( "translate", 3, fcnTranslate, "GeometryGroup" )
<< new StaticFunction( "buffer", -1, fcnBuffer, "GeometryGroup" )
<< new StaticFunction( "offset_curve", ParameterList() << Parameter( "geometry" )
<< Parameter( "distance" )
<< Parameter( "segments", true, 8.0 )
<< Parameter( "join", true, QgsGeometry::JoinStyleRound )
<< Parameter( "mitre_limit", true, 2.0 ),
fcnOffsetCurve, "GeometryGroup" )
<< new StaticFunction( "single_sided_buffer", ParameterList() << Parameter( "geometry" )
<< Parameter( "distance" )
<< Parameter( "segments", true, 8.0 )
<< Parameter( "join", true, QgsGeometry::JoinStyleRound )
<< Parameter( "mitre_limit", true, 2.0 ),
fcnSingleSidedBuffer, "GeometryGroup" )
<< new StaticFunction( "centroid", 1, fcnCentroid, "GeometryGroup" )
<< new StaticFunction( "point_on_surface", 1, fcnPointOnSurface, "GeometryGroup" )
<< new StaticFunction( "reverse", 1, fcnReverse, "GeometryGroup" )
Expand Down
12 changes: 12 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -677,6 +677,18 @@ class TestQgsExpression: public QObject
QTest::newRow( "line_merge point" ) << "line_merge(geom_from_wkt('POINT(1 2)'))" << false << QVariant();
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)" );
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)" );
QTest::newRow( "offset_curve not geom" ) << "offset_curve('g', 5)" << true << QVariant();
QTest::newRow( "offset_curve null" ) << "offset_curve(NULL, 5)" << false << QVariant();
QTest::newRow( "offset_curve point" ) << "offset_curve(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant();
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)" );
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)" );
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)" );
QTest::newRow( "single_sided_buffer not geom" ) << "single_sided_buffer('g', 5)" << true << QVariant();
QTest::newRow( "single_sided_buffer null" ) << "single_sided_buffer(NULL, 5)" << false << QVariant();
QTest::newRow( "single_sided_buffer point" ) << "single_sided_buffer(geom_from_wkt('POINT(1 2)'),5)" << false << QVariant();
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))" );
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))" );
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))" );
QTest::newRow( "start_point point" ) << "geom_to_wkt(start_point(geom_from_wkt('POINT(2 0)')))" << false << QVariant( "Point (2 0)" );
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)" );
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)" );
Expand Down

0 comments on commit 298d047

Please sign in to comment.