Repository navigation
Fix raster/vector misregistration and projected-CRS input handling - #308
Merged
Merged
Conversation
The extent assumed the tiepoint was raster point (0, 0) and that it marked the top-left pixel's outer corner. Account for the tiepoint's (I, J) and, for GTRasterTypeGeoKey = PixelIsPoint, shift by half a pixel so the extent covers the outer pixel edges, as GDAL does. Extents now match gdalinfo for every fixture in geotiff-test-data except rotated ones (unsupported).
Rasters didn't line up with vector layers, e.g. the Great Lakes over a world raster in EPSG:3857: - The raster mesh was sampled over the extent clamped to the target CRS's area of use (±85.06° for 3857), but its UVs always spanned the full texture, squashing a ±90° raster toward the equator (~310 km at 45°N). UVs are now derived from each vertex's source coordinate. - A fixed 32×32 grid mapped the texture affinely within cells far larger than the projection's curvature allows (~7 km off at 45°N, 53 km near the pole in 3857). The grid is now refined per axis until the error at edge midpoints is under 0.25 texel, capped at 2^18 cells. - Projected-source rasters shown in a local CRS (e.g. 3857 → UTM) drew folded triangles; only the part covering the target's (expanded) area of use is sampled now. geo_geodesy::Transformer always treated input as degrees, scaling metres by π/180. That broke vector reprojection from projected CRSs, the cursor lat/lng readout, the measure tool, and the camera on CRS change. Replace it with rgis_crs::CrsTransformer, which handles units per CRS, and drop the geo-geodesy dependency. Add an exhaustive registration test (rgis-transform alignment_tests) that pushes sample points through the vector pipeline and locates them in the rendered raster mesh, across 36 raster-CRS × target-CRS combinations.
frewsxcv
enabled auto-merge
October 8, 2026 20:40
Grid refinement measured the mesh's error using its f32 positions. For a small, high-resolution raster (e.g. 5 cm drone imagery in 3857, where f32 spacing is ~0.5 m), the rounding alone exceeded the 0.25-texel target, so refinement ran to the 2^18-cell cap. That made the web app hang loading such rasters (Playwright timeouts on the hot-oam, NLCD, Umbra and Maxar fixtures). Sample and measure in f64, converting to f32 only for the final mesh. Also stop refining an axis once doubling it no longer at least halves its error, so no other non-converging case can run to the cap either.
Taken from CI's --update-snapshots run (Linux). Rasters now line up with vector layers, e.g. eox_cloudless coastlines with World: Countries, and antimeridian.tif is clamped at 180° rather than squeezed into -180..-162. The old baselines also predated main's graticule labels and scale bar.
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.
Problem
A world raster and world vector layers don't line up. The Great Lakes in the raster sit roughly 300 km south of the Lakes layer in EPSG:3857, the default CRS.
Causes and fixes
Raster UVs vs. clamped extent (the main bug). Before projecting, the raster grid is clamped to the target CRS's area of use (±85.06° for 3857). The UVs still spanned the whole texture (
[col/cols, 1 - row/rows]), so a ±90° raster was squashed onto ±85.06°. Each vertex's UV now comes from the source coordinate it was sampled at, andProjectedRasterGridcarriesuvsand atriangles()iterator that the renderer uses directly.Grid too coarse. Each grid cell maps the texture with a straight-line (affine) fit, which can't follow Mercator's curve. With a fixed 32×32 grid that caused about 7 km of error at 45°N and 53 km near the pole. The grid now starts at 32 cells per side and refines each axis separately until the error at edge midpoints is under 0.25 texel. The cap is 2048 cells per side and 2^18 cells total. The error is converted to texels through each cell's own affine map, because polar cells are very anisotropic. World 4326 → 3857 ends up at 32×512.
Projected-source rasters in local CRSs. For example, a world 3857 raster shown in UTM drew folded triangles over valid areas. Only the part of such a raster that covers the target's area of use, expanded by 25% on each side, is sampled now.
geo_geodesy::Transformertreats every input as degrees. It always calledCoor2D::gis, which multiplies projected metres by π/180. That broke:All of these now go through a new
rgis_crs::CrsTransformer, which converts units based on whether each CRS is geographic. Thegeo-geodesydependency is removed.GeoTIFF extent. The loader now applies the tiepoint's (I, J) offset and the half-pixel shift for
PixelIsPoint. Extents matchgdalinfofor every fixture ingeotiff-test-dataexcept rotated ones; rotated transforms were never supported.Tests
rgis-transform/src/alignment_tests.rs: an exhaustive registration test. Lon/lat sample points go through the vector pipeline, and synthetic rasters go through the raster pipeline. Each projected sample is then located in the rendered mesh using the same triangles and barycentric UV interpolation as the GPU, and its UV is compared with the true one. The test covers 5 raster setups (world 4326, Great Lakes 4326, world 3857, UTM 18N) × 6 targets (4326, 3857, 3395, 32618, 3978, 3035). The worst case is 0.15 px. With the old UV and grid logic swapped back in, it fails at 168 px (about 310 km) for Great Lakes samples in 3857.CrsTransformerround-trip tests for projected sources.compute_extentunit tests, plus a test pinningpixel_as_point.tifto the extentgdalinforeports.cargo test --workspacepasses, andcargo check --target wasm32-unknown-unknown -p rgisis clean with-D warnings.Playwright snapshot changes
Four
raster-layersnapshots were updated from CI's--update-snapshotsrun:Grid refinement and small high-res rasters
The first CI run timed out loading the hot-oam, NLCD, Umbra and Maxar rasters. The grid error was being measured on f32 mesh positions. For 5 cm drone imagery in 3857, f32 rounding (about 0.5 m) alone exceeds the 0.25-texel target, so refinement ran to the 2^18-cell cap. The error is now measured in f64, and refinement stops on any axis where doubling it no longer halves its error.
small_high_res_rasters_keep_a_coarse_gridcovers this case.Not addressed
EPSG:3857 is still projected on the ellipsoid rather than the sphere, because geodesy's
parse_projdrops+b. It shifts rasters and vectors equally, so it doesn't cause misregistration between them.