Skip to content

Commit 33f3f4c

Browse files
thodson-usgsclaude
andauthored
fix(nldi): return an empty GeoDataFrame instead of crashing on empty results (#302)
NLDI can legitimately return no features (e.g. a feature with nothing upstream), and _query_nldi returns {} when a 200 response carries no JSON body (per its own comment). The getters then called gpd.GeoDataFrame.from_features(..., crs=_CRS), which raises "Assigning CRS to a GeoDataFrame without a geometry column is not supported" — an opaque crash on a valid empty result. (as_json=True already tolerated empties; the default GeoDataFrame path did not.) Added a shared _features_to_gdf helper, used by all four getters, that returns an empty GeoDataFrame with the correct CRS when there are no features. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ee653e5 commit 33f3f4c

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

dataretrieval/nldi.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@ def _query_nldi(url, query_params, error_message):
3232
return response_data
3333

3434

35+
def _features_to_gdf(feature_collection: dict) -> gpd.GeoDataFrame:
36+
"""Build a GeoDataFrame from an NLDI FeatureCollection, tolerating empties.
37+
38+
NLDI can legitimately return no features (e.g. a feature with nothing
39+
upstream), and :func:`_query_nldi` returns ``{}`` when a 200 response
40+
carries no JSON body. ``GeoDataFrame.from_features`` raises on those
41+
(there's no geometry column to attach the CRS to), so return an empty
42+
GeoDataFrame with the correct CRS instead of crashing.
43+
"""
44+
features = feature_collection.get("features") if feature_collection else None
45+
if not features:
46+
return gpd.GeoDataFrame(geometry=[], crs=_CRS)
47+
return gpd.GeoDataFrame.from_features(feature_collection, crs=_CRS)
48+
49+
3550
def get_flowlines(
3651
navigation_mode: str,
3752
distance: int = 5,
@@ -104,7 +119,7 @@ def get_flowlines(
104119
feature_collection = _query_nldi(url, query_params, err_msg)
105120
if as_json:
106121
return feature_collection
107-
gdf = gpd.GeoDataFrame.from_features(feature_collection, crs=_CRS)
122+
gdf = _features_to_gdf(feature_collection)
108123
return gdf
109124

110125

@@ -157,7 +172,7 @@ def get_basin(
157172
feature_collection = _query_nldi(url, query_params, err_msg)
158173
if as_json:
159174
return feature_collection
160-
gdf = gpd.GeoDataFrame.from_features(feature_collection, crs=_CRS)
175+
gdf = _features_to_gdf(feature_collection)
161176
return gdf
162177

163178

@@ -273,7 +288,7 @@ def get_features(
273288
feature_collection = _query_nldi(url, query_params, err_msg)
274289
if as_json:
275290
return feature_collection
276-
gdf = gpd.GeoDataFrame.from_features(feature_collection, crs=_CRS)
291+
gdf = _features_to_gdf(feature_collection)
277292
return gdf
278293

279294

@@ -307,7 +322,7 @@ def get_features_by_data_source(data_source: str) -> gpd.GeoDataFrame:
307322
url = f"{NLDI_API_BASE_URL}/{data_source}"
308323
err_msg = f"Error getting features for data source '{data_source}'"
309324
feature_collection = _query_nldi(url, {}, err_msg)
310-
gdf = gpd.GeoDataFrame.from_features(feature_collection, crs=_CRS)
325+
gdf = _features_to_gdf(feature_collection)
311326
return gdf
312327

313328

0 commit comments

Comments
 (0)