Repository: Zeroto521/my-data-toolkit

Issue:
ENH: New geoaccessor to calculate two coordinates distance on earth
<!--
Thanks for contributing a pull request!

Please follow these standard acronyms to start the commit message:

- ENH: enhancement
- BUG: bug fix
- DOC: documentation
- TYP: type annotations
- TST: addition or modification of tests
- MAINT: maintenance commit (refactoring, typos, etc.)
- BLD: change related to building
- REL: related to releasing
- API: an (incompatible) API change
- DEP: deprecate something, or remove a deprecated object
- DEV: development tool or utility
- REV: revert an earlier commit
- PERF: performance improvement
- BOT: always commit via a bot
- CI: related to CI or CD
- CLN: Code cleanup
-->

- [ ] closes #xxxx
- [ ] whatsnew entry



Discussion:
# [Codecov](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25) Report
> Merging [#699](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25) (3e2fbea) into [main](https://codecov.io/gh/Zeroto521/my-data-toolkit/commit/85ec5cf81287fbd41cd5e36955459bf9fd0ffaa6?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25) (85ec5cf) will **decrease** coverage by `0.12%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##             main     #699      +/-   ##
==========================================
- Coverage   99.55%   99.43%   -0.13%     
==========================================
  Files         116      118       +2     
  Lines        1361     1404      +43     
  Branches      123      128       +5     
==========================================
+ Hits         1355     1396      +41     
- Misses          1        2       +1     
- Partials        5        6       +1     
```


| [Impacted Files](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25) | Coverage Δ | |
|---|---|---|
| [...oolkit/geoaccessor/geoseries/geodistance\_matrix.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2Vvc2VyaWVzL2dlb2Rpc3RhbmNlX21hdHJpeC5weQ==) | `100.00% <ø> (ø)` | |
| [dtoolkit/geoaccessor/geodataframe/\_\_init\_\_.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2VvZGF0YWZyYW1lL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [dtoolkit/geoaccessor/geodataframe/geodistance.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2VvZGF0YWZyYW1lL2dlb2Rpc3RhbmNlLnB5) | `100.00% <100.00%> (ø)` | |
| [dtoolkit/geoaccessor/geoseries/\_\_init\_\_.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2Vvc2VyaWVzL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | |
| [dtoolkit/geoaccessor/geoseries/geodistance.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2Vvc2VyaWVzL2dlb2Rpc3RhbmNlLnB5) | `100.00% <100.00%> (ø)` | |
| [dtoolkit/transformer/sklearn/OneHotEncoder.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/699/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvdHJhbnNmb3JtZXIvc2tsZWFybi9PbmVIb3RFbmNvZGVyLnB5) | `92.30% <0.00%> (-7.70%)` | :arrow_down: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25)


Resolving patch:
diff --git a/dtoolkit/geoaccessor/geoseries/geodistance.py b/dtoolkit/geoaccessor/geoseries/geodistance.py
--- a/dtoolkit/geoaccessor/geoseries/geodistance.py
+++ b/dtoolkit/geoaccessor/geoseries/geodistance.py
@@ -134,8 +134,9 @@ def geodistance(
         if align and not s.index.equals(other.index):
             warn("The indices are different.", stacklevel=find_stack_level())
             s, other = s.align(other)
-        else:
-            other = other.geometry
+
+        # Force convert to GeoSeries
+        other = other.geometry
 
     return pd.Series(
         distance(
diff --git a/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py b/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
--- a/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
+++ b/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
@@ -103,8 +103,13 @@ def geodistance_matrix(
 
     if s.crs != 4326:
         raise ValueError(f"Only support 'EPSG:4326' CRS, but got {s.crs!r}.")
-    if other is not None and other.crs != 4326:
-        raise ValueError(f"Only support 'EPSG:4326' CRS, but got {other.crs!r}.")
+
+    if isinstance(other, gpd.base.GeoPandasBase):
+        if other.crs != 4326:
+            raise ValueError(f"Only support 'EPSG:4326' CRS, but got {other.crs!r}.")
+
+        # Force convert to GeoSeries
+        other = other.geometry
 
     X = np.radians(np.stack((s.x, s.y), axis=1))
     Y = np.radians(np.stack((other.x, other.y), axis=1)) if other is not None else other


Test patch:
diff --git a/test/geoaccessor/geoseries/test_geodistance.py b/test/geoaccessor/geoseries/test_geodistance.py
--- a/test/geoaccessor/geoseries/test_geodistance.py
+++ b/test/geoaccessor/geoseries/test_geodistance.py
@@ -45,3 +45,13 @@ def test_warning():
 
     with pytest.warns(UserWarning):
         geodistance(s1, s2)
+
+
+def test_geodataframe():
+    s = gpd.GeoSeries([Point(122, 55), Point(100, 1)], crs=4326)
+    df = gpd.GeoSeries([Point(122, 55), Point(100, 1)], crs=4326).to_frame("geometry")
+
+    assert isinstance(df, gpd.GeoDataFrame)
+    result = s.geodistance(df)
+
+    assert all(result == 0)
diff --git a/test/geoaccessor/geoseries/test_geodistance_matrix.py b/test/geoaccessor/geoseries/test_geodistance_matrix.py
--- a/test/geoaccessor/geoseries/test_geodistance_matrix.py
+++ b/test/geoaccessor/geoseries/test_geodistance_matrix.py
@@ -1,5 +1,7 @@
 import geopandas as gpd
+import pandas as pd
 import pytest
+from pandas.testing import assert_frame_equal
 from shapely.geometry import Point
 
 from dtoolkit.geoaccessor.geoseries import geodistance_matrix
@@ -23,3 +25,14 @@
 def test_error(s, other, error):
     with pytest.raises(error):
         geodistance_matrix(s, other=other)
+
+
+def test_geodataframe():
+    s = gpd.GeoSeries([Point(122, 55), Point(100, 1)], crs=4326)
+    df = gpd.GeoSeries([Point(122, 55), Point(100, 1)], crs=4326).to_frame("geometry")
+
+    assert isinstance(df, gpd.GeoDataFrame)
+    result = s.geodistance_matrix(df) / 1e6
+    expected = pd.DataFrame([[0, 3.02679565], [3.02679565, 0]])
+
+    assert_frame_equal(result, expected)