Skip to content

fix: warp rendered GeoTIFFs to Web Mercator so overlays land in place - #20

Merged
TommySquared merged 1 commit into
mainfrom
fix/geotiff-web-mercator
Oct 21, 2026
Merged

TommySquared merged 1 commit into
mainfrom
fix/geotiff-web-mercator

Conversation

@TommySquared

Copy link
Copy Markdown
Collaborator

Summary

GeoTIFF overlays were drawn in the wrong place whenever the raster was not already in Web Mercator. This warps each rendered image onto a Web Mercator grid before it is sent to the browser, so overlays line up with the basemap, with vector layers, and with each other.

The problem

process_geotiff_rgb / process_geotiff_singleband rendered the PNG on the file's native grid and reported its lon/lat bounding box (transform_bounds). The browser places an image source by those four corners and stretches it linearly in Web Mercator. For a UTM or equal-area raster this rotates and stretches the image:

Raster (UTM) Worst misplacement Typical
180 km wide at 68°N 9.5 km 3.1 km
43 km wide at 40°N 680 m 340 m
~10 km wide 140 m 75 m

Two rasters in different projections (e.g. UTM and EPSG:6933) are misplaced differently, so they visibly fail to overlap. Vector polygons are unaffected, so imagery appears to slide against them.

The fix

_to_web_mercator() takes the finished RGBA from _process_rgb / _process_singleband and reprojects it with nearest-neighbour resampling onto a Web Mercator grid. Pixels outside the source footprint get alpha 0. It returns exact lon/lat bounds, which form an axis-aligned box in both CRSs, so four-corner placement is exact.

Tests

server/tests/test_geotiff_placement.py:

  • Renders an 80 × 60 km UTM raster at 68–69°N through the public API, places the PNG the way the browser does, and checks three marker pixels: all land within one 100 m source pixel (36–75 m). The previous placement put the worst 1.3 km off; a companion test measures that failure on the same raster.
  • The RGB footprint is transparent outside and opaque inside.
  • Web Mercator input is not resampled.

The rest of the suite is unchanged relative to main (same pre-existing failures/errors in the MCP and screenshot harness).

🤖 Generated with Claude Code

The browser places a GeoTIFF overlay by its four lon/lat corners and
stretches it linearly in Web Mercator, but the image was rendered on the
file's native grid and placed by transform_bounds' lon/lat envelope. Any
non-Mercator raster (eo-gpt writes UTM, EPSG:6933, EPSG:3832) landed
rotated and stretched: up to 9.5 km off for a 180 km UTM raster at 68N,
~0.7 km for a 40 km one, ~140 m for 10 km. Two rasters in different
projections stopped overlapping, and imagery slid against vector polygons.

_to_web_mercator() warps the finished RGBA (nearest neighbour; masking
still runs on the native grid, unchanged) onto a Web Mercator grid and
returns its exact lon/lat bounds. EPSG:3857 input is passed through;
rasters reaching past 85.05 degrees keep the old placement with a warning.

Tests place the PNG the way the browser does: three markers on an
80 x 60 km UTM raster at 68-69N land within one 100 m pixel (36-75 m)
where the native-grid placement put the worst 1.3 km off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@TommySquared
TommySquared merged commit 8cdedf7 into main Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant