Skip to content

Commit 679797e

Browse files
committed
[FEATURE] New simplify + smoothing expression functions
Exposes simplification and smoothing algorithms to expression engine, via: - simplify(): applies Douglas-Peucker geometry simplification - simplify_vw(): applies Visvalingam-Whyatt geometry simplification - smooth(): smoothes a geometry Carto tip: use smooth along with geometry generators to minimise the typical "GIS" noded look of rendered geometries!
1 parent 8881a7e commit 679797e

File tree

5 files changed

+103
-0
lines changed

5 files changed

+103
-0
lines changed

resources/function_help/json/simplify

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "simplify",
3+
"type": "function",
4+
"description":"Simplifies a geometry by removing nodes using a distance based threshold (ie, the Douglas Peucker algorithm). The algorithm preserves large deviations in geometries and reduces the number of vertices in nearly straight segments.",
5+
"arguments": [ {"arg":"geometry","description":"a geometry"},
6+
{"arg":"tolerance","description":"maximum deviation from straight segments for points to be removed"} ],
7+
"examples": [ { "expression":"geom_to_wkt(simplify(geometry:=geom_from_wkt('LineString(0 0, 5 0.1, 10 0)'),tolerance:=5))", "returns":"'LineString(0 0, 10 0)'"}]
8+
}
9+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "simplify_vw",
3+
"type": "function",
4+
"description":"Simplifies a geometry by removing nodes using an area based threshold (ie, the Visvalingam-Whyatt algorithm). The algorithm removes vertices which create small areas in geometries, eg narrow spikes or nearly straight segments.",
5+
"arguments": [ {"arg":"geometry","description":"a geometry"},
6+
{"arg":"tolerance","description":"a measure of the maximum area created by a node for the node to be removed"} ],
7+
"examples": [ { "expression":"geom_to_wkt(simplify_vw(geometry:=geom_from_wkt('LineString(0 0, 5 0, 5.01 10, 5.02 0, 10 0)'),tolerance:=5))", "returns":"'LineString(0 0, 10 0)'"}]
8+
}
9+

resources/function_help/json/smooth

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "smooth",
3+
"type": "function",
4+
"description":"Smooths a geometry by adding extra nodes which round off corners in the geometry.",
5+
"arguments": [ {"arg":"geometry","description":"a geometry"},
6+
{"arg":"iterations", "optional":true,"description":"number of smoothing iterations to apply. Larger numbers result in smoother but more complex geometries."},
7+
{"arg":"offset", "optional":true,"description":"value between 0 and 0.5 which controls how tightly the smoothed geometry follow the original geometry. Smaller values result in a tighter smoothing, larger values result in looser smoothing."},
8+
{"arg":"min_length", "optional":true,"description":"minimum length of segments to apply smoothing to. This parameter can be used to avoid placing excessive additional nodes in shorter segments of the geometry."},
9+
{"arg":"max_angle", "optional":true,"description":"maximum angle at node for smoothing to be applied (0-180). By lowering the maximum angle intentionally sharp corners in the geometry can be preserved. For instance, a value of 80 degrees will retain right angles in the geometry."} ],
10+
"examples": [ { "expression":"geom_to_wkt(smooth(geometry:=geom_from_wkt('LineString(0 0, 5 0, 5 5)'),iterations:=1,offset:=0.2,min_length:=-1,max_angle:=180))", "returns":"'LineString (0 0, 4 0, 5 1, 5 5)'"}]
11+
}
12+

src/core/qgsexpression.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "qgscurvepolygon.h"
5252
#include "qgsexpressionprivate.h"
5353
#include "qgsexpressionsorter.h"
54+
#include "qgsmaptopixelgeometrysimplifier.h"
5455

5556
#if QT_VERSION < 0x050000
5657
#include <qtextdocument.h>
@@ -1767,6 +1768,59 @@ static QVariant fcnLineMerge( const QVariantList& values, const QgsExpressionCon
17671768
return QVariant::fromValue( merged );
17681769
}
17691770

1771+
static QVariant fcnSimplify( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1772+
{
1773+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1774+
1775+
if ( geom.isEmpty() )
1776+
return QVariant();
1777+
1778+
double tolerance = getDoubleValue( values.at( 1 ), parent );
1779+
1780+
QgsGeometry simplified = geom.simplify( tolerance );
1781+
if ( simplified.isEmpty() )
1782+
return QVariant();
1783+
1784+
return simplified;
1785+
}
1786+
1787+
static QVariant fcnSimplifyVW( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1788+
{
1789+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1790+
1791+
if ( geom.isEmpty() )
1792+
return QVariant();
1793+
1794+
double tolerance = getDoubleValue( values.at( 1 ), parent );
1795+
1796+
QgsMapToPixelSimplifier simplifier( QgsMapToPixelSimplifier::SimplifyGeometry, tolerance, QgsMapToPixelSimplifier::Visvalingam );
1797+
1798+
QgsGeometry simplified = simplifier.simplify( geom );
1799+
if ( simplified.isEmpty() )
1800+
return QVariant();
1801+
1802+
return simplified;
1803+
}
1804+
1805+
static QVariant fcnSmooth( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1806+
{
1807+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1808+
1809+
if ( geom.isEmpty() )
1810+
return QVariant();
1811+
1812+
int iterations = qMin( getIntValue( values.at( 1 ), parent ), 10 );
1813+
double offset = qBound( 0.0, getDoubleValue( values.at( 2 ), parent ), 0.5 );
1814+
double minLength = getDoubleValue( values.at( 3 ), parent );
1815+
double maxAngle = qBound( 0.0, getDoubleValue( values.at( 4 ), parent ), 180.0 );
1816+
1817+
QgsGeometry smoothed = geom.smooth( iterations, offset, minLength, maxAngle );
1818+
if ( smoothed.isEmpty() )
1819+
return QVariant();
1820+
1821+
return smoothed;
1822+
}
1823+
17701824
static QVariant fcnMakePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
17711825
{
17721826
if ( values.count() < 2 || values.count() > 4 )
@@ -3166,6 +3220,7 @@ const QStringList& QgsExpression::BuiltinFunctions()
31663220
<< "disjoint" << "intersects" << "touches" << "crosses" << "contains"
31673221
<< "relate"
31683222
<< "overlaps" << "within" << "buffer" << "offset_curve" << "single_sided_buffer"
3223+
<< "simplify" << "simplify_vw" << "smooth"
31693224
<< "centroid" << "bounds" << "reverse" << "exterior_ring"
31703225
<< "boundary" << "line_merge"
31713226
<< "bounds_width" << "bounds_height" << "is_closed" << "convex_hull" << "difference"
@@ -3377,6 +3432,12 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
33773432
<< new StaticFunction( "boundary", ParameterList() << Parameter( "geometry" ), fcnBoundary, "GeometryGroup" )
33783433
<< new StaticFunction( "line_merge", ParameterList() << Parameter( "geometry" ), fcnLineMerge, "GeometryGroup" )
33793434
<< new StaticFunction( "bounds", 1, fcnBounds, "GeometryGroup" )
3435+
<< new StaticFunction( "simplify", ParameterList() << Parameter( "geometry" ) << Parameter( "tolerance" ), fcnSimplify, "GeometryGroup" )
3436+
<< new StaticFunction( "simplify_vw", ParameterList() << Parameter( "geometry" ) << Parameter( "tolerance" ), fcnSimplifyVW, "GeometryGroup" )
3437+
<< new StaticFunction( "smooth", ParameterList() << Parameter( "geometry" ) << Parameter( "iterations", true, 1 )
3438+
<< Parameter( "offset", true, 0.25 )
3439+
<< Parameter( "min_length", true, -1 )
3440+
<< Parameter( "max_angle", true, 180 ), fcnSmooth, "GeometryGroup" )
33803441
<< new StaticFunction( "num_points", 1, fcnGeomNumPoints, "GeometryGroup" )
33813442
<< new StaticFunction( "num_interior_rings", 1, fcnGeomNumInteriorRings, "GeometryGroup" )
33823443
<< new StaticFunction( "num_rings", 1, fcnGeomNumRings, "GeometryGroup" )

tests/src/core/testqgsexpression.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,18 @@ class TestQgsExpression: public QObject
798798
QTest::newRow( "distance_to_vertex null" ) << "distance_to_vertex(NULL, 0)" << false << QVariant();
799799
QTest::newRow( "distance_to_vertex point" ) << "distance_to_vertex(geom_from_wkt('POINT(1 2)'),0)" << false << QVariant( 0.0 );
800800
QTest::newRow( "distance_to_vertex line" ) << "distance_to_vertex(geometry:=geom_from_wkt('LineString(0 0, 10 0, 10 10)'),vertex:=1)" << false << QVariant( 10.0 );
801+
QTest::newRow( "simplify not geom" ) << "simplify('g',5)" << true << QVariant();
802+
QTest::newRow( "simplify null" ) << "simplify(NULL,5)" << false << QVariant();
803+
QTest::newRow( "simplify point" ) << "geom_to_wkt(simplify(geom_from_wkt('POINT(1 2)'),10))" << false << QVariant( "Point (1 2)" );
804+
QTest::newRow( "simplify line" ) << "geom_to_wkt(simplify(geometry:=geom_from_wkt('LineString(0 0, 5 0, 10 0)'),tolerance:=5))" << false << QVariant( "LineString (0 0, 10 0)" );
805+
QTest::newRow( "simplify_vw not geom" ) << "simplify_vw('g',5)" << true << QVariant();
806+
QTest::newRow( "simplify_vw null" ) << "simplify_vw(NULL,5)" << false << QVariant();
807+
QTest::newRow( "simplify_vw point" ) << "geom_to_wkt(simplify_vw(geom_from_wkt('POINT(1 2)'),10))" << false << QVariant( "Point (1 2)" );
808+
QTest::newRow( "simplify_vw line" ) << "geom_to_wkt(simplify_vw(geometry:=geom_from_wkt('LineString(0 0, 5 0, 5.01 10, 5.02 0, 10 0)'),tolerance:=5))" << false << QVariant( "LineString (0 0, 10 0)" );
809+
QTest::newRow( "smooth not geom" ) << "smooth('g',5)" << true << QVariant();
810+
QTest::newRow( "smooth null" ) << "smooth(NULL,5)" << false << QVariant();
811+
QTest::newRow( "smooth point" ) << "geom_to_wkt(smooth(geom_from_wkt('POINT(1 2)'),10))" << false << QVariant( "Point (1 2)" );
812+
QTest::newRow( "smooth line" ) << "geom_to_wkt(smooth(geometry:=geom_from_wkt('LineString(0 0, 5 0, 5 5)'),iterations:=1,offset:=0.2,min_length:=-1,max_angle:=180))" << false << QVariant( "LineString (0 0, 4 0, 5 1, 5 5)" );
801813

802814
// string functions
803815
QTest::newRow( "lower" ) << "lower('HeLLo')" << false << QVariant( "hello" );

0 commit comments

Comments
 (0)