A VRT driver that understands mosaics and pixel functions but not warps only understands two-thirds of what GDAL actually writes.
Today we released OxiGeo 0.2.3 — built around two GitHub issues from real users. Issue #15 reported that oxigeo-vrt rejected every gdalwarp -of VRT product — a Warped VRT’s <GDALWarpOptions> block — with “Band must have at least one source or a pixel function”: the driver had no concept of a warp at all. Issue #16 reported that vector-layer support was incomplete: Dataset::open on a GeoPackage reported layer_count() == 0, with no public API to read a layer’s features regardless of format. Both are now implemented for real — 1,635 new lines in oxigeo-vrt and 1,405 new lines in the oxigeo facade.
No C. No C++. No Fortran. OxiGeo 0.2.3 still compiles to a single static binary (or WASM) and runs everywhere Rust runs — and it ships a real reprojection engine without picking up a single new native dependency to get there.
Why 0.2.3 is a game changer
A format driver that silently understands only part of the format it claims to support fails in the worst way — at parse time, on real-world files:
- Every Warped VRT GDAL has ever written — the direct output of
gdalwarp -of VRT— was rejected before a single pixel was read, becauseVrtDataset::validateapplied the same “needs a source or pixel function” rule that plain VRTs need to a band type that legitimately carries neither - A WKT string naming EPSG:4326 could silently resolve to EPSG:7030 instead — the ellipsoid’s own authority code, nested one level deeper in the tree, matched before the CRS’s real root-level code did
Dataset::open("x.gpkg")always reported zero layers — the facade’s vector-open path had no GeoPackage arm at all- GeoPackage
fidcolumns read backNULLon every feature, because SQLite stores anINTEGER PRIMARY KEYonly in the row’s ownrowid, never in the stored payload
OxiGeo 0.2.3 ends all of that:
- A real backward warp engine. New
warp,srs, andsource_datasetmodules giveoxigeo-vrtWarpOptions,WarpKernel,ReprojectionTransformer, andGenImgProjTransformer— plus a WKT/PROJ4/EPSG:nresolver (srs::resolve_crs) and recursive nested-VRT source dispatch (capped at 16 levels, so a self-referencing VRT fails cleanly instead of blowing the stack). - Depth-aware CRS resolution.
srs::resolve_crsnow tracks WKT bracket depth and reads only the root node’s ownAUTHORITY/ID— the ellipsoid’s nested code no longer wins by appearing first in the string. Dataset::layers()for real, across three formats. GeoPackage (behind the non-defaultgpkgfeature), Shapefile, and GeoJSON all now report real layers and features through one facade API — no format-specific code required at the call site.- The GeoPackage
fidrowid-alias fix.gpkg_schemanow detects anINTEGER PRIMARY KEYcolumn at schema-parse time and substitutes the row’s realrowidwhenever the stored cell isNULL. - The facade actually opens
.vrtfiles now.Dataset::openpreviously routed every VRT through a generic fallback that zeroed outwidth()/height()/band_count()/geotransform(); it now parses the real VRT header, and every raster read method — including through nested warps and mosaics — dispatches to the VRT reader.
Technical Deep Dive: what issues #15 and #16 actually touched
- The warp engine (
oxigeo-vrt::warp).WarpOptionsis the parsed<GDALWarpOptions>block;WarpResampleAlg::is_kernel_exact()reports which resample algorithms the engine runs exactly versus approximates — stated honestly rather than silently:NearestNeighbourandBilinearare exact today, while Cubic/CubicSpline/Lanczos/Average/Mode parse and select correctly but currently resample bilinearly. - A new
VrtError::EmptyWindowvariant. Distinguishes “no source covers this window” — legitimate on a warp over a sparse mosaic, mirroring GDAL’sERROR_OUT_IF_EMPTY_SOURCE_WINDOW=FALSEbehavior — from a genuine structural error, so a routine mosaic gap can no longer mask a real bug. - A quick-xml 0.41 entity-reference fix. quick-xml reports
"/&/"as their ownEvent::GeneralRef, separate from surroundingEvent::Text; those events fell through the parser’s catch-all arm and vanished, so a<SRS>block written by this crate’s ownVrtXmlWriterread back with every"silently missing. gpkg_schema, shared across two read paths. The sameCREATE TABLEcolumn/constraint parser now backs both the newlayers()reader and the existing streaming GeoPackage path — includingis_table_constraint, which stopsCONSTRAINT pk_geom_cols PRIMARY KEY (...)-style body items from being parsed as bogus extra columns.oxigeo-vrtgained a dependency onoxigeo-projto perform the reprojection — still Pure Rust, sinceoxigeo-proj’s default feature set excludes theoxiproj-db/tokio EPSG-database path, so this doesn’t pull SQLite into a defaultoxigeo-vrtbuild.
Getting Started
[dependencies]
oxigeo = "0.2" # GeoTIFF + GeoJSON + Shapefile by default
use oxigeo::Dataset;
fn main() -> oxigeo::Result<()> {
// Works for plain, mosaic, AND warped VRTs now — including whatever
// `gdalwarp -of VRT` wrote, previously rejected at parse time.
let dataset = Dataset::open("warped.vrt")?;
println!("{}x{}, {} bands", dataset.width(), dataset.height(), dataset.band_count());
let band0: Vec<u8> = dataset.read_band(0)?;
// GeoPackage needs the (non-default) `gpkg` feature; .shp/.geojson are on by default.
let cities = Dataset::open("cities.gpkg")?;
let layer = cities.layer(0)?; // or .layer_by_name("cities")
println!("{} ({:?}), {:?} features", layer.name(), layer.geometry_type(), layer.feature_count());
for feature in layer.features()? {
// feature.geometry: Option<oxigeo::Geometry>, feature.properties: HashMap<String, oxigeo::FieldValue>
println!("{:?} — {:?}", feature.geometry, feature.properties);
}
Ok(())
}
What’s New in 0.2.3
- Warped VRT support (issue #15): real
<GDALWarpOptions>warp engine — newwarp/warped/srs/source_datasetmodules inoxigeo-vrt(1,635 lines) - Vector layers (issue #16):
Dataset::layers()/layer()/layer_by_name()/layer_names()andLayer::features()for GeoPackage/Shapefile/GeoJSON — newoxigeo::{Layer, LayerFeatures}, plusFeature/FieldValue/Geometryre-exported fromoxigeo-core::vector - Depth-aware WKT
AUTHORITY/IDresolution — fixes an EPSG:4326 string silently resolving to the ellipsoid’s own EPSG:7030 relativeToVRTnow round-trips on both read and write; a quick-xml 0.41 entity-reference fix that was dropping escaped"from<SRS>blocks- The
oxigeofacade now parses real VRT metadata and dispatches raster reads through the VRT reader, instead of opening every.vrtwith a zero-filledDatasetInfo - GeoPackage
fidrowid-alias fix (INTEGER PRIMARY KEYcolumns no longer read backNULL), and named table-level constraints no longer misparsed as columns - Issue #14 needed no code change this time — the band/window/interleaved readers it asked for already shipped in 0.2.2
- Routine dependency maintenance:
scirs2-core0.6.4 → 0.6.5,oxicode0.2.4 → 0.2.5 (a hardening release adding DoS/panic/overflow rejections to its decode paths — no OxiGeo-visible behavior change) - Quality gates: 18,184 tests passed / 0 failed / 101 skipped (
--all-features; 16,722/0/80 on default features), 412 doc tests, clippy 0 warnings,cargo deny checkpassing across all 75 crates
Tips
- Re-check any VRT you’d previously given up on. If
gdalwarp -of VRToutput ever threw “Band must have at least one source or a pixel function” against this crate, that file was always valid — it’s readable as of 0.2.3 with no changes on your end. - Call
WarpResampleAlg::is_kernel_exact()before you trust a resample. OnlyNearestNeighbourandBilinearrun their named kernel exactly today; Cubic/Lanczos/Average/Mode currently fall back to bilinear internally even though they parse and select correctly. - Reading a GeoPackage? Turn on the
gpkgfeature.Dataset::layers()covers GeoPackage only behind that non-default flag — Shapefile and GeoJSON work out of the box. - FlatGeobuf and GeoParquet aren’t in
layers()yet. Both returnOxiGeoError::NotSupportednaming the driver; they remain reachable only through the streaming feature API for now. - Match on
VrtError::EmptyWindowinstead of treating it as fatal. It’s the expected result of a warp landing on a gap in a sparse mosaic, not a structural error in the VRT itself. - Feature properties come back as
HashMap<String, FieldValue>. Pattern-matchFieldValueper field rather than assuming a single scalar type — GeoPackage, Shapefile, and GeoJSON attribute schemas all flow through the same enum.
This is the foundation
OxiGeo 0.2.3 leans on the same Pure Rust COOLJAPAN stack as every release before it — and this release leans on it a little more directly: the new warp engine’s reprojection runs through OxiProj, the same CRS layer that powers oxigeo-proj’s standalone transforms. HDF5/NetCDF read-write continues through oxih5 and oxinetcdf, SQLite via oxisql-sqlite-compat (Limbo) — now doing double duty for both the streaming and the new layers() GeoPackage paths — TLS via OxiTLS, compression across every format driver via the OxiArc family, ML tensor math via SciRS2-Core (bumped to 0.6.5 this release), and model export validated against OxiONNX. Every one of those is itself Pure Rust — which is how a 75-crate, ~797K-SLoC workspace ships a real reprojection engine without picking up a single new native dependency.
Repository: https://github.com/cool-japan/oxigeo
Star the repo if you’d rather your VRT driver understood the file GDAL actually wrote than reject it at parse time. Two real user-filed issues, two real fixes — a warp engine and a vector-layer API, both grounded in files that were failing before today.
The era of “the VRT driver almost works” is over. Pure Rust geospatial is here — fast, safe, and sovereign.
— KitaSan at COOLJAPAN OÜ
August 5, 2026