contours: keep CRS on empty geopandas result (#2700)#2708
Open
brendancol wants to merge 4 commits into
Open
Conversation
_to_geopandas() crashed with 'Assigning CRS to a GeoDataFrame without a geometry column is not supported' when the result set was empty and the input had a crs attr. Build an empty frame with an explicit geometry column so the CRS still attaches. Also propagate the input CRS on the all-NaN early-return path instead of dropping it.
brendancol
commented
May 29, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
PR Review: contours: keep CRS on empty geopandas result (#2700)
Blockers (must fix before merge)
None.
Suggestions (should fix, not blocking)
None.
Nits (optional improvements)
- xrspatial/contour.py:551 -
crsis passed twice in the empty branch: once togpd.GeoSeries([], crs=crs)and once to the outerGeoDataFrame(..., crs=crs). The outer one sets the frame CRS, so the inner one is redundant. It is harmless and guarantees the geometry column itself carries the CRS, so dropping it is optional. - The empty-frame
levelcolumn comes from[], so its dtype is object/float rather than matching the populated path. This only matters if a caller concatenates an empty result with a populated one and checks dtypes. Low impact.
What looks good
- The fix targets the exact failure: an empty records list plus a non-None CRS. Building the frame with an explicit geometry column is the right way to let geopandas attach the CRS.
- The all-NaN early-return now forwards the input CRS instead of None, which closes the silent-drop path.
- Four new tests cover the populated, empty-with-CRS, all-NaN, and empty-without-CRS cases. The empty-with-CRS test names the regression (#2700).
- The change is surgical, with no unrelated refactoring.
Checklist
- Algorithm matches reference: n/a (CRS propagation fix)
- All implemented backends consistent: yes, conversion is host-side after tracing
- NaN handling correct: yes, all-NaN path verified by test
- Edge cases covered by tests: yes
- Dask chunk boundaries: n/a
- No premature materialization: yes
- Benchmark: not needed
- README feature matrix: not needed
- Docstrings present and accurate: yes, no API change
brendancol
commented
May 29, 2026
Contributor
Author
brendancol
left a comment
There was a problem hiding this comment.
Follow-up review (after 5d46d52)
Addressed the first nit: dropped the redundant crs=crs on the inner gpd.GeoSeries([]). The outer GeoDataFrame(..., crs=crs) still sets the frame CRS, and test_geopandas_empty_result_keeps_crs confirms the empty frame carries it. All 28 contour tests pass.
The second nit (empty-frame level column dtype) is left as is. Forcing a dtype on a zero-row column adds noise for an edge case that only matters under cross-frame concatenation with dtype checks, which the library does not do here. Recording it rather than fixing.
No remaining blockers or suggestions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #2700
contours(agg, return_type="geopandas")crashed when the input raster had acrsattribute but the requested levels produced no contour lines (flat raster, levels outside the data range, etc.)._to_geopandas()calledgpd.GeoDataFrame(records, crs=crs)with an emptyrecordslist, and geopandas rejects a CRS on a frame that has no geometry column.Changes:
_to_geopandas()builds an empty frame with an explicitgeometrycolumn when there are no records, so the input CRS still attaches and the result is a well-formed (empty) GeoDataFrame.None, so it no longer silently drops georeferencing.Backend coverage: the geopandas conversion runs on the host after tracing, so this path is identical for numpy, cupy, dask+numpy, and dask+cupy. No backend-specific code changed.
Test plan:
pytest xrspatial/tests/test_contour.py-> 28 passed