Repository: shapely/shapely

Issue:
geometry.mapping doesn't work for empty Point object
## Expected behavior and actual behavior.
Expected behavior:
```bash
>>> from shapely import geometry
>>> geometry.mapping(geometry.Point())
{'type': 'Point', 'coordinates': ()}
```
Actual behavior:
```bash
>>> from shapely import geometry
>>> geometry.mapping(geometry.Point())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "~/.pyenv/versions/3.8.13/lib/python3.8/site-packages/shapely/geometry/geo.py", line 136, in mapping
    return ob.__geo_interface__
  File "~/.pyenv/versions/3.8.13/lib/python3.8/site-packages/shapely/geometry/point.py", line 105, in __geo_interface__
    return {"type": "Point", "coordinates": self.coords[0]}
  File "~/.pyenv/versions/3.8.13/lib/python3.8/site-packages/shapely/coords.py", line 34, in __getitem__
    raise IndexError("index out of range")
IndexError: index out of range
```
We can use `geometry.mapping` function for empty features other than Point, i.e. `geometry.mapping(geometry.Polygon())`, `geometry.mapping(geometry.LineString())`. Point feature also should behave in the same way.

## Steps to reproduce the problem.
```python
from shapely import geometry
print(geometry.mapping(geometry.Polygon())) # success
print(geometry.mapping(geometry.LineString())) # success
print(geometry.mapping(geometry.MultiPoint())) # success
print(geometry.mapping(geometry.Point())) # fail
```

## Operating system

Ubuntu 20.04.6 LTS (Focal Fossa)

## Shapely version and provenance

2.0.1 installed from PyPI using pip
python 3.8.13



Discussion:
There doesn't seem to be a GeoJSON equivalent for `POINT EMPTY` (e.g. [here](https://lists.geojson.org/pipermail/geojson-geojson.org/2011-June/thread.html)). Two alternatives of expected behavior:

    {'type': 'Point', 'coordinates': None}
    {'type': 'Point', 'coordinates': (nan, nan)}

I don't have a strong opinion on which representation is "more correct".
Thank you @mwtoews for the reply.
- Other geometry type like Polygon, LineString behaves like below.
  ```python
  >>> geometry.mapping(geometry.Polygon())
  {'type': 'Polygon', 'coordinates': ()}
  >>> geometry.mapping(geometry.LineString())
  {'type': 'LineString', 'coordinates': ()}
  ```
- All the representations including your suggestions work for `geometry.shape` method.
  ```python
  >>> geometry.shape({'type': 'Point', 'coordinates': ()})
  <shapely.geometry.point.Point object at 0x7fd0d1becf70>
  >>> geometry.shape({'type': 'Point', 'coordinates': None})
  <shapely.geometry.point.Point object at 0x7fd0d1becf70>
  >>> geometry.shape({'type': 'Point', 'coordinates': (None, None)})
  <shapely.geometry.point.Point object at 0x7fd0d1becf70>
  ```
- RFC 7946 says that `GeoJSON processors MAY interpret Geometry objects with empty "coordinates" arrays as null objects.` ([here](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.2:~:text=GeoJSON%20processors%20MAY%20interpret%20Geometry%20objects%20with%0A%20%20%20%20%20%20empty%20%22coordinates%22%20arrays%20as%20null%20objects.))

Considering all the above points, I think `geometry.shape({'type': 'Point', 'coordinates': None})` is the best representation to follow the RFC, but in order to do that, we need to modify the representation of all the geometry types.
GEOS decided against `None`, and choose the empty array.
```py
>>> shapely.from_geojson(json.dumps({'type': 'Point', 'coordinates':[] }))
<POINT EMPTY>

>>> shapely.from_geojson(json.dumps({'type': 'Point', 'coordinates': None }))
[...]
shapely.errors.GEOSException: ParseException: Error parsing JSON: '[json.exception.type_error.302] type must be array, but is null'
```

For the sake of consistency and to avoid round-trip errors, the better bet is probably to follow that rule.



Patch:
diff --git a/CHANGES.txt b/CHANGES.txt
index efe04c899..f6218524f 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -8,6 +8,7 @@ Bug fixes:
 
 - Prevent crash when serializing a number > 1e100 to WKT with GEOS < 3.13. (#1907)
 - Fixes GeoJSON serialization of empty points (#2118)
+- Fixes `__geo_interface__` handling of empty points (#2120)
 
 Improvements:
 
diff --git a/shapely/geometry/point.py b/shapely/geometry/point.py
index ccdd5c642..1816b67e3 100644
--- a/shapely/geometry/point.py
+++ b/shapely/geometry/point.py
@@ -116,7 +116,8 @@ def m(self):
     @property
     def __geo_interface__(self):
         """Return a GeoJSON-like mapping of the Point geometry."""
-        return {"type": "Point", "coordinates": self.coords[0]}
+        coords = self.coords
+        return {"type": "Point", "coordinates": coords[0] if len(coords) > 0 else ()}
 
     def svg(self, scale_factor=1.0, fill_color=None, opacity=None):
         """Return SVG circle element for the Point geometry.


Test patch:
diff --git a/shapely/tests/geometry/test_point.py b/shapely/tests/geometry/test_point.py
index b8af1407f..9995c89ad 100644
--- a/shapely/tests/geometry/test_point.py
+++ b/shapely/tests/geometry/test_point.py
@@ -151,6 +151,8 @@ def test_point_empty(self):
         assert p_null.coords[:] == []
         assert p_null.area == 0.0
 
+        assert p_null.__geo_interface__ == {"type": "Point", "coordinates": ()}
+
     def test_coords(self):
         # From Array.txt
         p = Point(0.0, 0.0, 1.0)