Skip to content

Fix geodesic coord resolution to prefer real lat/lon over numeric y/x#2903

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

Fix geodesic coord resolution to prefer real lat/lon over numeric y/x#2903
brendancol merged 3 commits into
mainfrom
issue-2896

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Closes #2896

Problem

_find_coord() in xrspatial/utils.py accepted the numeric dimension coordinate before it scanned for a coordinate named latitude/longitude. For a curvilinear raster with dims ('y', 'x'), numeric y/x pixel-index coords, and real 2-D lat/lon coords, the geodesic path used the pixel indices as lat/lon. The range check did not catch it because small indices (0..N) fall inside the accepted geographic ranges. The result was a wrong slope (~0.007 vs ~0.067 on the reproduction grid) with no warning.

Change

  • _find_coord() now prefers an explicitly named lat/lon coord (lat/latitude/lon/longitude) over the dimension coord. The dimension coord and the broader y/x name set remain as fallbacks, so rasters with y/x as the geographic coords still work.
  • This fixes slope, aspect, and surface_distance, which share _extract_latlon_coords / _find_coord. The logic is coord resolution only, so it is backend-agnostic.

Backend coverage

numpy, cupy, dask+numpy, dask+cupy. Coord resolution runs on the host before dispatch; the new curvilinear tests assert parity across all four (cupy / dask+cupy skip without a GPU).

Test plan

  • Repro from the issue now matches the 1-D lat/lon reference exactly
  • New TestGeodesicSlopeCurvilinear* classes: dims y/x + 2-D lat/lon + numeric y/x, asserting correct slope and cross-backend parity
  • Existing geodesic slope/aspect and surface_distance suites still green (including test_projected_coords_raises, which relies on the y/x fallback)

…#2896)

_find_coord accepted the numeric dimension coord before scanning for a
coord named latitude/longitude, so a curvilinear raster with numeric y/x
index coords plus real 2-D lat/lon coords used the pixel indices as
lat/lon. The range check did not catch it because small indices fall
inside the geographic ranges.

Prefer an explicitly named lat/lon coord over the dimension coord. This
covers slope, aspect, and surface_distance, which share _extract_latlon_coords.

Add curvilinear coverage (dims y/x + 2-D lat/lon + numeric y/x) across the
four backends.
@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Jun 3, 2026
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

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

PR Review: Fix geodesic coord resolution to prefer real lat/lon over numeric y/x

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

None.

Nits (optional improvements)

  • xrspatial/utils.py:967-972: The explicit-name scan picks the first matching coord in agg.coords iteration order. If a DataArray carries both a lat and a latitude coord (or lon/longitude) with different dimensionality, the winner depends on coord insertion order, and a 1-D winner on one axis paired with a 2-D winner on the other would trip the "both 1-D or both 2-D" error in _extract_latlon_coords. This is an unusual input and the resulting error is clear, so it does not need handling now, but a one-line docstring note that duplicate explicit names resolve by coord order would help a future reader.

What looks good

  • Root cause fixed at the right layer: reordering _find_coord precedence fixes slope, aspect, and surface_distance at once, since they all route through _extract_latlon_coords.
  • The y/x fallback is preserved, so rasters that legitimately use y/x as geographic coords still resolve (confirmed by the still-passing test_projected_coords_raises).
  • Tests assert against the 1-D lat/lon reference rather than just "runs without error", and the > 0.05 interior check would catch a regression back to the pixel-index behavior (~0.007).
  • Backend parity covered across numpy, cupy, dask+numpy, dask+cupy via the general_checks markers.

Checklist

  • Algorithm matches reference: n/a (coord resolution, not a numeric kernel)
  • All implemented backends produce consistent results: yes, parity tests added
  • NaN handling correct: unchanged by this PR
  • Edge cases covered by tests: curvilinear path covered; duplicate-explicit-name case noted as a nit
  • Dask chunk boundaries handled correctly: unchanged; coord resolution is host-side
  • No premature materialization: coord .values read is host-side and pre-existing
  • Benchmark exists or not needed: not needed (no perf-sensitive path changed)
  • README feature matrix updated: not applicable (bug fix, no new API)
  • Docstrings present and accurate: yes, _find_coord docstring updated

Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

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

PR Review (follow-up pass)

Re-reviewed after commit 40a0065.

Blockers

None.

Suggestions

None.

Nits

None outstanding. The earlier nit on _find_coord (utils.py) is resolved: the docstring now states that when several explicit lat/lon names are present, the first one in coord order wins.

What looks good

  • The fix and tests are unchanged from the first pass and still green: 103 passed across geodesic slope, geodesic aspect, and surface_distance suites locally (cupy / dask+cupy classes skip without a GPU).
  • The diff stays scoped to coord resolution plus its tests. No unrelated edits.

Nothing further to address. Recommending merge once CI is green.

@brendancol brendancol merged commit f6055fa into main Jun 3, 2026
7 checks passed
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.

Geodesic slope/aspect silently use numeric y/x index coords instead of real 2-D lat/lon

1 participant