Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[FEATURE] Expose GEOS polygonize operation via QgsGeometry
  • Loading branch information
nyalldawson committed Jan 31, 2017
1 parent 8709e1f commit 4cebb46
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 7 deletions.
2 changes: 2 additions & 0 deletions python/core/geometry/qgsgeometry.sip
Expand Up @@ -777,6 +777,8 @@ class QgsGeometry
*/
static QgsGeometry unaryUnion( const QList<QgsGeometry>& geometryList );

static QgsGeometry polygonize( const QList< QgsGeometry>& lines );

/** Converts the geometry to straight line segments, if it is a curved geometry type.
* @note added in QGIS 2.10
* @see requiresConversionToStraightSegments
Expand Down
23 changes: 20 additions & 3 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -1940,13 +1940,13 @@ bool QgsGeometry::isGeosEqual( const QgsGeometry& g ) const
return geos.isEqual( *( g.d->geometry ) );
}

QgsGeometry QgsGeometry::unaryUnion( const QList<QgsGeometry>& geometryList )
QgsGeometry QgsGeometry::unaryUnion( const QList<QgsGeometry>& geometries )
{
QgsGeos geos( nullptr );

QList<QgsAbstractGeometry*> geomV2List;
QList<QgsGeometry>::const_iterator it = geometryList.constBegin();
for ( ; it != geometryList.constEnd(); ++it )
QList<QgsGeometry>::const_iterator it = geometries.constBegin();
for ( ; it != geometries.constEnd(); ++it )
{
if ( !(( *it ).isNull() ) )
{
Expand All @@ -1958,6 +1958,23 @@ QgsGeometry QgsGeometry::unaryUnion( const QList<QgsGeometry>& geometryList )
return QgsGeometry( geom );
}

QgsGeometry QgsGeometry::polygonize( const QList<QgsGeometry>& geometryList )
{
QgsGeos geos( nullptr );

QList<QgsAbstractGeometry*> geomV2List;
QList<QgsGeometry>::const_iterator it = geometryList.constBegin();
for ( ; it != geometryList.constEnd(); ++it )
{
if ( !(( *it ).isNull() ) )
{
geomV2List.append(( *it ).geometry() );
}
}

return geos.polygonize( geomV2List );
}

void QgsGeometry::convertToStraightSegment()
{
if ( !d->geometry || !requiresConversionToStraightSegments() )
Expand Down
18 changes: 14 additions & 4 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -843,11 +843,21 @@ class CORE_EXPORT QgsGeometry
**/
void validateGeometry( QList<Error> &errors );

/** Compute the unary union on a list of geometries. May be faster than an iterative union on a set of geometries.
* @param geometryList a list of QgsGeometry as input
* @returns the new computed QgsGeometry, or an empty QgsGeometry
/** Compute the unary union on a list of \a geometries. May be faster than an iterative union on a set of geometries.
* The returned geometry will be fully noded, i.e. a node will be created at every common intersection of the
* input geometries. An empty geometry will be returned in the case of errors.
*/
static QgsGeometry unaryUnion( const QList<QgsGeometry>& geometryList );
static QgsGeometry unaryUnion( const QList<QgsGeometry>& geometries );

/**
* Creates a GeometryCollection geometry containing possible polygons formed from the constituent
* linework of a set of \a geometries. The input geometries must be fully noded (i.e. nodes exist
* at every common intersection of the geometries). The easiest way to ensure this is to first
* call unaryUnion() on the set of input geometries and then pass the result to polygonize().
* An empty geometry will be returned in the case of errors.
* @note added in QGIS 3.0
*/
static QgsGeometry polygonize( const QList< QgsGeometry>& geometries );

/** Converts the geometry to straight line segments, if it is a curved geometry type.
* @note added in QGIS 2.10
Expand Down
39 changes: 39 additions & 0 deletions src/core/geometry/qgsgeos.cpp
Expand Up @@ -1941,6 +1941,45 @@ double QgsGeos::lineLocatePoint( const QgsPointV2& point, QString* errorMsg ) co
return distance;
}

QgsGeometry QgsGeos::polygonize( const QList<QgsAbstractGeometry*>& geometries, QString* errorMsg )
{
GEOSGeometry** const lineGeosGeometries = new GEOSGeometry*[ geometries.size()];
int validLines = 0;
Q_FOREACH ( const QgsAbstractGeometry* g, geometries )
{
GEOSGeometry* l = asGeos( g );
if ( l )
{
lineGeosGeometries[validLines] = l;
validLines++;
}
}

try
{
GEOSGeomScopedPtr result( GEOSPolygonize_r( geosinit.ctxt, lineGeosGeometries, validLines ) );
for ( int i = 0; i < validLines; ++i )
{
GEOSGeom_destroy_r( geosinit.ctxt, lineGeosGeometries[i] );
}
delete[] lineGeosGeometries;
return QgsGeometry( fromGeos( result.get() ) );
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
for ( int i = 0; i < validLines; ++i )
{
GEOSGeom_destroy_r( geosinit.ctxt, lineGeosGeometries[i] );
}
delete[] lineGeosGeometries;
return QgsGeometry();
}
}


//! Extract coordinates of linestring's endpoints. Returns false on error.
static bool _linestringEndpoints( const GEOSGeometry* linestring, double& x1, double& y1, double& x2, double& y2 )
Expand Down
2 changes: 2 additions & 0 deletions src/core/geometry/qgsgeos.h
Expand Up @@ -141,6 +141,8 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine
*/
double lineLocatePoint( const QgsPointV2& point, QString* errorMsg = nullptr ) const;

static QgsGeometry polygonize( const QList<QgsAbstractGeometry*>& geometries, QString* errorMsg = nullptr );

/** Create a geometry from a GEOSGeometry
* @param geos GEOSGeometry. Ownership is NOT transferred.
*/
Expand Down
31 changes: 31 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -3724,6 +3724,37 @@ def testOrthogonalize(self):
self.assertTrue(compareWkt(result, exp, 0.00001),
"orthogonalize: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

def testPolygonize(self):
o = QgsGeometry.polygonize([])
self.assertFalse(o)
empty = QgsGeometry()
o = QgsGeometry.polygonize([empty])
self.assertFalse(o)
line = QgsGeometry.fromWkt('LineString()')
o = QgsGeometry.polygonize([line])
self.assertFalse(o)

l1 = QgsGeometry.fromWkt("LINESTRING (100 180, 20 20, 160 20, 100 180)")
l2 = QgsGeometry.fromWkt("LINESTRING (100 180, 80 60, 120 60, 100 180)")
o = QgsGeometry.polygonize([l1, l2])
exp = "GeometryCollection(POLYGON ((100 180, 160 20, 20 20, 100 180), (100 180, 80 60, 120 60, 100 180)),POLYGON ((100 180, 120 60, 80 60, 100 180)))"
result = o.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"polygonize: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

lines = [QgsGeometry.fromWkt('LineString(0 0, 1 1)'),
QgsGeometry.fromWkt('LineString(0 0, 0 1)'),
QgsGeometry.fromWkt('LineString(0 1, 1 1)'),
QgsGeometry.fromWkt('LineString(1 1, 1 0)'),
QgsGeometry.fromWkt('LineString(1 0, 0 0)'),
QgsGeometry.fromWkt('LineString(5 5, 6 6)'),
QgsGeometry.fromWkt('Point(0, 0)')]
o = QgsGeometry.polygonize(lines)
exp = "GeometryCollection (Polygon ((0 0, 1 1, 1 0, 0 0)),Polygon ((1 1, 0 0, 0 1, 1 1)))"
result = o.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"polygonize: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))


if __name__ == '__main__':
unittest.main()

0 comments on commit 4cebb46

Please sign in to comment.