Skip to content

Honor requested resolution exactly in from_template() (#3494)#3495

Merged
brendancol merged 3 commits into
mainfrom
issue-3494
Jun 25, 2026
Merged

Honor requested resolution exactly in from_template() (#3494)#3495
brendancol merged 3 commits into
mainfrom
issue-3494

Conversation

@brendancol

Copy link
Copy Markdown
Contributor

Summary

from_template(name, resolution=r) now honors the requested resolution exactly. The previous code held the template's bbox fixed and recomputed the realized cell size from it, so res drifted when the extent was not a whole number of cells (e.g. from_template('nyc', resolution=10) returned res=(10.0, 10.000757)).

  • Anchor the lower-left corner and nudge the far edges (right, top) to an exact multiple of the cell size, so attrs['res'] matches the requested resolution.
  • This matches the reproject grid path (_compute_output_grid in reproject/_grid.py).
  • Pixel centers still fall inside the original registry bbox; the far edges move by less than half a cell.

Closes #3494

Backends

Backend-agnostic: the change is in the shared grid math before the array is allocated, so numpy, cupy, dask+numpy, and dask+cupy all get the same exact res.

Test plan

  • from_template('nyc', resolution=10).attrs['res'] == (10.0, 10.0)
  • tuple resolution honored exactly
  • coord spacing equals the requested resolution
  • pixel centers stay within the registry bbox
  • preserve path honors resolution exactly
  • full test_templates.py suite passes (44 tests)

Nudge the far edges of the study-area box to an exact multiple of the
cell size (anchor lower-left) so attrs['res'] matches the requested
resolution instead of drifting when the bbox isn't a whole number of
cells. Mirrors the reproject grid path in _compute_output_grid.

@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: Honor requested resolution exactly in from_template() (#3494)

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

  • The new exact-resolution tests only cover projected-CRS regions (nyc, conus) and the preserve path. Country codes come back in EPSG:4326 and run through the same nudge math, but nothing pins exact res for a 4326 template. A one-liner like from_template('FRA', resolution=0.25).attrs['res'] == (0.25, 0.25) would close that gap. (xrspatial/tests/test_templates.py)

Nits (optional improvements)

  • The dask backend's exact res is covered indirectly by test_dask_numpy_backend (it asserts attrs == numpy ref). An explicit assertion would document the intent, but it's optional. (xrspatial/tests/test_templates.py:139)

What looks good

  • The fix mirrors the existing reproject grid path (_compute_output_grid in reproject/_grid.py lines 457-459), so the two paths now agree on how resolution is honored.
  • The change sits in the shared grid math before _make_data, so all four backends get identical coords and res with no per-backend code.
  • Pixel centers stay inside the original registry bbox: the center sits res/2 inward, which absorbs the half-cell nudge. Checked both axes.
  • I checked the EPSG:4326 country path for latitude/longitude overflow from the nudge. Latitude never gets near 90 (the northernmost bbox top is ~83.6 for GRL), and the lon>180 values come from pre-existing antimeridian-wrapped bboxes, not from the nudge. No new invalid-coordinate risk.
  • Docstring updated to describe the nudge and the lower-left anchor; autosummary picks it up.

Checklist

  • Algorithm matches the reproject grid convention
  • All four backends produce consistent results (shared pre-allocation math)
  • NaN handling unchanged (fill path untouched)
  • Edge cases covered (tuple res, centers within bbox)
  • No dask chunk concern (change is before array allocation)
  • No premature materialization
  • No benchmark needed (constant-time arithmetic)
  • No README change needed (no new function, no backend change)
  • Docstring 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 0dc8a4e)

Both findings from the first pass are addressed:

  • Suggestion (4326 exact-res test): fixed. Added test_country_resolution_honored_exactly, asserting from_template('FRA', resolution=0.25).attrs['res'] == (0.25, 0.25).
  • Nit (explicit dask exact-res): fixed. test_dask_numpy_backend now asserts the dask+numpy path returns res == (10.0, 10.0).

Full test_templates.py suite passes (45 tests). No new findings.

@brendancol brendancol merged commit c0a156a into main Jun 25, 2026
9 of 10 checks passed
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.

from_template() should honor the requested resolution exactly

1 participant