Skip to content

Commit 5b244ae

Browse files
committed
[FEATURE] More geometry functions for expressions
- add accessors: geometry_n, interior_ring_n - add num_geometries, num_rings, num_interior_rings - add nodes_to_points for converting every node in a geometry to a multipoint geometry - add segments_to_lines for converting every segment in a geometry to a multiline geometry nodes_to_points and segments_to_lines are intended for use with geometry generator symbology, eg to allow use of m and z values for nodes/lines with data defined symbology.
1 parent 24a61ff commit 5b244ae

File tree

10 files changed

+372
-1
lines changed

10 files changed

+372
-1
lines changed
+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "geometry_n",
3+
"type": "function",
4+
"description": "Returns a specific geometry from a geometry collection, or null if the input geometry is not a collection.",
5+
"arguments": [ {"arg":"geometry","description":"geometry collection"},
6+
{"arg":"index","description":"index of geometry to return, where 1 is the first geometry in the collection"} ],
7+
"examples": [ { "expression":"geom_to_wkt(geometry_n(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'),3))", "returns":"'Point (1 0)'"}]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"name": "interior_ring_n",
3+
"type": "function",
4+
"description": "Returns a specific interior ring from a polygon geometry, or null if the geometry is not a polygon.",
5+
"arguments": [ {"arg":"geometry","description":"polygon geometry"},
6+
{"arg":"index","description":"index of interior to return, where 1 is the first interior ring"} ],
7+
"examples": [ { "expression":"geom_to_wkt(interior_ring_n(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1),(-0.1 -0.1, 0.4 0, 0.4 0.2, 0 0.2, -0.1 -0.1),(-1 -1, 4 0, 4 2, 0 2, -1 -1))'),1))", "returns":"'LineString (-0.1 -0.1, 0.4 0, 0.4 0.2, 0 0.2, -0.1 -0.1))'"}]
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "nodes_to_points",
3+
"type": "function",
4+
"description": "Returns a multipoint geometry consisting of every node in the input geometry.",
5+
"arguments": [ {"arg":"geometry","description":"geometry object"},
6+
{"arg":"ignore_closing_nodes","description":"optional argument specifying whether to include duplicate nodes which close lines or polygons rings. Defaults to false, set to true to avoid including these duplicate nodes in the output collection."} ],
7+
"examples": [ { "expression":"geom_to_wkt(nodes_to_points(geom_from_wkt('LINESTRING(0 0, 1 1, 2 2)')))", "returns":"'MultiPoint ((0 0),(1 1),(2 2))'"},
8+
{ "expression":"geom_to_wkt(nodes_to_points(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1))'),true))", "returns":"'MultiPoint ((-1 -1),(4 0),(4 2),(0 2))'"}]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "num_geometries",
3+
"type": "function",
4+
"description": "Returns the number of geometries in a geometry collection, or null if the input geometry is not a collection.",
5+
"arguments": [ {"arg":"geometry","description":"geometry collection"} ],
6+
"examples": [ { "expression":"num_geometries(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'))'),3))", "returns":"4"}]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "num_interior_rings",
3+
"type": "function",
4+
"description": "Returns the number of interior rings in a polygon or geometry collection, or null if the input geometry is not a polygon or collection.",
5+
"arguments": [ {"arg":"geometry","description":"input geometry"} ],
6+
"examples": [ { "expression":"num_interior_rings(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1),(-0.1 -0.1, 0.4 0, 0.4 0.2, 0 0.2, -0.1 -0.1))'))", "returns":"1"}]
7+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "num_rings",
3+
"type": "function",
4+
"description": "Returns the number of rings (including exterior rings) in a polygon or geometry collection, or null if the input geometry is not a polygon or collection.",
5+
"arguments": [ {"arg":"geometry","description":"input geometry"} ],
6+
"examples": [ { "expression":"num_rings(geom_from_wkt('POLYGON((-1 -1, 4 0, 4 2, 0 2, -1 -1),(-0.1 -0.1, 0.4 0, 0.4 0.2, 0 0.2, -0.1 -0.1))'))", "returns":"2"}]
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "segments_to_lines",
3+
"type": "function",
4+
"description": "Returns a multi line geometry consisting of a line for every segment in the input geometry.",
5+
"arguments": [ {"arg":"geometry","description":"geometry object"}],
6+
"examples": [ { "expression":"geom_to_wkt(segments_to_lines(geom_from_wkt('LINESTRING(0 0, 1 1, 2 2)')))", "returns":"'MultiLineString ((0 0, 1 1),(1 1, 2 2))'"}]
7+
}

src/core/geometry/qgsgeometrycollectionv2.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ const QgsAbstractGeometryV2* QgsGeometryCollectionV2::geometryN( int n ) const
9191

9292
QgsAbstractGeometryV2* QgsGeometryCollectionV2::geometryN( int n )
9393
{
94-
if ( n >= mGeometries.size() )
94+
if ( n < 0 || n >= mGeometries.size() )
9595
{
9696
return 0;
9797
}

src/core/qgsexpression.cpp

+238
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@
4242
#include "qgsgeometrycollectionv2.h"
4343
#include "qgspointv2.h"
4444
#include "qgspolygonv2.h"
45+
#include "qgsmultipointv2.h"
46+
#include "qgsmultilinestringv2.h"
47+
#include "qgscurvepolygonv2.h"
4548

4649
#if QT_VERSION < 0x050000
4750
#include <qtextdocument.h>
@@ -1382,6 +1385,160 @@ static QVariant fcnEndPoint( const QVariantList& values, const QgsExpressionCont
13821385
return QVariant::fromValue( QgsGeometry( new QgsPointV2( point ) ) );
13831386
}
13841387

1388+
static QVariant fcnNodesToPoints( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1389+
{
1390+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1391+
1392+
if ( geom.isEmpty() )
1393+
return QVariant();
1394+
1395+
bool ignoreClosing = false;
1396+
if ( values.length() > 1 )
1397+
{
1398+
ignoreClosing = getIntValue( values.at( 1 ), parent );
1399+
}
1400+
1401+
QgsMultiPointV2* mp = new QgsMultiPointV2();
1402+
1403+
QList< QList< QList< QgsPointV2 > > > coords;
1404+
geom.geometry()->coordinateSequence( coords );
1405+
1406+
Q_FOREACH ( const QList< QList< QgsPointV2 > >& part, coords )
1407+
{
1408+
Q_FOREACH ( const QList< QgsPointV2 >& ring, part )
1409+
{
1410+
bool skipLast = false;
1411+
if ( ignoreClosing && ring.count() > 2 && ring.first() == ring.last() )
1412+
{
1413+
skipLast = true;
1414+
}
1415+
1416+
for ( int i = 0; i < ( skipLast ? ring.count() - 1 : ring.count() ); ++ i )
1417+
{
1418+
mp->addGeometry( ring.at( i ).clone() );
1419+
}
1420+
}
1421+
}
1422+
1423+
return QVariant::fromValue( QgsGeometry( mp ) );
1424+
}
1425+
1426+
static QVariant fcnSegmentsToLines( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1427+
{
1428+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1429+
1430+
if ( geom.isEmpty() )
1431+
return QVariant();
1432+
1433+
QList< QgsAbstractGeometryV2 * > geometries;
1434+
1435+
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
1436+
if ( collection )
1437+
{
1438+
for ( int i = 0; i < collection->numGeometries(); ++i )
1439+
{
1440+
geometries.append( collection->geometryN( i ) );
1441+
}
1442+
}
1443+
else
1444+
{
1445+
geometries.append( geom.geometry() );
1446+
}
1447+
1448+
QList< QgsLineStringV2* > linesToProcess;
1449+
while ( ! geometries.isEmpty() )
1450+
{
1451+
QgsAbstractGeometryV2* g = geometries.takeFirst();
1452+
QgsCurveV2* curve = dynamic_cast< QgsCurveV2* >( g );
1453+
if ( curve )
1454+
{
1455+
linesToProcess << static_cast< QgsLineStringV2* >( curve->segmentize() );
1456+
continue;
1457+
}
1458+
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( g );
1459+
if ( collection )
1460+
{
1461+
for ( int i = 0; i < collection->numGeometries(); ++i )
1462+
{
1463+
geometries.append( collection->geometryN( i ) );
1464+
}
1465+
}
1466+
QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( g );
1467+
if ( curvePolygon )
1468+
{
1469+
if ( curvePolygon->exteriorRing() )
1470+
linesToProcess << static_cast< QgsLineStringV2* >( curvePolygon->exteriorRing()->segmentize() );
1471+
1472+
for ( int i = 0; i < curvePolygon->numInteriorRings(); ++i )
1473+
{
1474+
linesToProcess << static_cast< QgsLineStringV2* >( curvePolygon->interiorRing( i )->segmentize() );
1475+
}
1476+
continue;
1477+
}
1478+
}
1479+
1480+
//ok, now we have a complete list of segmentized lines from the geometry
1481+
QgsMultiLineStringV2* ml = new QgsMultiLineStringV2();
1482+
Q_FOREACH ( QgsLineStringV2* line, linesToProcess )
1483+
{
1484+
for ( int i = 0; i < line->numPoints() - 1; ++i )
1485+
{
1486+
QgsLineStringV2* segment = new QgsLineStringV2();
1487+
segment->setPoints( QList<QgsPointV2>()
1488+
<< line->pointN( i )
1489+
<< line->pointN( i + 1 ) );
1490+
ml->addGeometry( segment );
1491+
}
1492+
delete line;
1493+
}
1494+
1495+
return QVariant::fromValue( QgsGeometry( ml ) );
1496+
}
1497+
1498+
static QVariant fcnInteriorRingN( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1499+
{
1500+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1501+
1502+
if ( geom.isEmpty() )
1503+
return QVariant();
1504+
1505+
QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
1506+
if ( !curvePolygon )
1507+
return QVariant();
1508+
1509+
//idx is 1 based
1510+
int idx = getIntValue( values.at( 1 ), parent ) - 1;
1511+
1512+
if ( idx >= curvePolygon->numInteriorRings() || idx < 0 )
1513+
return QVariant();
1514+
1515+
QgsCurveV2* curve = static_cast< QgsCurveV2* >( curvePolygon->interiorRing( idx )->clone() );
1516+
QVariant result = curve ? QVariant::fromValue( QgsGeometry( curve ) ) : QVariant();
1517+
return result;
1518+
}
1519+
1520+
static QVariant fcnGeometryN( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1521+
{
1522+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1523+
1524+
if ( geom.isEmpty() )
1525+
return QVariant();
1526+
1527+
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
1528+
if ( !collection )
1529+
return QVariant();
1530+
1531+
//idx is 1 based
1532+
int idx = getIntValue( values.at( 1 ), parent ) - 1;
1533+
1534+
if ( idx < 0 || idx >= collection->numGeometries() )
1535+
return QVariant();
1536+
1537+
QgsAbstractGeometryV2* part = collection->geometryN( idx )->clone();
1538+
QVariant result = part ? QVariant::fromValue( QgsGeometry( part ) ) : QVariant();
1539+
return result;
1540+
}
1541+
13851542
static QVariant fcnMakePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
13861543
{
13871544
if ( values.count() < 2 || values.count() > 4 )
@@ -1583,6 +1740,77 @@ static QVariant fcnGeomNumPoints( const QVariantList& values, const QgsExpressio
15831740
return QVariant( geom.isEmpty() ? 0 : geom.geometry()->nCoordinates() );
15841741
}
15851742

1743+
static QVariant fcnGeomNumGeometries( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1744+
{
1745+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1746+
if ( geom.isEmpty() )
1747+
return QVariant();
1748+
1749+
return QVariant( geom.geometry()->partCount() );
1750+
}
1751+
1752+
static QVariant fcnGeomNumInteriorRings( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1753+
{
1754+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1755+
1756+
if ( geom.isEmpty() )
1757+
return QVariant();
1758+
1759+
QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
1760+
if ( curvePolygon )
1761+
return QVariant( curvePolygon->numInteriorRings() );
1762+
1763+
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
1764+
if ( collection )
1765+
{
1766+
//find first CurvePolygon in collection
1767+
for ( int i = 0; i < collection->numGeometries(); ++i )
1768+
{
1769+
curvePolygon = dynamic_cast< QgsCurvePolygonV2*>( collection->geometryN( i ) );
1770+
if ( !curvePolygon )
1771+
continue;
1772+
1773+
return QVariant( curvePolygon->isEmpty() ? 0 : curvePolygon->numInteriorRings() );
1774+
}
1775+
}
1776+
1777+
return QVariant();
1778+
}
1779+
1780+
static QVariant fcnGeomNumRings( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
1781+
{
1782+
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
1783+
1784+
if ( geom.isEmpty() )
1785+
return QVariant();
1786+
1787+
QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
1788+
if ( curvePolygon )
1789+
return QVariant( curvePolygon->ringCount() );
1790+
1791+
bool foundPoly = false;
1792+
int ringCount = 0;
1793+
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
1794+
if ( collection )
1795+
{
1796+
//find CurvePolygons in collection
1797+
for ( int i = 0; i < collection->numGeometries(); ++i )
1798+
{
1799+
curvePolygon = dynamic_cast< QgsCurvePolygonV2*>( collection->geometryN( i ) );
1800+
if ( !curvePolygon )
1801+
continue;
1802+
1803+
foundPoly = true;
1804+
ringCount += curvePolygon->ringCount();
1805+
}
1806+
}
1807+
1808+
if ( !foundPoly )
1809+
return QVariant();
1810+
1811+
return QVariant( ringCount );
1812+
}
1813+
15861814
static QVariant fcnBounds( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
15871815
{
15881816
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
@@ -2435,7 +2663,10 @@ const QStringList& QgsExpression::BuiltinFunctions()
24352663
<< "color_cmyk" << "color_cmyka" << "color_part" << "set_color_part"
24362664
<< "xat" << "yat" << "$area" << "area" << "perimeter"
24372665
<< "$length" << "$perimeter" << "x" << "y" << "$x" << "$y" << "z" << "m" << "num_points"
2666+
<< "num_interior_rings" << "num_rings" << "num_geometries"
2667+
<< "geometry_n" << "interior_ring_n"
24382668
<< "point_n" << "start_point" << "end_point" << "make_point" << "make_point_m"
2669+
<< "nodes_to_points" << "segments_to_lines"
24392670
<< "make_line" << "make_polygon"
24402671
<< "$x_at" << "x_at" << "xat" << "$y_at" << "y_at" << "yat" << "x_min" << "xmin" << "x_max" << "xmax"
24412672
<< "y_min" << "ymin" << "y_max" << "ymax" << "geom_from_wkt" << "geomFromWKT"
@@ -2560,6 +2791,8 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
25602791
<< new StaticFunction( "point_n", 2, fcnPointN, "GeometryGroup" )
25612792
<< new StaticFunction( "start_point", 1, fcnStartPoint, "GeometryGroup" )
25622793
<< new StaticFunction( "end_point", 1, fcnEndPoint, "GeometryGroup" )
2794+
<< new StaticFunction( "nodes_to_points", -1, fcnNodesToPoints, "GeometryGroup" )
2795+
<< new StaticFunction( "segments_to_lines", 1, fcnSegmentsToLines, "GeometryGroup" )
25632796
<< new StaticFunction( "make_point", -1, fcnMakePoint, "GeometryGroup" )
25642797
<< new StaticFunction( "make_point_m", 3, fcnMakePointM, "GeometryGroup" )
25652798
<< new StaticFunction( "make_line", -1, fcnMakeLine, "GeometryGroup" )
@@ -2587,8 +2820,13 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
25872820
<< new StaticFunction( "point_on_surface", 1, fcnPointOnSurface, "GeometryGroup" )
25882821
<< new StaticFunction( "reverse", 1, fcnReverse, "GeometryGroup" )
25892822
<< new StaticFunction( "exterior_ring", 1, fcnExteriorRing, "GeometryGroup" )
2823+
<< new StaticFunction( "interior_ring_n", 2, fcnInteriorRingN, "GeometryGroup" )
2824+
<< new StaticFunction( "geometry_n", 2, fcnGeometryN, "GeometryGroup" )
25902825
<< new StaticFunction( "bounds", 1, fcnBounds, "GeometryGroup" )
25912826
<< new StaticFunction( "num_points", 1, fcnGeomNumPoints, "GeometryGroup" )
2827+
<< new StaticFunction( "num_interior_rings", 1, fcnGeomNumInteriorRings, "GeometryGroup" )
2828+
<< new StaticFunction( "num_rings", 1, fcnGeomNumRings, "GeometryGroup" )
2829+
<< new StaticFunction( "num_geometries", 1, fcnGeomNumGeometries, "GeometryGroup" )
25922830
<< new StaticFunction( "bounds_width", 1, fcnBoundsWidth, "GeometryGroup" )
25932831
<< new StaticFunction( "bounds_height", 1, fcnBoundsHeight, "GeometryGroup" )
25942832
<< new StaticFunction( "is_closed", 1, fcnIsClosed, "GeometryGroup" )

0 commit comments

Comments
 (0)