Skip to content

fix(ogc): keep the frame type when a result is empty - #373

Merged
thodson-usgs merged 1 commit into
DOI-USGS:mainfrom
thodson-usgs:fix/empty-result-frame-type
Aug 14, 2026
Merged

fix(ogc): keep the frame type when a result is empty#373
thodson-usgs merged 1 commit into
DOI-USGS:mainfrom
thodson-usgs:fix/empty-result-frame-type

Conversation

@thodson-usgs

Copy link
Copy Markdown
Collaborator

The bug

Every OGC getter returns a GeoDataFrame when its filter matches at least one row, and a plain DataFrame when it matches none.

locs = waterdata.get_monitoring_locations(state_name="Wisconsin")   # GeoDataFrame
locs = waterdata.get_monitoring_locations(state_name="Wisconsin",
                                          site_type_code="ZZ")      # DataFrame (!)

So .geometry, .to_crs(), .plot(), and pd.concat across a loop of per-site calls break only on the empty result — the case least likely to be covered by a caller's tests. It is also invisible from df.columns, because the geometry column is still listed.

Why

ogc/shaping.py::_empty_feature_frame exists precisely to prevent this; its docstring calls itself "the single home for this empty-page contract", because a later pd.concat([empty_page, geo_page]) would otherwise downgrade the result and strip geometry/CRS.

But _deal_with_empty runs last, on every getter, 160 lines away in the same file — and built a fresh pd.DataFrame(columns=properties), discarding the guarantee it had just been handed.

The fix

Reindex the frame we were given instead of constructing a new one. That applies the schema's columns while keeping the concrete class — and the geometry dtype with it:

GeoDataFrame().reindex(columns=["monitoring_location_id", "geometry"])
# -> GeoDataFrame, geometry dtype preserved, empty

The plain-pandas path is unchanged: pd.DataFrame().reindex(columns=[...]) is still a pd.DataFrame.

Testing

  • New regression test uses a DataFrame subclass rather than real geopandas, so the invariant is checked even where geopandas is absent (it is not in the [test] extra, only [nldi] and [doc]). Verified to fail against the old line and pass against the new one.
  • 788 passed; mypy --strict, ruff, xenon, complexipy, lint-imports all pass.

Found by the scan in #372, split out because it changes user-visible behavior. Independent of #372 — branches off main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BTaSm7HmVb94RSJiKW4WAS

@thodson-usgs
thodson-usgs force-pushed the fix/empty-result-frame-type branch from d6ea9e1 to 335dedb Compare August 14, 2026 16:00
@thodson-usgs
thodson-usgs marked this pull request as ready for review August 14, 2026 19:23
@thodson-usgs
thodson-usgs merged commit c324278 into DOI-USGS:main Aug 14, 2026
11 checks passed
@thodson-usgs
thodson-usgs deleted the fix/empty-result-frame-type branch August 14, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant