fix: render COGs in bounded projections (Mollweide, Sinusoidal, Equal Earth) - #636
Open
tylere wants to merge 1 commit into
Open
fix: render COGs in bounded projections (Mollweide, Sinusoidal, Equal Earth)#636tylere wants to merge 1 commit into
tylere wants to merge 1 commit into
Conversation
… Earth) Four root causes prevented tiles from rendering when a COG uses a CRS with a bounded domain (an ellipse or other closed shape), such as World Mollweide (ESRI:54009): 1. **AffineTilesetLevel.projectedTileCorners** extrapolated past the array boundary for the last tile row/column. For bounded projections the extrapolated corners fall outside the CRS domain; proj4 clamps them to ±85.05° in Mercator, collapsing all such corners to a single horizontal line and producing a zero-height bounding volume — causing every tile to be culled immediately. Fix: clip tile corners to `[0, arrayWidth] × [0, arrayHeight]`. 2. **RasterTileNode._getGenericBoundingVolume** passed NaN common-space positions (from OOD corners) straight to `makeOrientedBoundingBoxFromPoints`, producing a degenerate OBB that always returned -1 from `computeVisibility`. Fix: filter NaN positions before OBB/AABB computation; return an off-screen degenerate volume for fully-OOD tiles; build the OBB from AABB corners (with a 1-unit minimum extent on each axis) to avoid a collinear-points degenerate OBB for equatorial Mollweide tiles. 3. **RasterLayer._generateMesh** fed OOD tile corners to the adaptive `RasterReprojector`; it exhausted its iteration budget refining OOD-vertex triangles, leaving the valid region severely under-resolved. Fix: detect "border tiles" (any corner fails the round-trip forwardReproject→inverseReproject check), and for those tiles build a dense 64×64 uniform grid mesh instead, filtering out OOD-vertex triangles. Also filter OOD triangles from the adaptive mesh path via `reprojectorToMesh`. 4. **RasterTileset2D.projectPosition** was not explicit about returning NaN for OOD inputs. Made the NaN-pass-through explicit so future changes to the clamping path don't silently break the GPU triangle discard. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
|
These changes are based on patches used with deck.gl-raster 0.8.0-beta.2 |
Member
|
I don't know enough about these types of projections but we definitely need a careful understanding of how they work and careful tests before merging |
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.
Summary
Fixes rendering of COGs that use a CRS with a bounded domain — an ellipse or other closed shape — such as World Mollweide (ESRI:54009), Sinusoidal, and Equal Earth. Before this PR, loading any such COG produced a blank map.
Abbreviations used below:
[minX, minY, maxX, maxY]). Used here as thecommonSpaceBoundsfor a quick pre-filter before the full frustum cull.Four root causes were identified and fixed:
1.
AffineTilesetLevel.projectedTileCorners— corner extrapolation past array boundaryThe last tile in each row/column has nominal corners at
(col+1)*tileWidthpixels, which exceedsarrayWidthwhen the tile matrix doesn't evenly divide the array. For bounded projections, those extrapolated corners fall outside the CRS domain; proj4 clamps them to ±85.05° in Mercator, collapsing all such corners onto a single horizontal line and producing a zero-height bounding volume — causing every tile to be culled immediately.Fix: clip
right = min((col+1)*tw, arrayWidth)andbottom = min((row+1)*th, arrayHeight)before applying the affine.2.
RasterTileNode._getGenericBoundingVolume— NaN positions in OBB inputOOD corners produce NaN common-space positions. Passing NaN to
makeOrientedBoundingBoxFromPointsproduced a degenerate OBB that always returned -1 fromcomputeVisibility, incorrectly culling every tile. Additionally, when the only valid reference points are collinear (e.g. the equatorial band of a Mollweide tile), the OBB is degenerate for a different reason.Fix: filter NaN positions before OBB/AABB computation; return an off-screen degenerate bounding volume for fully-OOD tiles; build the OBB from the four AABB corners (with a 1-unit minimum extent on each axis) so the OBB is always non-degenerate.
3.
RasterLayer._generateMesh— adaptive reprojector choked by OOD verticesTiles that straddle the CRS domain boundary have some corners with valid output positions and some OOD. The adaptive
RasterReprojectorspent its entire iteration budget refining OOD-vertex triangles, leaving the valid area of the tile severely under-resolved (error 100s of pixels).Fix: detect "border tiles" by checking whether any corner's round-trip error via
forwardReproject → inverseReprojectexceeds 1% of the tile CRS width. For border tiles, build a dense 64×64 uniform grid mesh and filter out any triangle whose vertex fails the OOD check — producing a clean domain-boundary cutoff. Also filter OOD triangles from the adaptive mesh path inreprojectorToMesh.4.
RasterTileset2D.projectPosition— explicit NaN for OOD inputsMade the NaN pass-through explicit so the GPU rasterizer reliably discards triangles touching OOD vertices, regardless of future changes to the clamping path.
Test plan
pnpm -r test— 135 pass (4 pre-existing failures inweb-mercator-clamp.test.tsrelated totriangulateRectanglenot exported from dist; unrelated to this PR)pnpm biome check— clean across all changed filespnpm -r typecheck— same pre-existing failures as onmain(InitialTriangulationnot in dist); no new errors from this PRaffine-tileset-level.test.tshttps://data.source.coop/vizzuality/hfp-100/hfp_2017_100m_v1-2_cog.tif(World Mollweide HFP COG) in thecog-basicexample — tiles should render over the globeRelated
affine-tileset-levelfix from that PR; this PR includes that fix plus the three additional root causes)🤖 Generated with Claude Code