fix: clamp Web Mercator mesh for south-up affines#590
Merged
Conversation
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>
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.
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
ein the affine), whereas this dataset has an origin in the bottom-left.Before:
After:
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 takesnorth = topLeft; south = bottomLeftand bails via thenorth - south <= LAT_EPSILONguard.Grids with a positive-
eaffine are stored south-up — common for GRIB/IFS-derived reanalysis data. For these,projectedTileCornersputs the southern edge attopLeft, sonorth - southis negative on every tile, the guard trips, and the clamp is silently skipped. The pole then gets meshed:makeClampedForwardTo3857collapses the polar vertices onto a single Y, producing degenerate triangles that never converge:…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
vaxis aslat(v) = top + v * (bottom - top), so intersect that segment with[-maxLat, maxLat]by solving forvat each limit and taking the overlapping interval viamin/max. This keepsvanchored to the texture's top row (topLeft), so the band is correct whether latitude increases or decreases withv.North-up behavior is unchanged (verified — all pre-existing tests pass without modification).
Notes
undefined(full mesh) by design; that's exactly the case that needed handling, not skipping.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:
topLeft = -90,bottomLeft = 90) → clamped band, previouslyundefined;topLeft = -90,bottomLeft = 80) — asymmetric, so it would catch a mirrored band.vitest runon the suite: 7/7 pass.tsc --noEmitclean.🤖 Generated with Claude Code