Skip to content
Permalink
Browse files

[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.
  • Loading branch information
nyalldawson committed Dec 15, 2015
1 parent 24a61ff commit 5b244ae88ce2c0d851f52a3aaef971bd5beb10c7
@@ -0,0 +1,8 @@
{
"name": "geometry_n",
"type": "function",
"description": "Returns a specific geometry from a geometry collection, or null if the input geometry is not a collection.",
"arguments": [ {"arg":"geometry","description":"geometry collection"},
{"arg":"index","description":"index of geometry to return, where 1 is the first geometry in the collection"} ],
"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)'"}]
}
@@ -0,0 +1,8 @@
{
"name": "interior_ring_n",
"type": "function",
"description": "Returns a specific interior ring from a polygon geometry, or null if the geometry is not a polygon.",
"arguments": [ {"arg":"geometry","description":"polygon geometry"},
{"arg":"index","description":"index of interior to return, where 1 is the first interior ring"} ],
"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))'"}]
}
@@ -0,0 +1,9 @@
{
"name": "nodes_to_points",
"type": "function",
"description": "Returns a multipoint geometry consisting of every node in the input geometry.",
"arguments": [ {"arg":"geometry","description":"geometry object"},
{"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."} ],
"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))'"},
{ "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))'"}]
}
@@ -0,0 +1,7 @@
{
"name": "num_geometries",
"type": "function",
"description": "Returns the number of geometries in a geometry collection, or null if the input geometry is not a collection.",
"arguments": [ {"arg":"geometry","description":"geometry collection"} ],
"examples": [ { "expression":"num_geometries(geom_from_wkt('GEOMETRYCOLLECTION(POINT(0 1), POINT(0 0), POINT(1 0), POINT(1 1))'))'),3))", "returns":"4"}]
}
@@ -0,0 +1,7 @@
{
"name": "num_interior_rings",
"type": "function",
"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.",
"arguments": [ {"arg":"geometry","description":"input geometry"} ],
"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"}]
}
@@ -0,0 +1,7 @@
{
"name": "num_rings",
"type": "function",
"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.",
"arguments": [ {"arg":"geometry","description":"input geometry"} ],
"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"}]
}
@@ -0,0 +1,7 @@
{
"name": "segments_to_lines",
"type": "function",
"description": "Returns a multi line geometry consisting of a line for every segment in the input geometry.",
"arguments": [ {"arg":"geometry","description":"geometry object"}],
"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))'"}]
}
@@ -91,7 +91,7 @@ const QgsAbstractGeometryV2* QgsGeometryCollectionV2::geometryN( int n ) const

QgsAbstractGeometryV2* QgsGeometryCollectionV2::geometryN( int n )
{
if ( n >= mGeometries.size() )
if ( n < 0 || n >= mGeometries.size() )
{
return 0;
}
@@ -42,6 +42,9 @@
#include "qgsgeometrycollectionv2.h"
#include "qgspointv2.h"
#include "qgspolygonv2.h"
#include "qgsmultipointv2.h"
#include "qgsmultilinestringv2.h"
#include "qgscurvepolygonv2.h"

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

static QVariant fcnNodesToPoints( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

bool ignoreClosing = false;
if ( values.length() > 1 )
{
ignoreClosing = getIntValue( values.at( 1 ), parent );
}

QgsMultiPointV2* mp = new QgsMultiPointV2();

QList< QList< QList< QgsPointV2 > > > coords;
geom.geometry()->coordinateSequence( coords );

Q_FOREACH ( const QList< QList< QgsPointV2 > >& part, coords )
{
Q_FOREACH ( const QList< QgsPointV2 >& ring, part )
{
bool skipLast = false;
if ( ignoreClosing && ring.count() > 2 && ring.first() == ring.last() )
{
skipLast = true;
}

for ( int i = 0; i < ( skipLast ? ring.count() - 1 : ring.count() ); ++ i )
{
mp->addGeometry( ring.at( i ).clone() );
}
}
}

return QVariant::fromValue( QgsGeometry( mp ) );
}

static QVariant fcnSegmentsToLines( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QList< QgsAbstractGeometryV2 * > geometries;

QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
if ( collection )
{
for ( int i = 0; i < collection->numGeometries(); ++i )
{
geometries.append( collection->geometryN( i ) );
}
}
else
{
geometries.append( geom.geometry() );
}

QList< QgsLineStringV2* > linesToProcess;
while ( ! geometries.isEmpty() )
{
QgsAbstractGeometryV2* g = geometries.takeFirst();
QgsCurveV2* curve = dynamic_cast< QgsCurveV2* >( g );
if ( curve )
{
linesToProcess << static_cast< QgsLineStringV2* >( curve->segmentize() );
continue;
}
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( g );
if ( collection )
{
for ( int i = 0; i < collection->numGeometries(); ++i )
{
geometries.append( collection->geometryN( i ) );
}
}
QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( g );
if ( curvePolygon )
{
if ( curvePolygon->exteriorRing() )
linesToProcess << static_cast< QgsLineStringV2* >( curvePolygon->exteriorRing()->segmentize() );

for ( int i = 0; i < curvePolygon->numInteriorRings(); ++i )
{
linesToProcess << static_cast< QgsLineStringV2* >( curvePolygon->interiorRing( i )->segmentize() );
}
continue;
}
}

//ok, now we have a complete list of segmentized lines from the geometry
QgsMultiLineStringV2* ml = new QgsMultiLineStringV2();
Q_FOREACH ( QgsLineStringV2* line, linesToProcess )
{
for ( int i = 0; i < line->numPoints() - 1; ++i )
{
QgsLineStringV2* segment = new QgsLineStringV2();
segment->setPoints( QList<QgsPointV2>()
<< line->pointN( i )
<< line->pointN( i + 1 ) );
ml->addGeometry( segment );
}
delete line;
}

return QVariant::fromValue( QgsGeometry( ml ) );
}

static QVariant fcnInteriorRingN( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
if ( !curvePolygon )
return QVariant();

//idx is 1 based
int idx = getIntValue( values.at( 1 ), parent ) - 1;

if ( idx >= curvePolygon->numInteriorRings() || idx < 0 )
return QVariant();

QgsCurveV2* curve = static_cast< QgsCurveV2* >( curvePolygon->interiorRing( idx )->clone() );
QVariant result = curve ? QVariant::fromValue( QgsGeometry( curve ) ) : QVariant();
return result;
}

static QVariant fcnGeometryN( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
if ( !collection )
return QVariant();

//idx is 1 based
int idx = getIntValue( values.at( 1 ), parent ) - 1;

if ( idx < 0 || idx >= collection->numGeometries() )
return QVariant();

QgsAbstractGeometryV2* part = collection->geometryN( idx )->clone();
QVariant result = part ? QVariant::fromValue( QgsGeometry( part ) ) : QVariant();
return result;
}

static QVariant fcnMakePoint( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
if ( values.count() < 2 || values.count() > 4 )
@@ -1583,6 +1740,77 @@ static QVariant fcnGeomNumPoints( const QVariantList& values, const QgsExpressio
return QVariant( geom.isEmpty() ? 0 : geom.geometry()->nCoordinates() );
}

static QVariant fcnGeomNumGeometries( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
if ( geom.isEmpty() )
return QVariant();

return QVariant( geom.geometry()->partCount() );
}

static QVariant fcnGeomNumInteriorRings( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
if ( curvePolygon )
return QVariant( curvePolygon->numInteriorRings() );

QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
if ( collection )
{
//find first CurvePolygon in collection
for ( int i = 0; i < collection->numGeometries(); ++i )
{
curvePolygon = dynamic_cast< QgsCurvePolygonV2*>( collection->geometryN( i ) );
if ( !curvePolygon )
continue;

return QVariant( curvePolygon->isEmpty() ? 0 : curvePolygon->numInteriorRings() );
}
}

return QVariant();
}

static QVariant fcnGeomNumRings( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );

if ( geom.isEmpty() )
return QVariant();

QgsCurvePolygonV2* curvePolygon = dynamic_cast< QgsCurvePolygonV2* >( geom.geometry() );
if ( curvePolygon )
return QVariant( curvePolygon->ringCount() );

bool foundPoly = false;
int ringCount = 0;
QgsGeometryCollectionV2* collection = dynamic_cast< QgsGeometryCollectionV2* >( geom.geometry() );
if ( collection )
{
//find CurvePolygons in collection
for ( int i = 0; i < collection->numGeometries(); ++i )
{
curvePolygon = dynamic_cast< QgsCurvePolygonV2*>( collection->geometryN( i ) );
if ( !curvePolygon )
continue;

foundPoly = true;
ringCount += curvePolygon->ringCount();
}
}

if ( !foundPoly )
return QVariant();

return QVariant( ringCount );
}

static QVariant fcnBounds( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
{
QgsGeometry geom = getGeometry( values.at( 0 ), parent );
@@ -2435,7 +2663,10 @@ const QStringList& QgsExpression::BuiltinFunctions()
<< "color_cmyk" << "color_cmyka" << "color_part" << "set_color_part"
<< "xat" << "yat" << "$area" << "area" << "perimeter"
<< "$length" << "$perimeter" << "x" << "y" << "$x" << "$y" << "z" << "m" << "num_points"
<< "num_interior_rings" << "num_rings" << "num_geometries"
<< "geometry_n" << "interior_ring_n"
<< "point_n" << "start_point" << "end_point" << "make_point" << "make_point_m"
<< "nodes_to_points" << "segments_to_lines"
<< "make_line" << "make_polygon"
<< "$x_at" << "x_at" << "xat" << "$y_at" << "y_at" << "yat" << "x_min" << "xmin" << "x_max" << "xmax"
<< "y_min" << "ymin" << "y_max" << "ymax" << "geom_from_wkt" << "geomFromWKT"
@@ -2560,6 +2791,8 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "point_n", 2, fcnPointN, "GeometryGroup" )
<< new StaticFunction( "start_point", 1, fcnStartPoint, "GeometryGroup" )
<< new StaticFunction( "end_point", 1, fcnEndPoint, "GeometryGroup" )
<< new StaticFunction( "nodes_to_points", -1, fcnNodesToPoints, "GeometryGroup" )
<< new StaticFunction( "segments_to_lines", 1, fcnSegmentsToLines, "GeometryGroup" )
<< new StaticFunction( "make_point", -1, fcnMakePoint, "GeometryGroup" )
<< new StaticFunction( "make_point_m", 3, fcnMakePointM, "GeometryGroup" )
<< new StaticFunction( "make_line", -1, fcnMakeLine, "GeometryGroup" )
@@ -2587,8 +2820,13 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( "point_on_surface", 1, fcnPointOnSurface, "GeometryGroup" )
<< new StaticFunction( "reverse", 1, fcnReverse, "GeometryGroup" )
<< new StaticFunction( "exterior_ring", 1, fcnExteriorRing, "GeometryGroup" )
<< new StaticFunction( "interior_ring_n", 2, fcnInteriorRingN, "GeometryGroup" )
<< new StaticFunction( "geometry_n", 2, fcnGeometryN, "GeometryGroup" )
<< new StaticFunction( "bounds", 1, fcnBounds, "GeometryGroup" )
<< new StaticFunction( "num_points", 1, fcnGeomNumPoints, "GeometryGroup" )
<< new StaticFunction( "num_interior_rings", 1, fcnGeomNumInteriorRings, "GeometryGroup" )
<< new StaticFunction( "num_rings", 1, fcnGeomNumRings, "GeometryGroup" )
<< new StaticFunction( "num_geometries", 1, fcnGeomNumGeometries, "GeometryGroup" )
<< new StaticFunction( "bounds_width", 1, fcnBoundsWidth, "GeometryGroup" )
<< new StaticFunction( "bounds_height", 1, fcnBoundsHeight, "GeometryGroup" )
<< new StaticFunction( "is_closed", 1, fcnIsClosed, "GeometryGroup" )

0 comments on commit 5b244ae

Please sign in to comment.
You can’t perform that action at this time.