Repository: geopandas/geopandas
Pull request #3017 (2023-09-14 10:44:01)

DEP: GeoSeries.geom_almost_equals
Continuation of #2604


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a4e770f..5c8066a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -48,6 +48,10 @@ Notes on (optional) dependencies:
   listed dependencies have now changed to shapely 1.8.0, fiona 1.8.21, pyproj 3.3.0 and
   matplotlib 3.5.0 (#3001)
 
+Deprecations and compatibility notes:
+ - `geom_almost_equals()` methods have been deprecated and  
+   `geom_equals_exact()` should be used instead (#2604).
+
 ## Version 0.13.2 (Jun 6, 2023)
 
 Bug fix:
diff --git a/doc/source/docs/user_guide/data_structures.rst b/doc/source/docs/user_guide/data_structures.rst
index 9c9f2675..ff4a9d7c 100644
--- a/doc/source/docs/user_guide/data_structures.rst
+++ b/doc/source/docs/user_guide/data_structures.rst
@@ -72,7 +72,7 @@ Basic methods
 Relationship tests
 ^^^^^^^^^^^^^^^^^^^
 
-* :meth:`~GeoSeries.geom_almost_equals`: is shape almost the same as ``other`` (good when floating point precision issues make shapes slightly different)
+* :meth:`~GeoSeries.geom_equals_exact`: is shape the same as ``other`` (up to a specified decimal place tolerance)
 * :meth:`~GeoSeries.contains`: is shape contained within ``other``
 * :meth:`~GeoSeries.intersects`: does shape intersect ``other``
 
diff --git a/geopandas/_vectorized.py b/geopandas/_vectorized.py
index 5e4a0649..9f1a8b39 100644
--- a/geopandas/_vectorized.py
+++ b/geopandas/_vectorized.py
@@ -904,13 +904,6 @@ def equals_exact(data, other, tolerance):
         return _binary_predicate("equals_exact", data, other, tolerance=tolerance)
 
 
-def almost_equals(self, other, decimal):
-    if compat.USE_PYGEOS or compat.USE_SHAPELY_20:
-        return self.equals_exact(other, 0.5 * 10 ** (-decimal))
-    else:
-        return _binary_predicate("almost_equals", self, other, decimal=decimal)
-
-
 #
 # Binary operations that return new geometries
 #
diff --git a/geopandas/array.py b/geopandas/array.py
index 944d7a66..5e84d16a 100644
--- a/geopandas/array.py
+++ b/geopandas/array.py
@@ -644,8 +644,13 @@ class GeometryArray(ExtensionArray):
         return self._binary_method("equals_exact", self, other, tolerance=tolerance)
 
     def geom_almost_equals(self, other, decimal):
+        warnings.warn(
+            "The 'geom_almost_equals()' method is deprecated because the name is "
+            "confusing. The 'geom_equals_exact()' method should be used instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
         return self.geom_equals_exact(other, 0.5 * 10 ** (-decimal))
-        # return _binary_predicate("almost_equals", self, other, decimal=decimal)
 
     #
     # Binary operations that return new geometries
diff --git a/geopandas/base.py b/geopandas/base.py
index 63a0a6eb..625633a7 100644
--- a/geopandas/base.py
+++ b/geopandas/base.py
@@ -1,4 +1,5 @@
 from warnings import warn
+import warnings
 
 import numpy as np
 import pandas as pd
@@ -1441,7 +1442,6 @@ GeometryCollection
 
         See also
         --------
-        GeoSeries.geom_almost_equals
         GeoSeries.geom_equals_exact
 
         """
@@ -1514,8 +1514,15 @@ GeometryCollection
         GeoSeries.geom_equals_exact
 
         """
+        warnings.warn(
+            "The 'geom_almost_equals()' method is deprecated because the name is "
+            "confusing. The 'geom_equals_exact()' method should be used instead.",
+            FutureWarning,
+            stacklevel=2,
+        )
+        tolerance = 0.5 * 10 ** (-decimal)
         return _binary_op(
-            "geom_almost_equals", self, other, decimal=decimal, align=align
+            "geom_equals_exact", self, other, tolerance=tolerance, align=align
         )
 
     def geom_equals_exact(self, other, tolerance, align=True):
@@ -1579,7 +1586,6 @@ GeometryCollection
         See also
         --------
         GeoSeries.geom_equals
-        GeoSeries.geom_almost_equals
         """
         return _binary_op(
             "geom_equals_exact", self, other, tolerance=tolerance, align=align


Test patch:
diff --git a/geopandas/testing.py b/geopandas/testing.py
index 54b68686..580586f0 100644
--- a/geopandas/testing.py
+++ b/geopandas/testing.py
@@ -80,8 +80,7 @@ def _geom_almost_equals_mask(this, that):
 
     Parameters
     ----------
-    this, that : arrays of Geo objects (or anything that has an `is_empty`
-                 property)
+    this, that : arrays of Geo objects
 
     Returns
     -------
@@ -90,7 +89,7 @@ def _geom_almost_equals_mask(this, that):
     """
 
     return (
-        this.geom_almost_equals(that)
+        this.geom_equals_exact(that, tolerance=0.5 * 10 ** (-6))
         | (this.is_empty & that.is_empty)
         | (_isna(this) & _isna(that))
     )
@@ -114,6 +113,9 @@ def geom_almost_equals(this, that):
     bool
         True if all geometries in left almost equal geometries in right
     """
+    if isinstance(this, GeoDataFrame) and isinstance(that, GeoDataFrame):
+        this = this.geometry
+        that = that.geometry
 
     return _geom_almost_equals_mask(this, that).all()
 
@@ -144,7 +146,8 @@ def assert_geoseries_equal(
         Check that both are same type (*and* are GeoSeries). If False,
         will attempt to convert both into GeoSeries.
     check_less_precise : bool, default False
-        If True, use geom_almost_equals. if False, use geom_equals.
+        If True, use geom_equals_exact with relative error of 0.5e-6.
+        If False, use geom_equals.
     check_geom_type : bool, default False
         If True, check that all the geom types are equal.
     check_crs: bool, default True
@@ -153,7 +156,7 @@ def assert_geoseries_equal(
     normalize: bool, default False
         If True, normalize the geometries before comparing equality.
         Typically useful with ``check_less_precise=True``, which uses
-        ``geom_almost_equals`` and requires exact coordinate order.
+        ``geom_equals_exact`` and requires exact coordinate order.
     """
     assert len(left) == len(right), "%d != %d" % (len(left), len(right))
 
@@ -265,7 +268,7 @@ def assert_geodataframe_equal(
     check_like : bool, default False
         If true, ignore the order of rows & columns
     check_less_precise : bool, default False
-        If True, use geom_almost_equals. if False, use geom_equals.
+        If True, use geom_equals_exact. if False, use geom_equals.
     check_geom_type : bool, default False
         If True, check that all the geom types are equal.
     check_crs: bool, default True
@@ -274,7 +277,7 @@ def assert_geodataframe_equal(
     normalize: bool, default False
         If True, normalize the geometries before comparing equality.
         Typically useful with ``check_less_precise=True``, which uses
-        ``geom_almost_equals`` and requires exact coordinate order.
+        ``geom_equals_exact`` and requires exact coordinate order.
     """
     try:
         # added from pandas 0.20
diff --git a/geopandas/tests/test_dissolve.py b/geopandas/tests/test_dissolve.py
index 0a93dea4..e5d90513 100644
--- a/geopandas/tests/test_dissolve.py
+++ b/geopandas/tests/test_dissolve.py
@@ -10,7 +10,7 @@ from pandas.testing import assert_frame_equal
 import pytest
 
 from geopandas._compat import PANDAS_GE_15, PANDAS_GE_20
-from geopandas.testing import assert_geodataframe_equal
+from geopandas.testing import assert_geodataframe_equal, geom_almost_equals
 
 
 @pytest.fixture
@@ -61,7 +61,7 @@ def expected_mean(merged_shapes):
 def test_geom_dissolve(nybb_polydf, first):
     test = nybb_polydf.dissolve("manhattan_bronx")
     assert test.geometry.name == "myshapes"
-    assert test.geom_almost_equals(first).all()
+    assert geom_almost_equals(test, first)
 
 
 def test_dissolve_retains_existing_crs(nybb_polydf):
diff --git a/geopandas/tests/test_geom_methods.py b/geopandas/tests/test_geom_methods.py
index 61220086..4d21242e 100644
--- a/geopandas/tests/test_geom_methods.py
+++ b/geopandas/tests/test_geom_methods.py
@@ -23,8 +23,8 @@ from shapely import wkt
 from geopandas import GeoDataFrame, GeoSeries
 from geopandas.base import GeoPandasBase
 
-from geopandas.testing import assert_geodataframe_equal
-from geopandas.tests.util import assert_geoseries_equal, geom_almost_equals, geom_equals
+from geopandas.testing import assert_geodataframe_equal, geom_almost_equals
+from geopandas.tests.util import assert_geoseries_equal, geom_equals
 from geopandas import _compat as compat
 from pandas.testing import assert_frame_equal, assert_index_equal, assert_series_equal
 import pytest
@@ -1321,7 +1321,7 @@ class TestGeomMethods:
     def test_minimum_bounding_circle(self):
         mbc = self.g1.minimum_bounding_circle()
         centers = GeoSeries([Point(0.5, 0.5)] * 2)
-        assert np.all(mbc.centroid.geom_equals_exact(centers, 0.001))
+        assert np.all(mbc.centroid.geom_almost_equals(centers, 0.001))
         assert_series_equal(
             mbc.area,
             Series([1.560723, 1.560723]),
diff --git a/geopandas/tests/test_geoseries.py b/geopandas/tests/test_geoseries.py
index 7ec315ad..9b7d1290 100644
--- a/geopandas/tests/test_geoseries.py
+++ b/geopandas/tests/test_geoseries.py
@@ -1,6 +1,7 @@
 import json
 import os
 import random
+import re
 import shutil
 import tempfile
 import warnings
@@ -25,7 +26,7 @@ from shapely.geometry.base import BaseGeometry
 from geopandas import GeoSeries, GeoDataFrame, read_file, datasets, clip
 from geopandas._compat import ignore_shapely2_warnings
 from geopandas.array import GeometryArray, GeometryDtype
-from geopandas.testing import assert_geoseries_equal
+from geopandas.testing import assert_geoseries_equal, geom_almost_equals
 
 from geopandas.tests.util import geom_equals
 from pandas.testing import assert_series_equal
@@ -146,15 +147,16 @@ class TestSeries:
 
     def test_geom_almost_equals(self):
         # TODO: test decimal parameter
-        assert np.all(self.g1.geom_almost_equals(self.g1))
-        assert_array_equal(self.g1.geom_almost_equals(self.sq), [False, True])
+        with pytest.warns(FutureWarning, match=re.escape("The 'geom_almost_equals()'")):
+            assert np.all(self.g1.geom_almost_equals(self.g1))
+            assert_array_equal(self.g1.geom_almost_equals(self.sq), [False, True])
 
-        assert_array_equal(
-            self.a1.geom_almost_equals(self.a2, align=True), [False, True, False]
-        )
-        assert_array_equal(
-            self.a1.geom_almost_equals(self.a2, align=False), [False, False]
-        )
+            assert_array_equal(
+                self.a1.geom_almost_equals(self.a2, align=True), [False, True, False]
+            )
+            assert_array_equal(
+                self.a1.geom_almost_equals(self.a2, align=False), [False, False]
+            )
 
     def test_geom_equals_exact(self):
         # TODO: test tolerance parameter
@@ -200,7 +202,7 @@ class TestSeries:
     def test_transform(self):
         utm18n = self.landmarks.to_crs(epsg=26918)
         lonlat = utm18n.to_crs(epsg=4326)
-        assert np.all(self.landmarks.geom_almost_equals(lonlat))
+        assert geom_almost_equals(self.landmarks, lonlat)
         with pytest.raises(ValueError):
             self.g1.to_crs(epsg=4326)
         with pytest.raises(ValueError):
@@ -260,24 +262,24 @@ class TestSeries:
         # As string
         reprojected = self.g3.to_crs("+proj=utm +zone=30")
         reprojected_back = reprojected.to_crs(epsg=4326)
-        assert np.all(self.g3.geom_almost_equals(reprojected_back))
+        assert geom_almost_equals(self.g3, reprojected_back)
 
         # As dict
         reprojected = self.g3.to_crs({"proj": "utm", "zone": "30"})
         reprojected_back = reprojected.to_crs(epsg=4326)
-        assert np.all(self.g3.geom_almost_equals(reprojected_back))
+        assert geom_almost_equals(self.g3, reprojected_back)
 
         # Set to equivalent string, convert, compare to original
         copy = self.g3.copy()
         copy.crs = "epsg:4326"
         reprojected = copy.to_crs({"proj": "utm", "zone": "30"})
         reprojected_back = reprojected.to_crs(epsg=4326)
-        assert np.all(self.g3.geom_almost_equals(reprojected_back))
+        assert geom_almost_equals(self.g3, reprojected_back)
 
         # Conversions by different format
         reprojected_string = self.g3.to_crs("+proj=utm +zone=30")
         reprojected_dict = self.g3.to_crs({"proj": "utm", "zone": "30"})
-        assert np.all(reprojected_string.geom_almost_equals(reprojected_dict))
+        assert geom_almost_equals(reprojected_string, reprojected_dict)
 
     def test_from_wkb(self):
         assert_geoseries_equal(self.g1, GeoSeries.from_wkb([self.t1.wkb, self.sq.wkb]))