Skip to content

Guard rasterize() against geometry/like CRS mismatch (#3058) - #3062

Merged
brendancol merged 3 commits into
mainfrom
issue-3058
Jun 9, 2026
Merged

Guard rasterize() against geometry/like CRS mismatch (#3058)#3062
brendancol merged 3 commits into
mainfrom
issue-3058

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Closes #3058

What

rasterize(geometries, like=template) no longer silently burns geometries onto a template in a different CRS.

  • _parse_input now returns the GeoDataFrame's .crs instead of dropping it.
  • A new _like_crs helper reads the template CRS (attrs['crs'], attrs['crs_wkt'], the spatial_ref coord, or rio.crs), matching polygonize._detect_raster_crs.
  • When both sides carry a CRS, rasterize normalizes them through pyproj and raises ValueError on mismatch. A new check_crs=True parameter (default) gates this; check_crs=False opts out.

Why

_parse_input discarded gdf.crs while _extract_grid_from_like propagated the template CRS onto the output. So an EPSG:4326 GeoDataFrame rasterized onto an EPSG:3857 template came back tagged EPSG:3857, with no reprojection and no warning. It looked authoritative but was wrong.

The geometries are never reprojected; reproject them to match the template, or pass check_crs=False to burn onto the template grid anyway.

Backend coverage

The guard runs before backend dispatch, so it behaves the same for numpy, cupy, dask+numpy, and dask+cupy. Tests cover the numpy path plus the dask and cupy paths.

Test plan

  • Matching CRS passes; mismatched CRS raises; check_crs=False bypasses
  • CRS read from attrs['crs'], attrs['crs_wkt'], and the spatial_ref coord
  • No-op when either side lacks a CRS, and for the (geometry, value) iterable path
  • int EPSG vs "EPSG:xxxx" string compare equal
  • Invalid CRS value raises instead of disabling the guard
  • Existing rasterize suite still green (215 passed)

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Jun 9, 2026

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Guard rasterize() against geometry/like CRS mismatch (#3058)

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

  • _like_crs wraps the rio.crs lookup in a bare except Exception (rasterize.py:3010-3015). That matches the broad catch in polygonize._detect_raster_crs, so it is consistent with the codebase, but it also swallows a real rioxarray error (say a corrupt spatial_ref) and falls through to None, which disables the guard for that template. Since the resolution order checks attrs['crs'], attrs['crs_wkt'], and the spatial_ref coord first, this only bites templates that rely solely on rio.crs, so it is minor. A short comment on the intent would help; matching the existing helper is defensible either way.

Nits (optional improvements)

  • The mismatch error embeds geom_crs!r, which for a geopandas frame is a multi-line pyproj CRS repr, so the message runs long. accessor._open_geotiff_windowed does the same thing, so it is consistent, but a to_epsg()-style summary would read better. Not blocking.

What looks good

  • The guard runs before backend dispatch and before any host or device allocation, so it behaves the same across numpy, cupy, dask+numpy, and dask+cupy with no per-backend code.
  • CRS resolution in _like_crs follows the same order as polygonize._detect_raster_crs, so a template round-trips the same CRS through rasterize and polygonize.
  • Both sides go through pyproj.CRS before .equals(), so int EPSG, "EPSG:xxxx" strings, and WKT all compare correctly. Same precedent as accessor.py.
  • The check_crs=False opt-out is documented and tested, and the no-op-when-either-side-is-None behavior keeps CRS-less inputs working unchanged.
  • Tests cover the attrs, crs_wkt, and spatial_ref resolution paths, the bypass, int-vs-string equivalence, invalid-CRS surfacing, and the dask and cupy backends.

Checklist

  • Algorithm matches reference/paper (N/A, validation guard)
  • All implemented backends produce consistent results (guard pre-dispatch)
  • NaN handling is correct (unchanged by this PR)
  • Edge cases are covered by tests
  • Dask chunk boundaries handled correctly (guard runs before chunking)
  • No premature materialization or unnecessary copies
  • Benchmark exists or is not needed (not needed, validation-only)
  • README feature matrix updated (N/A, no new function or backend change)
  • Docstrings present and accurate

@brendancol brendancol left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review (after fixes)

Both findings from the first pass are resolved.

  • Suggestion (bare except in _like_crs): added a comment at rasterize.py:3014 explaining the broad catch is deliberate. .rio raises AttributeError without rioxarray, and rio.crs can raise on a malformed georeference, so either way the template is treated as having no detectable CRS and the attrs/spatial_ref paths stay the source of truth. Behavior is unchanged on purpose; narrowing the type would break the no-rioxarray path.
  • Nit (long pyproj repr in the error): the mismatch message now labels each side with a compact EPSG:xxxx, falling back to the CRS name when no EPSG code exists. A new test asserts the message is single-line and contains both codes.

Tests: 17 in the new file, all green; the full rasterize suite is still 231 passed, 2 skipped. flake8 clean.

No new issues found.

@brendancol
brendancol merged commit 8e45dda into main Jun 9, 2026
6 of 7 checks passed
@brendancol
brendancol deleted the issue-3058 branch June 25, 2026 12:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rasterize() silently ignores CRS mismatch between geometries and like template

1 participant