Repository: Zeroto521/my-data-toolkit

Issue:
ENH: New geoaccessor to generate great circle distances matrix
<!--
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
-->

- [x] closes #699
- [ ] whatsnew entry



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

```diff
@@            Coverage Diff             @@
##             main     #701      +/-   ##
==========================================
- Coverage   99.55%   99.41%   -0.14%     
==========================================
  Files         114      116       +2     
  Lines        1334     1361      +27     
  Branches      121      123       +2     
==========================================
+ Hits         1328     1353      +25     
- Misses          1        2       +1     
- Partials        5        6       +1     
```


| [Impacted Files](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25) | Coverage Δ | |
|---|---|---|
| [dtoolkit/geoaccessor/geodataframe/\_\_init\_\_.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701/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%> (ø)` | |
| [...kit/geoaccessor/geodataframe/geodistance\_matrix.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=40%25#diff-ZHRvb2xraXQvZ2VvYWNjZXNzb3IvZ2VvZGF0YWZyYW1lL2dlb2Rpc3RhbmNlX21hdHJpeC5weQ==) | `100.00% <100.00%> (ø)` | |
| [dtoolkit/geoaccessor/geoseries/\_\_init\_\_.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701/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%> (ø)` | |
| [...oolkit/geoaccessor/geoseries/geodistance\_matrix.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701/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% <100.00%> (ø)` | |
| [dtoolkit/transformer/sklearn/OneHotEncoder.py](https://codecov.io/gh/Zeroto521/my-data-toolkit/pull/701/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_matrix.py b/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
--- a/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
+++ b/dtoolkit/geoaccessor/geoseries/geodistance_matrix.py
@@ -53,6 +53,9 @@ def geodistance_matrix(
     ValueError
         If the CRS is not ``ESGP:4326``.
 
+    TypeError
+        If the other is not a GeoSeries, GeoDataFrame, or None type.
+
     See Also
     --------
     sklearn.metrics.pairwise.haversine_distances
@@ -72,6 +75,7 @@ def geodistance_matrix(
     Examples
     --------
     >>> import dtoolkit.geoaccessor
+    >>> import pandas as pd
     >>> df = pd.DataFrame(
     ...     {
     ...         "x": [120, 122, 100],
@@ -104,17 +108,19 @@ def geodistance_matrix(
     if s.crs != 4326:
         raise ValueError(f"Only support 'EPSG:4326' CRS, but got {s.crs!r}.")
 
-    if isinstance(other, gpd.base.GeoPandasBase):
+    if other is None:
+        Y = None
+    elif 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
+        Y = np.radians(np.stack((other.geometry.y, other.geometry.x), axis=1))
+    else:
+        raise TypeError(f"Unknown type: {type(other).__name__!r}.")
 
     X = np.radians(np.stack((s.y, s.x), axis=1))
-    Y = np.radians(np.stack((other.y, other.x), axis=1)) if other is not None else other
     return pd.DataFrame(
         radius * haversine_distances(X, Y),
         index=s.index,
-        columns=other.index,
+        columns=other.index if other is not None else s.index,
     )


Test patch:
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
@@ -20,6 +20,16 @@
             gpd.GeoSeries([Point(120, 30), Point(122, 55), Point(100, 1)]),
             ValueError,
         ),
+        (
+            gpd.GeoSeries([Point(120, 30), Point(110, 40)], crs=4326),
+            Point(1, 1),
+            TypeError,
+        ),
+        (
+            gpd.GeoSeries([Point(120, 30), Point(110, 40)], crs=4326),
+            "string",
+            TypeError,
+        ),
     ],
 )
 def test_error(s, other, error):
@@ -36,3 +46,12 @@ def test_geodataframe():
     expected = pd.DataFrame([[0, 6.3275778074520295], [6.3275778074520295, 0]])
 
     assert_frame_equal(result, expected)
+
+
+def test_none():
+    s = gpd.GeoSeries([Point(122, 55), Point(100, 1)], crs=4326)
+
+    result = s.geodistance_matrix()
+    expected = pd.DataFrame([[0, 6327577.80745203], [6327577.80745203, 0]])
+
+    assert_frame_equal(result, expected)