Skip to content

Commit 3ffbd84

Browse files
committed
Add a bounding box intersection test to QgsGeometry
We only previously had methods for exact intersections - this commit adds a new QgsGeometry.boundingBoxIntersects() method which can be used to test if just the bounding boxes of geometries/rectangles intersect. It's fast, and doesn't care about invalid geometries (unlike the exact intersects checks)
1 parent 9471c5d commit 3ffbd84

File tree

4 files changed

+125
-6
lines changed

4 files changed

+125
-6
lines changed

python/core/geometry/qgsgeometry.sip.in

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -755,14 +755,48 @@ were found.
755755
.. versionadded:: 3.0
756756
%End
757757

758-
bool intersects( const QgsRectangle &r ) const;
758+
bool intersects( const QgsRectangle &rectangle ) const;
759759
%Docstring
760-
Tests for intersection with a rectangle (uses GEOS)
760+
Returns true if this geometry exactly intersects with a ``rectangle``. This test is exact
761+
and can be slow for complex geometries.
762+
763+
The GEOS library is used to perform the intersection test. Geometries which are not
764+
valid may return incorrect results.
765+
766+
.. seealso:: :py:func:`boundingBoxIntersects`
761767
%End
762768

763769
bool intersects( const QgsGeometry &geometry ) const;
764770
%Docstring
765-
Tests for intersection with a geometry (uses GEOS)
771+
Returns true if this geometry exactly intersects with another ``geometry``. This test is exact
772+
and can be slow for complex geometries.
773+
774+
The GEOS library is used to perform the intersection test. Geometries which are not
775+
valid may return incorrect results.
776+
777+
.. seealso:: :py:func:`boundingBoxIntersects`
778+
%End
779+
780+
bool boundingBoxIntersects( const QgsRectangle &rectangle ) const;
781+
%Docstring
782+
Returns true if the bounding box of this geometry intersects with a ``rectangle``. Since this
783+
test only considers the bounding box of the geometry, is is very fast to calculate and handles invalid
784+
geometries.
785+
786+
.. seealso:: :py:func:`intersects`
787+
788+
.. versionadded:: 3.0
789+
%End
790+
791+
bool boundingBoxIntersects( const QgsGeometry &geometry ) const;
792+
%Docstring
793+
Returns true if the bounding box of this geometry intersects with the bounding box of another ``geometry``. Since this
794+
test only considers the bounding box of the geometries, is is very fast to calculate and handles invalid
795+
geometries.
796+
797+
.. seealso:: :py:func:`intersects`
798+
799+
.. versionadded:: 3.0
766800
%End
767801

768802
bool contains( const QgsPointXY *p ) const;

src/core/geometry/qgsgeometry.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,6 +1106,26 @@ bool QgsGeometry::intersects( const QgsGeometry &geometry ) const
11061106
return geos.intersects( geometry.d->geometry.get(), &mLastError );
11071107
}
11081108

1109+
bool QgsGeometry::boundingBoxIntersects( const QgsRectangle &rectangle ) const
1110+
{
1111+
if ( !d->geometry )
1112+
{
1113+
return false;
1114+
}
1115+
1116+
return d->geometry->boundingBox().intersects( rectangle );
1117+
}
1118+
1119+
bool QgsGeometry::boundingBoxIntersects( const QgsGeometry &geometry ) const
1120+
{
1121+
if ( !d->geometry || geometry.isNull() )
1122+
{
1123+
return false;
1124+
}
1125+
1126+
return d->geometry->boundingBox().intersects( geometry.constGet()->boundingBox() );
1127+
}
1128+
11091129
bool QgsGeometry::contains( const QgsPointXY *p ) const
11101130
{
11111131
if ( !d->geometry || !p )

src/core/geometry/qgsgeometry.h

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -798,12 +798,50 @@ class CORE_EXPORT QgsGeometry
798798
*/
799799
bool removeDuplicateNodes( double epsilon = 4 * DBL_EPSILON, bool useZValues = false );
800800

801-
//! Tests for intersection with a rectangle (uses GEOS)
802-
bool intersects( const QgsRectangle &r ) const;
801+
/**
802+
* Returns true if this geometry exactly intersects with a \a rectangle. This test is exact
803+
* and can be slow for complex geometries.
804+
*
805+
* The GEOS library is used to perform the intersection test. Geometries which are not
806+
* valid may return incorrect results.
807+
*
808+
* \see boundingBoxIntersects()
809+
*/
810+
bool intersects( const QgsRectangle &rectangle ) const;
803811

804-
//! Tests for intersection with a geometry (uses GEOS)
812+
/**
813+
* Returns true if this geometry exactly intersects with another \a geometry. This test is exact
814+
* and can be slow for complex geometries.
815+
*
816+
* The GEOS library is used to perform the intersection test. Geometries which are not
817+
* valid may return incorrect results.
818+
*
819+
* \see boundingBoxIntersects()
820+
*/
805821
bool intersects( const QgsGeometry &geometry ) const;
806822

823+
/**
824+
* Returns true if the bounding box of this geometry intersects with a \a rectangle. Since this
825+
* test only considers the bounding box of the geometry, is is very fast to calculate and handles invalid
826+
* geometries.
827+
*
828+
* \see intersects()
829+
*
830+
* \since QGIS 3.0
831+
*/
832+
bool boundingBoxIntersects( const QgsRectangle &rectangle ) const;
833+
834+
/**
835+
* Returns true if the bounding box of this geometry intersects with the bounding box of another \a geometry. Since this
836+
* test only considers the bounding box of the geometries, is is very fast to calculate and handles invalid
837+
* geometries.
838+
*
839+
* \see intersects()
840+
*
841+
* \since QGIS 3.0
842+
*/
843+
bool boundingBoxIntersects( const QgsGeometry &geometry ) const;
844+
807845
//! Tests for containment of a point (uses GEOS)
808846
bool contains( const QgsPointXY *p ) const;
809847

tests/src/python/test_qgsgeometry.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4286,6 +4286,33 @@ def testHausdorffDensify(self):
42864286
self.assertAlmostEqual(o, exp, 5,
42874287
"mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(t[0], t[1], exp, o))
42884288

4289+
def testBoundingBoxIntersects(self):
4290+
tests = [
4291+
["LINESTRING (0 0, 100 100)", "LINESTRING (90 0, 100 0)", True],
4292+
["LINESTRING (0 0, 100 100)", "LINESTRING (101 0, 102 0)", False],
4293+
["POINT(20 1)", "LINESTRING( 0 0, 100 100 )", True],
4294+
["POINT(20 1)", "POINT(21 1)", False],
4295+
["POINT(20 1)", "POINT(20 1)", True]
4296+
]
4297+
for t in tests:
4298+
g1 = QgsGeometry.fromWkt(t[0])
4299+
g2 = QgsGeometry.fromWkt(t[1])
4300+
res = g1.boundingBoxIntersects(g2)
4301+
self.assertEqual(res, t[2], "mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(g1.asWkt(), g2.asWkt(), t[2], res))
4302+
4303+
def testBoundingBoxIntersectsRectangle(self):
4304+
tests = [
4305+
["LINESTRING (0 0, 100 100)", QgsRectangle(90, 0, 100, 10), True],
4306+
["LINESTRING (0 0, 100 100)", QgsRectangle(101, 0, 102, 10), False],
4307+
["POINT(20 1)", QgsRectangle(0, 0, 100, 100), True],
4308+
["POINT(20 1)", QgsRectangle(21, 1, 21, 1), False],
4309+
["POINT(20 1)", QgsRectangle(20, 1, 20, 1), True]
4310+
]
4311+
for t in tests:
4312+
g1 = QgsGeometry.fromWkt(t[0])
4313+
res = g1.boundingBoxIntersects(t[1])
4314+
self.assertEqual(res, t[2], "mismatch for {} to {}, expected:\n{}\nGot:\n{}\n".format(g1.asWkt(), t[1].toString(), t[2], res))
4315+
42894316
def renderGeometry(self, geom, use_pen, as_polygon=False, as_painter_path=False):
42904317
image = QImage(200, 200, QImage.Format_RGB32)
42914318
image.fill(QColor(0, 0, 0))

0 commit comments

Comments
 (0)