Skip to content

fix: clamp Web Mercator mesh for south-up affines#590

Merged
kylebarron merged 1 commit into
mainfrom
fix/web-mercator-clamp-south-up-affine
Jun 4, 2026
Merged

fix: clamp Web Mercator mesh for south-up affines#590
kylebarron merged 1 commit into
mainfrom
fix/web-mercator-clamp-south-up-affine

Conversation

@kylebarron

@kylebarron kylebarron commented Jun 4, 2026

Copy link
Copy Markdown
Member

This is a follow up to #574. I was working with @gadomski on a Zarr visualization with bounds [-180, -90, 180, 90].

I expected it to be solved by #574, but it turned out it wasn't touched by that because that PR expected an origin in the top-left (i.e. with a negative e in the affine), whereas this dataset has an origin in the bottom-left.

Before:

image

After:

image

Problem

createInitialWebMercatorTriangulation (added in #574 to fix degenerate near-pole triangles for global EPSG:4326 imagery, issues #182 / #351) assumes the tile is north-up — it takes north = topLeft; south = bottomLeft and bails via the north - south <= LAT_EPSILON guard.

Grids with a positive-e affine are stored south-up — common for GRIB/IFS-derived reanalysis data. For these, projectedTileCorners puts the southern edge at topLeft, so north - south is negative on every tile, the guard trips, and the clamp is silently skipped. The pole then gets meshed: makeClampedForwardTo3857 collapses the polar vertices onto a single Y, producing degenerate triangles that never converge:

RasterReprojector: mesh refinement did not converge after 10001 iterations
  (maxError=0.125, currentError=19.79...)

…along with visible pole misalignment (high-latitude pixels drift poleward of the basemap).

Fix

Derive the band orientation-agnostically. Latitude varies linearly along the seed's v axis as lat(v) = top + v * (bottom - top), so intersect that segment with [-maxLat, maxLat] by solving for v at each limit and taking the overlapping interval via min/max. This keeps v anchored to the texture's top row (topLeft), so the band is correct whether latitude increases or decreases with v.

North-up behavior is unchanged (verified — all pre-existing tests pass without modification).

Notes

  • The previous "south-up tile" path returned undefined (full mesh) by design; that's exactly the case that needed handling, not skipping.
  • A naive "normalize corners to north-up, keep the formula" approach does not work: the seed's v-axis is pinned to the texture's top row, not to north, so swapping corner values mirrors the band vertically unless the output is also flipped (v → 1-v). Deriving the band directly avoids that footgun.

Tests

Adds two regression tests:

  • a global south-up tile (topLeft = -90, bottomLeft = 90) → clamped band, previously undefined;
  • a south-up tile clamped on only the south edge (topLeft = -90, bottomLeft = 80) — asymmetric, so it would catch a mirrored band.

vitest run on the suite: 7/7 pass. tsc --noEmit clean.

🤖 Generated with Claude Code

createInitialWebMercatorTriangulation assumed `topLeft` is the northern
corner (`north = topLeft; south = bottomLeft`), then bailed via the
`north - south <= LAT_EPSILON` guard. Grids with a positive-`e` affine —
where row 0 is the south pole, common for GRIB/IFS-derived reanalysis
data — are stored south-up, so `topLeft` is the *southern* edge. The
guard tripped on every tile, the clamp was silently skipped, and the
pole was meshed: degenerate near-pole triangles that never converge
("mesh refinement did not converge") plus pole misalignment.

Derive the band orientation-agnostically: latitude varies linearly along
v as `lat(v) = top + v * (bottom - top)`, so intersect that segment with
[-maxLat, maxLat] by solving for v at each limit and taking the
overlapping interval (min/max). This keeps `v` anchored to the texture's
top row, so the band is correct whether latitude increases or decreases
with v. North-up behavior is unchanged.

Extends #574 (issues #182 / #351). Adds regression tests for a global
south-up tile and a south-up tile clamped on only the south edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label Jun 4, 2026
@kylebarron kylebarron changed the title fix(deck.gl-raster): clamp Web Mercator mesh for south-up affines fix: clamp Web Mercator mesh for south-up affines Jun 4, 2026
@kylebarron kylebarron merged commit 0e640be into main Jun 4, 2026
5 checks passed
@kylebarron kylebarron deleted the fix/web-mercator-clamp-south-up-affine branch June 4, 2026 21:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant