1
0
mirror of https://github.com/django/django.git synced 2025-06-03 18:49:12 +00:00

Fixed #34739 -- Added GEOSGeometry.equals_identical() method.

This commit is contained in:
Olivier Tabone 2023-07-26 23:18:29 +02:00 committed by Mariusz Felisiak
parent 8edaf07a28
commit 0f3b1a783d
6 changed files with 96 additions and 2 deletions

View File

@ -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.base import GEOSBase
from django.contrib.gis.geos.coordseq import GEOSCoordSeq from django.contrib.gis.geos.coordseq import GEOSCoordSeq
from django.contrib.gis.geos.error import GEOSException 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.mutable_list import ListMixin
from django.contrib.gis.geos.prepared import PreparedGeometry 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 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)) 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): def intersects(self, other):
"Return true if disjoint return false." "Return true if disjoint return false."
return capi.geos_intersects(self.ptr, other.ptr) return capi.geos_intersects(self.ptr, other.ptr)

View File

@ -51,6 +51,7 @@ from django.contrib.gis.geos.prototypes.predicates import ( # NOQA
geos_disjoint, geos_disjoint,
geos_equals, geos_equals,
geos_equalsexact, geos_equalsexact,
geos_equalsidentical,
geos_hasz, geos_hasz,
geos_intersects, geos_intersects,
geos_isclosed, geos_isclosed,

View File

@ -38,6 +38,7 @@ geos_equals = BinaryPredicate("GEOSEquals")
geos_equalsexact = BinaryPredicate( geos_equalsexact = BinaryPredicate(
"GEOSEqualsExact", argtypes=[GEOM_PTR, GEOM_PTR, c_double] "GEOSEqualsExact", argtypes=[GEOM_PTR, GEOM_PTR, c_double]
) )
geos_equalsidentical = BinaryPredicate("GEOSEqualsIdentical")
geos_intersects = BinaryPredicate("GEOSIntersects") geos_intersects = BinaryPredicate("GEOSIntersects")
geos_overlaps = BinaryPredicate("GEOSOverlaps") geos_overlaps = BinaryPredicate("GEOSOverlaps")
geos_relatepattern = BinaryPredicate( geos_relatepattern = BinaryPredicate(

View File

@ -483,6 +483,15 @@ return a boolean.
``poly1.equals_exact(poly2, 0.001)`` will compare equality to within ``poly1.equals_exact(poly2, 0.001)`` will compare equality to within
one thousandth of a unit. 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) .. method:: GEOSGeometry.intersects(other)
Returns ``True`` if :meth:`GEOSGeometry.disjoint` is ``False``. Returns ``True`` if :meth:`GEOSGeometry.disjoint` is ``False``.

View File

@ -192,6 +192,9 @@ Minor features
* Added support for GEOS 3.12. * Added support for GEOS 3.12.
* The new :meth:`.GEOSGeometry.equals_identical` method allows point-wise
equivalence checking of geometries.
:mod:`django.contrib.messages` :mod:`django.contrib.messages`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -1,11 +1,12 @@
import ctypes import ctypes
import itertools import itertools
import json import json
import math
import pickle import pickle
import random import random
from binascii import a2b_hex from binascii import a2b_hex
from io import BytesIO from io import BytesIO
from unittest import mock from unittest import mock, skipIf
from django.contrib.gis import gdal from django.contrib.gis import gdal
from django.contrib.gis.geos import ( from django.contrib.gis.geos import (
@ -241,6 +242,75 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
self.assertEqual(p0, "SRID=0;POINT (5 23)") self.assertEqual(p0, "SRID=0;POINT (5 23)")
self.assertNotEqual(p1, "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): def test_points(self):
"Testing Point objects." "Testing Point objects."
prev = fromstr("POINT(0 0)") prev = fromstr("POINT(0 0)")