Skip to content

COG tiles near ±180° longitude cause RasterReprojector mesh divergence in Web Mercator mode #366

@james-willis

Description

@james-willis

Description

When rendering an EPSG:4326 COG that spans the antimeridian (±180° longitude) in a Web Mercator viewport, tiles near the dateline cause RasterReprojector mesh refinement to diverge, producing mislocated rectangles across the map.

The root cause: forwardTo3857 maps nearby source coordinates on opposite sides of ±180° to EPSG:3857 x-values ~40 million meters apart (+20M vs -20M). The mesh triangles connecting these vertices span the entire map, and the reprojection error never converges (error=43200 pixels after 10001 iterations).

Reproduction

Load a global EPSG:4326 COG (e.g. WorldPop population data):

new COGLayer({
  geotiff: 's3://wherobots-examples/data/ppp_2020_1km_Aggregated.tif',
  // ...
})

Console shows:

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

Analysis

In _renderSubLayers, for Web Mercator viewports, reprojectionFns.forwardReproject is set to forwardTo3857. For tiles at the antimeridian, this projection has a discontinuity — longitude values like +179° and -179° (which are 2° apart geographically) map to x-values ~40M meters apart in EPSG:3857. The RasterReprojector mesh subdivision cannot resolve this discontinuity no matter how many iterations it runs.

Workaround

We're patching _renderSubLayers to detect antimeridian-crossing tiles by checking if the tile's corner x-coordinates in EPSG:3857 span more than half the world circumference. When detected, forwardTo3857 is wrapped to shift negative x-values by +circumference, making the mesh continuous:

const HALF_CIRCUMFERENCE = WEB_MERCATOR_METER_CIRCUMFERENCE / 2;
const cornerXs = /* compute tile corners in EPSG:3857 */;
if (Math.max(...cornerXs) - Math.min(...cornerXs) > HALF_CIRCUMFERENCE) {
    tileForwardTo3857 = (x, y) => {
        const [px, py] = forwardTo3857(x, y);
        return [px < 0 ? px + WEB_MERCATOR_METER_CIRCUMFERENCE : px, py];
    };
    tileInverseFrom3857 = (x, y) => {
        const unwrapped = x > HALF_CIRCUMFERENCE ? x - WEB_MERCATOR_METER_CIRCUMFERENCE : x;
        return inverseFrom3857(unwrapped, y);
    };
}

Environment

@developmentseed/deck.gl-geotiff: 0.4.0
@developmentseed/raster-reproject: 0.4.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions