summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Tabone <olivier.tabone@ripplemotion.fr>2023-07-26 23:18:29 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-08-05 18:23:38 +0200
commit0f3b1a783dfa36cb23aae0bb954756d0edcd9fc1 (patch)
tree8b0fff946225328e3f936d34cc563043c353febc
parent8edaf07a28a3e3848ab1edbdcd2fdc25e6bd6015 (diff)
Fixed #34739 -- Added GEOSGeometry.equals_identical() method.
-rw-r--r--django/contrib/gis/geos/geometry.py12
-rw-r--r--django/contrib/gis/geos/prototypes/__init__.py1
-rw-r--r--django/contrib/gis/geos/prototypes/predicates.py1
-rw-r--r--docs/ref/contrib/gis/geos.txt9
-rw-r--r--docs/releases/5.0.txt3
-rw-r--r--tests/gis_tests/geos_tests/test_geos.py72
6 files changed, 96 insertions, 2 deletions
diff --git a/django/contrib/gis/geos/geometry.py b/django/contrib/gis/geos/geometry.py
index 38e5fe554c..00b36af0a6 100644
--- a/django/contrib/gis/geos/geometry.py
+++ b/django/contrib/gis/geos/geometry.py
@@ -11,7 +11,7 @@ from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.base import GEOSBase
from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException
-from django.contrib.gis.geos.libgeos import GEOM_PTR
+from django.contrib.gis.geos.libgeos import GEOM_PTR, geos_version_tuple
from django.contrib.gis.geos.mutable_list import ListMixin
from django.contrib.gis.geos.prepared import PreparedGeometry
from django.contrib.gis.geos.prototypes.io import ewkb_w, wkb_r, wkb_w, wkt_r, wkt_w
@@ -318,6 +318,16 @@ class GEOSGeometryBase(GEOSBase):
"""
return capi.geos_equalsexact(self.ptr, other.ptr, float(tolerance))
+ def equals_identical(self, other):
+ """
+ Return true if the two Geometries are point-wise equivalent.
+ """
+ if geos_version_tuple() < (3, 12):
+ raise GEOSException(
+ "GEOSGeometry.equals_identical() requires GEOS >= 3.12.0."
+ )
+ return capi.geos_equalsidentical(self.ptr, other.ptr)
+
def intersects(self, other):
"Return true if disjoint return false."
return capi.geos_intersects(self.ptr, other.ptr)
diff --git a/django/contrib/gis/geos/prototypes/__init__.py b/django/contrib/gis/geos/prototypes/__init__.py
index 8fa98f98e7..3e980b9b89 100644
--- a/django/contrib/gis/geos/prototypes/__init__.py
+++ b/django/contrib/gis/geos/prototypes/__init__.py
@@ -51,6 +51,7 @@ from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
geos_disjoint,
geos_equals,
geos_equalsexact,
+ geos_equalsidentical,
geos_hasz,
geos_intersects,
geos_isclosed,
diff --git a/django/contrib/gis/geos/prototypes/predicates.py b/django/contrib/gis/geos/prototypes/predicates.py
index d2e113a734..32b790173a 100644
--- a/django/contrib/gis/geos/prototypes/predicates.py
+++ b/django/contrib/gis/geos/prototypes/predicates.py
@@ -38,6 +38,7 @@ geos_equals = BinaryPredicate("GEOSEquals")
geos_equalsexact = BinaryPredicate(
"GEOSEqualsExact", argtypes=[GEOM_PTR, GEOM_PTR, c_double]
)
+geos_equalsidentical = BinaryPredicate("GEOSEqualsIdentical")
geos_intersects = BinaryPredicate("GEOSIntersects")
geos_overlaps = BinaryPredicate("GEOSOverlaps")
geos_relatepattern = BinaryPredicate(
diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt
index 564d49bbb6..471103bf97 100644
--- a/docs/ref/contrib/gis/geos.txt
+++ b/docs/ref/contrib/gis/geos.txt
@@ -483,6 +483,15 @@ return a boolean.
``poly1.equals_exact(poly2, 0.001)`` will compare equality to within
one thousandth of a unit.
+.. method:: GEOSGeometry.equals_identical(other)
+
+ .. versionadded:: 5.0
+
+ Returns ``True`` if the two geometries are point-wise equivalent by
+ checking that the structure, ordering, and values of all vertices are
+ identical in all dimensions. ``NaN`` values are considered to be equal to
+ other ``NaN`` values. Requires GEOS 3.12.
+
.. method:: GEOSGeometry.intersects(other)
Returns ``True`` if :meth:`GEOSGeometry.disjoint` is ``False``.
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index 030e16a054..8f15171d17 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -192,6 +192,9 @@ Minor features
* Added support for GEOS 3.12.
+* The new :meth:`.GEOSGeometry.equals_identical` method allows point-wise
+ equivalence checking of geometries.
+
:mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py
index 9f1ba6d45f..0051a88b07 100644
--- a/tests/gis_tests/geos_tests/test_geos.py
+++ b/tests/gis_tests/geos_tests/test_geos.py
@@ -1,11 +1,12 @@
import ctypes
import itertools
import json
+import math
import pickle
import random
from binascii import a2b_hex
from io import BytesIO
-from unittest import mock
+from unittest import mock, skipIf
from django.contrib.gis import gdal
from django.contrib.gis.geos import (
@@ -241,6 +242,75 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
self.assertEqual(p0, "SRID=0;POINT (5 23)")
self.assertNotEqual(p1, "SRID=0;POINT (5 23)")
+ @skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
+ def test_equals_identical(self):
+ tests = [
+ # Empty inputs of different types are not equals_identical.
+ ("POINT EMPTY", "LINESTRING EMPTY", False),
+ # Empty inputs of different dimensions are not equals_identical.
+ ("POINT EMPTY", "POINT Z EMPTY", False),
+ # Non-empty inputs of different dimensions are not equals_identical.
+ ("POINT Z (1 2 3)", "POINT M (1 2 3)", False),
+ ("POINT ZM (1 2 3 4)", "POINT Z (1 2 3)", False),
+ # Inputs with different structure are not equals_identical.
+ ("LINESTRING (1 1, 2 2)", "MULTILINESTRING ((1 1, 2 2))", False),
+ # Inputs with different types are not equals_identical.
+ (
+ "GEOMETRYCOLLECTION (LINESTRING (1 1, 2 2))",
+ "MULTILINESTRING ((1 1, 2 2))",
+ False,
+ ),
+ # Same lines are equals_identical.
+ ("LINESTRING M (1 1 0, 2 2 1)", "LINESTRING M (1 1 0, 2 2 1)", True),
+ # Different lines are not equals_identical.
+ ("LINESTRING M (1 1 0, 2 2 1)", "LINESTRING M (1 1 1, 2 2 1)", False),
+ # Same polygons are equals_identical.
+ ("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((0 0, 1 0, 1 1, 0 0))", True),
+ # Different polygons are not equals_identical.
+ ("POLYGON ((0 0, 1 0, 1 1, 0 0))", "POLYGON ((1 0, 1 1, 0 0, 1 0))", False),
+ # Different polygons (number of holes) are not equals_identical.
+ (
+ "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1))",
+ (
+ "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 2 1, 2 2, 1 1), "
+ "(3 3, 4 3, 4 4, 3 3))"
+ ),
+ False,
+ ),
+ # Same collections are equals_identical.
+ (
+ "MULTILINESTRING ((1 1, 2 2), (2 2, 3 3))",
+ "MULTILINESTRING ((1 1, 2 2), (2 2, 3 3))",
+ True,
+ ),
+ # Different collections (structure) are not equals_identical.
+ (
+ "MULTILINESTRING ((1 1, 2 2), (2 2, 3 3))",
+ "MULTILINESTRING ((2 2, 3 3), (1 1, 2 2))",
+ False,
+ ),
+ ]
+ for g1, g2, is_equal_identical in tests:
+ with self.subTest(g1=g1, g2=g2):
+ self.assertIs(
+ fromstr(g1).equals_identical(fromstr(g2)), is_equal_identical
+ )
+
+ @skipIf(geos_version_tuple() < (3, 12), "GEOS >= 3.12.0 is required")
+ def test_infinite_values_equals_identical(self):
+ # Input with identical infinite values are equals_identical.
+ g1 = Point(x=float("nan"), y=math.inf)
+ g2 = Point(x=float("nan"), y=math.inf)
+ self.assertIs(g1.equals_identical(g2), True)
+
+ @mock.patch("django.contrib.gis.geos.libgeos.geos_version", lambda: b"3.11.0")
+ def test_equals_identical_geos_version(self):
+ g1 = fromstr("POINT (1 2 3)")
+ g2 = fromstr("POINT (1 2 3)")
+ msg = "GEOSGeometry.equals_identical() requires GEOS >= 3.12.0"
+ with self.assertRaisesMessage(GEOSException, msg):
+ g1.equals_identical(g2)
+
def test_points(self):
"Testing Point objects."
prev = fromstr("POINT(0 0)")