Repository: shapely/shapely

Issue:
Default kwarg `offsets` in `from_ragged_array` cannot be unpacked
## Expected behavior and actual behavior.

Expected behavior: call to `from_ragged_array` with default keyword arguments should not fail.

Actual behavior: call to `from_ragged_array` without specifying `offsets` results in unpacking a NoneType error.

https://github.com/shapely/shapely/blob/bc62f989c8bb2de38f9762a301b7b02c416d38b9/shapely/_ragged_array.py#L401

https://github.com/shapely/shapely/blob/bc62f989c8bb2de38f9762a301b7b02c416d38b9/shapely/_ragged_array.py#L442

## Steps to reproduce the problem.

`shapely.from_ragged_array(shapely.GeometryType(1), np.array([[0, 0], [0, 1]]))`

## Shapely version and provenance

shapely 2.0.1



Discussion:
Maybe the API could be better, but the key here is that `offsets` has to be provided for any geometry type other than `Point`, as only points always have the same distance (i.e. two numbers) between each geometry.

You can see this if you use `to_ragged_array` first:
```py
In [4]: geom = shapely.LineString([[0, 0], [0, 1]])

In [6]: shapely.to_ragged_array([geom])
Out[6]:
(<GeometryType.LINESTRING: 1>,
 array([[0., 0.],
        [0., 1.]]),
 (array([0, 2]),))
```
In effect you need to pass in an array of `[0, 2]` into `offsets` to tell the conversion that your linestring at index 0 has a length of 2.
Right, from my perspective as a user the API needs to be cleaned up.
Do you have something specific you would like to change about the API? Do you want to get a better error message? (that would already help, I think, and should be easy to add) 
Or is just the fact that the signature seems to indicate it is an optional arguments while that is not always the case? (i.e. remove the default of `None`)

For `to_ragged_array`, we actually do return an empty tuple for the offsets in case of Points:

```
In [2]: geoms = shapely.points([[1, 2], [3, 4]])

In [3]: shapely.to_ragged_array(geoms)
Out[3]: 
(<GeometryType.POINT: 0>,
 array([[1., 2.],
        [3., 4.]]),
 ())
```

So that means that even if we remove the `=None` and make the offsets argument required, a roundtrip with to_ragged_array / from_ragged_array still works out of the box:

```
In [4]: shapely.from_ragged_array(*shapely.to_ragged_array(geoms))
Out[4]: array([<POINT (1 2)>, <POINT (3 4)>], dtype=object)
```

But, it would make calling `from_ragged_array` with custom data for just points a bit harder, as you can no longer do:

```
shapely.from_ragged_array(shapely.GeometryType.POINT, np.random.randn(10, 2))
```

but would have to do 

```
shapely.from_ragged_array(shapely.GeometryType.POINT, np.random.randn(10, 2), ())
# or
shapely.from_ragged_array(shapely.GeometryType.POINT, np.random.randn(10, 2), None)
```

instead (which is a bit more verbose / ugly). 
Now, maybe this is not the most important use case of `from_ragged_array`, as in this case of creating points without offsets variable, you can also just use `shapely.points(..)`



> Or is just the fact that the signature seems to indicate it is an optional arguments while that is not always the case?

This is what got me.

My workflow was to use `fiona` to read in a KML and then use `shapely` to instantiate a geometry using `from_ragged_array` and the record object. I wanted to do this without regard to the geometry in the KML, so I was going to hard-code a lookup dictionary with geometry type strings as the keys and integers for `GeometryType()` as values. I suppose I could've used the geometry creation functions as the values instead.

The KML happened to have only 1 LineString, hence the unpacking failure. I did this unnecessarily complicated flow because I got confused by the doc theme switch---in shapely 1.8 I used `shapely.geometry.shape()`, which is still around 😅 

Regardless, imo kwargs should be optional.

I'd be happy to contribute a PR once y'all think about the best solution.
> Regardless, imo kwargs should be optional.

It's still optional in _some_ cases .. So I think I would prefer to keep it optional for the Point case. But then we can certainly improve the error message. After the ``if geometry_type == GeometryType.POINT:`` block, we can add a `if offsets is None: raise TypeError("'offsets' should be specified ...")`
Opened a small PR to improve the error message -> https://github.com/shapely/shapely/pull/2224



Patch:
diff --git a/shapely/_ragged_array.py b/shapely/_ragged_array.py
index a1af4a03d..ca63d5153 100644
--- a/shapely/_ragged_array.py
+++ b/shapely/_ragged_array.py
@@ -422,11 +422,18 @@ def from_ragged_array(geometry_type, coords, offsets=None):
     coords = np.asarray(coords, dtype="float64")
 
     if geometry_type == GeometryType.POINT:
-        assert offsets is None or len(offsets) == 0
+        if not (offsets is None or len(offsets) == 0):
+            raise ValueError("'offsets' should not be provided for geometry type Point")
         return _point_from_flatcoords(coords)
+
+    if offsets is None:
+        raise ValueError(
+            "'offsets' must be provided for any geometry type except for Point"
+        )
+
     if geometry_type == GeometryType.LINESTRING:
         return _linestring_from_flatcoords(coords, *offsets)
-    if geometry_type == GeometryType.POLYGON:
+    elif geometry_type == GeometryType.POLYGON:
         return _polygon_from_flatcoords(coords, *offsets)
     elif geometry_type == GeometryType.MULTIPOINT:
         return _multipoint_from_flatcoords(coords, *offsets)


Test patch:
diff --git a/shapely/tests/test_ragged_array.py b/shapely/tests/test_ragged_array.py
index c11e6c86f..850715259 100644
--- a/shapely/tests/test_ragged_array.py
+++ b/shapely/tests/test_ragged_array.py
@@ -443,3 +443,17 @@ def test_from_ragged_incorrect_rings_unclosed():
         shapely.from_ragged_array(
             shapely.GeometryType.POLYGON, coords, (offsets1, offsets2)
         )
+
+
+def test_from_ragged_wrong_offsets():
+    with pytest.raises(ValueError, match="'offsets' must be provided"):
+        shapely.from_ragged_array(
+            shapely.GeometryType.LINESTRING, np.array([[0, 0], [0, 1]])
+        )
+
+    with pytest.raises(ValueError, match="'offsets' should not be provided"):
+        shapely.from_ragged_array(
+            shapely.GeometryType.POINT,
+            np.array([[0, 0], [0, 1]]),
+            offsets=(np.array([0, 1]),),
+        )