Skip to content

feat: Add antimeridian crossing support to RasterReprojector#269

Open
espg wants to merge 2 commits into
developmentseed:mainfrom
englacial:feat/antimeridian
Open

feat: Add antimeridian crossing support to RasterReprojector#269
espg wants to merge 2 commits into
developmentseed:mainfrom
englacial:feat/antimeridian

Conversation

@espg
Copy link
Copy Markdown

@espg espg commented Feb 26, 2026

Ported from #266. Needed for default globe discussed on source-cooperative/source.coop#230.

Summary

  • Detect antimeridian (±180°) crossing from initial corner vertices in RasterReprojector — when a tile's WGS84 longitudes span >180°, normalize them to a continuous range (e.g., [170, 190] instead of [170, -170]) so mesh triangles don't wrap the wrong way around the globe
  • Apply longitude offset to subsequent vertices during mesh refinement
  • Add 5 new unit tests for antimeridian handling

Closes #171

Test plan

  • All unit tests pass (pnpm test in raster-reproject)
  • Run cog-basic example — verify no regression in standard rendering

espg added 2 commits February 26, 2026 13:15
Detect when a tile's output positions span the ±180° meridian and
normalize longitudes to a continuous range (e.g., [170, 190] instead
of [170, -170]). This prevents mesh triangles from spanning 340° of
longitude and ensures correct GPU-side interpolation for tiles near
the date line.
@dcherian
Copy link
Copy Markdown

dcherian commented Mar 3, 2026

I'm curious as to how this works.

In xpublish-tiles, I found that fixing the discontinuity in source CRS space was pointless since proj4 would just wrap to the extents of the output CRS upon transforming. So I had to do the unwrapping after transforming. My solution: use phase unwrapping on the coordinates since we know the coordinate space width.

@kylebarron
Copy link
Copy Markdown
Member

@espg do you think you'd be able to add some visual screenshots/screencasts to your PRs to more visually explain the internals of the changes?

kylebarron added a commit that referenced this pull request May 27, 2026
…hes to ±85.051° (#574)

* docs(specs): add antimeridian crossing-tile (cut-in-two) design

Design for rendering imagery crossing ±180° in Web Mercator (issues #171,
#366). Splits a crossing tile at the antimeridian into west/east pieces so
each reprojects as a normal non-crossing tile — avoiding the proj4-rewrap
unwrap that prior attempts (#353/#374/#269) stumbled on. Generalizes the
RasterReprojector to accept a delaunator-shaped initial-triangulation seed
(subsuming #351 uvBounds / pole clamp), splits in _renderSubLayers into two
single-mesh RasterLayers, and uses a two-box bounding volume composing with
the merged world-copy traversal (#518).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(specs): refine antimeridian spec — add test fixture, handle slanted cuts

- Use the vendored geotiff-test-data antimeridian.tif fixture (42x42 EPSG:4326,
  crosses -180 at column 24) as the primary deterministic crossing test.
- Handle slanted (rotated-geotransform) cuts, not just vertical: any straight
  cut yields convex pieces delaunator triangulates exactly; error only on
  curved/concave cuts. createInitialConditions is therefore part of the MVP
  crossing path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(raster-reproject): add createInitialConditions (delaunator-backed seed)

Add the InitialTriangulation type and a tree-shakeable createInitialConditions
helper that builds a Delaunay seed from a UV point set. delaunator is confined
to its own module (initial-conditions.ts); delatin.ts does not import it and the
package is sideEffects:false, so it tree-shakes out for consumers that don't use
it. Foundation for antimeridian cut-in-two and the sub-domain capability in #351.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(raster-reproject): document delaunator seed pattern, don't ship a wrapper

Per review: a one-line delaunator wrapper isn't worth a runtime dependency.
Expose only the InitialTriangulation type and show the delaunator one-liner in
its docstring. delaunator moves to devDependencies — used by tests to validate
winding compatibility, not shipped to consumers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(specs): drop createInitialConditions wrapper from antimeridian design

raster-reproject exposes only the InitialTriangulation type + documents the
delaunator one-liner; delaunator is a dev/test dep (winding validation), not
shipped. Runtime seed-building for crossing tiles is the deck.gl-raster
builder's job (follow-up plan). Mark stage 1 done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(raster-reproject): seed RasterReprojector from an initial triangulation

Generalize the constructor to accept an optional initialTriangulation seed
(delaunator's data shape), defaulting to a hardcoded unit-square seed so
behavior is unchanged and the package needs no runtime triangulation dep.
Refinement only ever splits existing triangles, so a sub-domain seed confines
the mesh to that region. Tests build seeds via delaunator (the documented
pattern) to validate winding compatibility + sub-domain confinement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(raster-reproject): add rectangleSeed helper for sub-rectangle seeds

Build an axis-aligned 2-triangle rectangle seed for a UV sub-rectangle (no
delaunator, runtime-safe). UNIT_SQUARE_SEED is now rectangleSeed(0,0,1,1).
Used to clamp a mesh to a UV band — e.g. the valid Web Mercator latitude band
(#182 / #351) — and reused by the antimeridian vertical-cut case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(deck.gl-raster): add webMercatorClampSeed (clamp mesh to ±85.051°)

Pure helper that returns a rectangleSeed clamping a north-up geographic tile's
reprojection mesh to the Web Mercator latitude band, or undefined when no clamp
is needed/possible (rotated/projected tiles, fully-polar tiles). Avoids the
degenerate near-pole triangles from #182 / #351. Unit-tested with synthetic
corner latitudes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(deck.gl-raster): clamp Web Mercator meshes to valid latitude band

Wire the reprojector seed through the render path: RasterLayer gains an
initialTriangulation prop (passed to RasterReprojector, regenerated on change);
getTileMetadata computes a per-tile _webMercatorReprojectorSeed via
webMercatorClampSeed; _renderSubLayers passes it in the Web Mercator branch
only (globe shows the poles, full mesh). Fixes the degenerate near-pole
triangles for EPSG:4326 imagery reaching ±90° (#182 / #351).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(deck.gl-raster): name clamp seed as initialTriangulation

Rename webMercatorClampSeed → webMercatorInitialTriangulation and the tile
metadata field _webMercatorReprojectorSeed → _webMercatorInitialTriangulation,
for consistency with the InitialTriangulation type and the
RasterLayer.initialTriangulation prop (drops the ad-hoc 'seed'/'ReprojectorSeed'
terms).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(deck.gl-raster): rename clamp fn to createInitialWebMercatorTriangulation

Verb-prefixed name for the builder (was webMercatorInitialTriangulation); the
tile metadata field stays _webMercatorInitialTriangulation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(raster-reproject): rename rectangleSeed -> triangulateRectangle

Active verb name for the helper that triangulates a UV rectangle into an
InitialTriangulation; internal UNIT_SQUARE_SEED -> UNIT_SQUARE_TRIANGULATION;
test file renamed to match. Updates the deck.gl-raster clamp caller too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: apply triangulateRectangle rename across source + caller

Completes the rename (a657eb8 only moved the test file): rectangleSeed ->
triangulateRectangle in delatin.ts + index export + the renamed test, and the
deck.gl-raster web-mercator-clamp caller. UNIT_SQUARE_SEED -> UNIT_SQUARE_TRIANGULATION.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

Support images spanning the antimeridian

3 participants