Skip to content

Commit

Permalink
Expose GEOS clip by rect algorithm via QgsGeometry API
Browse files Browse the repository at this point in the history
Performs a fast, non-robust intersection between the geometry and
a rectangle. The returned geometry may be invalid.
  • Loading branch information
nyalldawson committed Jun 14, 2017
1 parent b620b6e commit bde0c72
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 15 deletions.
11 changes: 10 additions & 1 deletion python/core/geometry/qgsgeometry.sip
Expand Up @@ -828,6 +828,16 @@ Returns a geometry representing the points shared by this geometry and other.
:rtype: QgsGeometry
%End

QgsGeometry clipped( const QgsRectangle &rectangle );
%Docstring
Clips the geometry using the specified ``rectangle``.

Performs a fast, non-robust intersection between the geometry and
a ``rectangle``. The returned geometry may be invalid.
.. versionadded:: 3.0
:rtype: QgsGeometry
%End

QgsGeometry combine( const QgsGeometry &geometry ) const;
%Docstring
Returns a geometry representing all the points in this geometry and other (a
Expand Down Expand Up @@ -1129,7 +1139,6 @@ Ring 0 is outer ring and can't be deleted.
.. versionadded:: 2.10
%End


void draw( QPainter &p ) const;
%Docstring
Draws the geometry onto a QPainter
Expand Down
14 changes: 7 additions & 7 deletions src/core/geometry/qgsgeometry.cpp
Expand Up @@ -2089,17 +2089,17 @@ void QgsGeometry::mapToPixel( const QgsMapToPixel &mtp )
}
}

#if 0
void QgsGeometry::clip( const QgsRectangle &rect )
QgsGeometry QgsGeometry::clipped( const QgsRectangle &rectangle )
{
if ( d->geometry )
if ( !d->geometry || rectangle.isNull() || rectangle.isEmpty() )
{
detach();
d->geometry->clip( rect );
removeWkbGeos();
return QgsGeometry();
}

QgsGeos geos( d->geometry );
QgsAbstractGeometry *resultGeom = geos.clip( rectangle );
return QgsGeometry( resultGeom );
}
#endif

void QgsGeometry::draw( QPainter &p ) const
{
Expand Down
16 changes: 9 additions & 7 deletions src/core/geometry/qgsgeometry.h
Expand Up @@ -765,6 +765,15 @@ class CORE_EXPORT QgsGeometry
//! Returns a geometry representing the points shared by this geometry and other.
QgsGeometry intersection( const QgsGeometry &geometry ) const;

/**
* Clips the geometry using the specified \a rectangle.
*
* Performs a fast, non-robust intersection between the geometry and
* a \a rectangle. The returned geometry may be invalid.
* \since QGIS 3.0
*/
QgsGeometry clipped( const QgsRectangle &rectangle );

/** Returns a geometry representing all the points in this geometry and other (a
* union geometry operation).
* \note this operation is not called union since its a reserved word in C++.
Expand Down Expand Up @@ -999,13 +1008,6 @@ class CORE_EXPORT QgsGeometry
*/
void mapToPixel( const QgsMapToPixel &mtp );

// not implemented for 2.10
/* Clips the geometry using the specified rectangle
* \param rect clip rectangle
* \since QGIS 2.10
*/
// void clip( const QgsRectangle& rect );

/** Draws the geometry onto a QPainter
* \param p destination QPainter
* \since QGIS 2.10
Expand Down
24 changes: 24 additions & 0 deletions src/core/geometry/qgsgeos.cpp
Expand Up @@ -189,6 +189,30 @@ QgsAbstractGeometry *QgsGeos::difference( const QgsAbstractGeometry &geom, QStri
return overlay( geom, DIFFERENCE, errorMsg );
}

QgsAbstractGeometry *QgsGeos::clip( const QgsRectangle &rect, QString *errorMsg ) const
{
if ( !mGeos || rect.isNull() || rect.isEmpty() )
{
return nullptr;
}

try
{
GEOSGeomScopedPtr opGeom;
opGeom.reset( GEOSClipByRect_r( geosinit.ctxt, mGeos, rect.xMinimum(), rect.yMinimum(), rect.xMaximum(), rect.yMaximum() ) );
QgsAbstractGeometry *opResult = fromGeos( opGeom.get() );
return opResult;
}
catch ( GEOSException &e )
{
if ( errorMsg )
{
*errorMsg = e.what();
}
return nullptr;
}
}

QgsAbstractGeometry *QgsGeos::combine( const QgsAbstractGeometry &geom, QString *errorMsg ) const
{
return overlay( geom, UNION, errorMsg );
Expand Down
7 changes: 7 additions & 0 deletions src/core/geometry/qgsgeos.h
Expand Up @@ -45,6 +45,13 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine

QgsAbstractGeometry *intersection( const QgsAbstractGeometry &geom, QString *errorMsg = nullptr ) const override;
QgsAbstractGeometry *difference( const QgsAbstractGeometry &geom, QString *errorMsg = nullptr ) const override;

/**
* Performs a fast, non-robust intersection between the geometry and
* a \a rectangle. The returned geometry may be invalid.
*/
QgsAbstractGeometry *clip( const QgsRectangle &rectangle, QString *errorMsg = nullptr ) const;

QgsAbstractGeometry *combine( const QgsAbstractGeometry &geom, QString *errorMsg = nullptr ) const override;
QgsAbstractGeometry *combine( const QList< QgsAbstractGeometry *> &, QString *errorMsg = nullptr ) const override;
QgsAbstractGeometry *symDifference( const QgsAbstractGeometry &geom, QString *errorMsg = nullptr ) const override;
Expand Down
27 changes: 27 additions & 0 deletions tests/src/python/test_qgsgeometry.py
Expand Up @@ -4165,6 +4165,33 @@ def testPoint(self):
self.assertEqual(point_zm.z(), 3)
self.assertEqual(point_zm.m(), 4)

def testClipped(self):
tests = [["LINESTRING (1 1,1 9,9 9,9 1)", QgsRectangle(0, 0, 10, 10), "LINESTRING (1 1,1 9,9 9,9 1)"],
["LINESTRING (-1 -9,-1 11,9 11)", QgsRectangle(0, 0, 10, 10), "GEOMETRYCOLLECTION ()"],
["LINESTRING (-1 5,5 5,9 9)", QgsRectangle(0, 0, 10, 10), "LINESTRING (0 5,5 5,9 9)"],
["LINESTRING (5 5,8 5,12 5)", QgsRectangle(0, 0, 10, 10), "LINESTRING (5 5,8 5,10 5)"],
["LINESTRING (5 -1,5 5,1 2,-3 2,1 6)", QgsRectangle(0, 0, 10, 10), "MULTILINESTRING ((5 0,5 5,1 2,0 2),(0 5,1 6))"],
["LINESTRING (0 3,0 5,0 7)", QgsRectangle(0, 0, 10, 10), "GEOMETRYCOLLECTION ()"],
["LINESTRING (0 3,0 5,-1 7)", QgsRectangle(0, 0, 10, 10), "GEOMETRYCOLLECTION ()"],
["LINESTRING (0 3,0 5,2 7)", QgsRectangle(0, 0, 10, 10), "LINESTRING (0 5,2 7)"],
["LINESTRING (2 1,0 0,1 2)", QgsRectangle(0, 0, 10, 10), "LINESTRING (2 1,0 0,1 2)"],
["LINESTRING (3 3,0 3,0 5,2 7)", QgsRectangle(0, 0, 10, 10), "MULTILINESTRING ((3 3,0 3),(0 5,2 7))"],
["LINESTRING (5 5,10 5,20 5)", QgsRectangle(0, 0, 10, 10), "LINESTRING (5 5,10 5)"],
["LINESTRING (3 3,0 6,3 9)", QgsRectangle(0, 0, 10, 10), "LINESTRING (3 3,0 6,3 9)"],
["POLYGON ((5 5,5 6,6 6,6 5,5 5))", QgsRectangle(0, 0, 10, 10), "POLYGON ((5 5,5 6,6 6,6 5,5 5))"],
["POLYGON ((15 15,15 16,16 16,16 15,15 15))", QgsRectangle(0, 0, 10, 10), "GEOMETRYCOLLECTION ()"],
["POLYGON ((-1 -1,-1 11,11 11,11 -1,-1 -1))", QgsRectangle(0, 0, 10, 10), "Polygon ((0 0, 0 10, 10 10, 10 0, 0 0))"],
["POLYGON ((-1 -1,-1 5,5 5,5 -1,-1 -1))", QgsRectangle(0, 0, 10, 10), "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0))"],
["POLYGON ((-2 -2,-2 5,5 5,5 -2,-2 -2), (3 3,4 4,4 2,3 3))", QgsRectangle(0, 0, 10, 10), "Polygon ((0 0, 0 5, 5 5, 5 0, 0 0),(3 3, 4 4, 4 2, 3 3))"]
]
for t in tests:
input = QgsGeometry.fromWkt(t[0])
o = input.clipped(t[1])
exp = t[2]
result = o.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"clipped: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))


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

0 comments on commit bde0c72

Please sign in to comment.