A GeoPackage reader that chokes on any row wide enough to spill onto a second SQLite page, and a VRT parser that rejects the exact sub-pixel coordinates GDAL itself writes, are not edge cases — they’re the common case for real-world files.
Today we released OxiGeo 0.2.4 — a correctness release built around three GitHub issues filed by real users: issue #17 (GeoPackage overflow-page cells), issue #18 (VRT fractional SrcRect/DstRect parsing), and issue #19 (VRT mosaic nodata-skip compositing) — plus the browser COG viewer (AdvancedCogViewer) working end to end for the first time, and two large embedded EPSG registry correctness passes.
No C. No C++. No Fortran. OxiGeo 0.2.4 still compiles to a single static binary (or WASM) and runs everywhere Rust runs — and every one of these fixes landed without picking up a single new native dependency.
Why 0.2.4 is a game changer
Three real bug reports, three real files that shouldn’t have failed:
- A
sqlite_masterrow wide enough to spill onto SQLite overflow pages — a ~5000-character QGIS layer name was enough — failedGeoPackage::from_bytes/load_contents()outright with “overflow cell needs 4061 bytes inline … but only 3209 available”, because the local-payload split usedmin(P, U − 35)instead of SQLite’s real formula, and the overflow-page chain was never followed at all - Every
gdalbuildvrt/gdalwarp -of VRTproduct carrying sub-pixel window values —xOff="9783.50000000003", exactly how GDAL writes them — was rejected with “Invalid u64: invalid digit found in string”, becauseSrcRect/DstRectparsed asu64 - Mosaic compositing let the first source to cover a pixel win even when all it had there was nodata, punching holes along every overlap band of every
gdalbuildvrtmosaic AdvancedCogViewer.open()failed on its very first header read in the one environment it exists for — the browser — because the onlyDataSourceit had was hard-wired toNotSupported("Synchronous read in WASM")- The embedded EPSG registry misregistered whole CRS families: JGD2011 Japan Plane Rectangular zones I–X reported themselves as “UTM zone 51N–60N” — landing real coordinates roughly 4,000 km into the Pacific — and zones XI–XIX didn’t exist at all
OxiGeo 0.2.4 ends all of that:
- A real overflow-page reader.
oxigeo-gpkgnow computes the local-payload split against the true usable page size (page_size − reserved_bytes, via a newSqliteHeader::usable_size()) and reassembles the full payload across the overflow chain. - GDAL’s actual numeric formats.
SrcRect/DstRectparse asf64and round where GDAL rounds — every realgdalbuildvrt/gdalwarp -of VRToutput now parses. - Document-order nodata-aware compositing. A later overlapping source can now supply valid data where an earlier one only had nodata, matching GDAL’s own semantics exactly, including the float NaN convention.
- A browser COG viewer that actually opens. A new
buffered_sourcemodule inverts the async/sync mismatch:BufferedRangeSourceserves the synchronousDataSourcetrait from a range cache, recording what it’s missing and re-running until nothing’s pending — a normally laid-out COG now opens in oneHEADplus one coalesced range request. - Two EPSG correctness passes. ~79 US State Plane zones had their native survey-foot/international-foot units corrected (some
x_0/y_0constants were off by the metre/foot ratio, ~3.28×), and all nineteen JGD2011 Japan Plane Rectangular zones are now registered from a verified per-zone table, with the real UTM zones moved to their true codesEPSG:6688–6692.
Technical Deep Dive: what issues #17, #18, and #19 actually touched
- The overflow-page chain (
oxigeo-gpkg).SqliteHeadergained a publicreserved_bytes: u8field (byte 20 of the SQLite header) andusable_size(). The local-payload threshold — how many bytes of a cell SQLite stores inline before spilling to an overflow page — is now computed from the usable page size, not the raw one, matching SQLite’s ownK/Mformula instead of the previousmin(P, U − 35)approximation. SrcRect/DstRectasf64(oxigeo-vrt). GDAL keeps warp/mosaic source and destination windows as doubles internally and rounds only at rasterization time, so real VRTs carry values like9783.50000000003. A scientific-notation regression test now separately pins<WarpMemoryLimit>6.71089e+07</WarpMemoryLimit>— parsed asf64since 0.2.3 — so the two are never conflated again.- Per-band nodata comparison in mosaic compositing. The nodata check now runs on the decoded sample value for the band’s actual data type rather than raw bytes, so float nodata — including NaN — compares correctly across sources.
buffered_source(oxigeo-wasm).pull_until_readyre-runs a synchronous parse operation, fetching only the ranges it’s missing between attempts, until a round makes no progress or the operation succeeds — bounded in every direction, and keyed on the CogReader’s own best-effort miss log rather than onErr, so a silently-swallowed failure (e.g. losing the file’sepsgCode) still triggers a retry.- The EPSG registry’s
epsg_unit_forhelper. Previously every projected-CRS registration hardcoded its reported unit to"metre"regardless of what its own PROJ string declared — now the reported unit is derived from each entry’s real+units=/+to_meter=token.
Getting Started
[dependencies]
oxigeo = "0.2" # GeoTIFF + GeoJSON + Shapefile by default
oxigeo-gpkg = { version = "0.2", features = ["geojson-convert"] } # GeoPackage, opt-in convert
use oxigeo::Dataset;
fn main() -> oxigeo::Result<()> {
// A GeoPackage with a long layer name (QGIS-style) no longer fails to open —
// the sqlite_master row's overflow pages are now followed correctly.
let gpkg = Dataset::open("wide_schema.gpkg")?;
// A gdalbuildvrt mosaic with fractional SrcRect/DstRect windows now parses,
// and overlapping sources composite with per-source <NODATA> honoured.
let mosaic = Dataset::open("mosaic.vrt")?;
println!("{}x{}, {} bands", mosaic.width(), mosaic.height(), mosaic.band_count());
Ok(())
}
What’s New in 0.2.4
- GeoPackage overflow-page cells (issue #17): correct local-payload split against the true usable page size, overflow chain followed
- VRT fractional window parsing (issue #18):
SrcRect/DstRectasf64, rounded where GDAL rounds - VRT mosaic nodata-skip compositing (issue #19): document-order compositing now honours each source’s
<NODATA>, NaN-aware - Browser COG viewer works for the first time: new
buffered_sourcesync-over-fetch driver;AdvancedCogViewer/WasmCogViewernow agree onoverviewCount, honour theirlevelargument, and size RGBA buffers from the requested level’s real tile geometry oxigeo-projverified feature matrix:no_std(newlibm-backed math shim),proj-db(now impliesstd, and no longer changes transform results — feature-invariant CRS resolution on OxiProj 0.1.5),proj4rs-compatall compile and are tested standalone- SIMD batch fast path gated for correctness: declines to a scalar fallback for non-Greenwich meridians, non-ENU axes, real
nadgrids, or unrecognized ellipsoids — previously silently mis-projected by up to 2.1×10⁵ m - EPSG registry corrections: ~79 US State Plane zones’ native ft/us-ft units, all 19 JGD2011 Japan Plane Rectangular zones (previously 10 misregistered as UTM, 9 missing entirely)
- New APIs:
GeoParquetReader::from_bytes()for in-memory GeoParquet,read_geometries_optional()/extract_geometries_optional()for null-preserving reads,SphericalTransverseMercator/EllipsoidalTransverseMercatortype aliases,GeoPackage::scan_table_by_name_typed()for REAL-affinity-correct scans quick-xml→oxixml-quickxml-compat(Pure Rust drop-in shim); routine ecosystem bumps including OxiProj 0.1.5- Quality gates: 18,327 tests passed / 0 failed / 101 skipped (
--all-features; 16,862/0/80 skipped on default features), 414 doc tests, clippy 0 warnings,cargo deny checkpassing across all 75 crates
Tips
- Re-check any GeoPackage you’d given up on. Long layer names, wide schemas, big
sqlite_masterrows — anything that used to throw “overflow cell needs N bytes inline” now opens. - Re-check any
gdalbuildvrt/gdalwarp -of VRToutput that threw “Invalid u64”. That file was always valid GDAL output; 0.2.4 parses it with no changes on your end. - Reach for
GeoParquetReader::from_bytes()for in-memory data. Every read path —read_geometries,read_row_group,read_all,read_pushdown— now behaves identically whether the source is a file or an in-memory buffer. - Prefer
SphericalTransverseMercator/EllipsoidalTransverseMercatorover the bareTransverseMercator/GaussKrugernames. The bareTransverseMercatoris sphere-based and can be off by ~24.9 km of northing at 48°N if you meant UTM/national-grid math. - Building
oxigeo-projwith--no-default-features --features proj-db? It compiles for the first time in 0.2.4, andproj-dbnow impliesstd— it was never expressible underno_std+allocto begin with. - Null geometries no longer silently desync from property rows. Switch to
read_geometries_optional/extract_geometries_optionalif you need each row’s geometry (or its absence) at its original index.
This is the foundation
OxiGeo 0.2.4 leans on the same Pure Rust COOLJAPAN stack as every release before it: CRS transforms through OxiProj (bumped to 0.1.5 this release, itself the authority-path correctness release that unblocked proj-db’s feature-invariance fix), HDF5/NetCDF through oxih5/oxinetcdf, SQLite via oxisql-sqlite-compat (Limbo) — now exercised even harder by the overflow-page fix — TLS via OxiTLS, XML via the new oxixml-quickxml-compat shim, compression across every format driver via the OxiArc family, and tensor math via SciRS2-Core. Every one of those is itself Pure Rust — which is how a 75-crate, ~814K-SLoC workspace fixes three overflow/parsing/compositing bugs and ships a working browser COG viewer without a single new native dependency.
Repository: https://github.com/cool-japan/oxigeo
Star the repo if you’d rather your GeoPackage reader followed the overflow chain than reject the file, and your VRT parser accepted the numbers GDAL actually writes instead of the ones it wishes GDAL wrote. Three real user-filed issues, three real fixes — grounded in files that were failing before today.
The era of “the format reader mostly works” is over. Pure Rust geospatial is here — fast, safe, and sovereign.
— KitaSan at COOLJAPAN OÜ
August 18, 2026