All notable changes to OxiGeo will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.2.4 - 2026-08-18
oxigeo-proj: added the unambiguous type aliasesSphericalTransverseMercator(=TransverseMercator) andEllipsoidalTransverseMercator(=GaussKruger), re-exported at the crate root, so call sites can state which Earth model they mean —TransverseMercatoris sphere-based and is wrong for UTM/national grids by ~24.9 km of northing at 48° N, which its docs now warn about prominently; a new regression test pins the two apart at a real UTM 33N reference point.oxigeo-proj: re-exported the ellipsoidal Transverse Mercator kernelprojections::tmerc_forward/projections::tmerc_inversefromprojections(previously reachable only asprojections::cylindrical::tmerc_*).oxigeo-proj:transformnow re-exportsSphericalTransverseMercatorandEllipsoidalTransverseMercatoralongsideCassineSoldner/GaussKruger/TransverseMercator, souse oxigeo_proj::transform::*surfaces the two aliases instead of forcing the longertransform::cylindrical::path. Samestdgate as the existing re-exports; a regression test now imports them through the glob and checks they denote the same types as the crate-root re-exports.oxigeo-geoparquet:GeoParquetReader::from_bytes(impl Into<bytes::Bytes>)reads a GeoParquet image held entirely in memory. The reader now keeps an internalFile/Bytessource that implementsChunkReader, so every read path —read_geometries,read_row_group,read_all,read_pushdown— behaves identically for on-disk and in-memory inputs, with no change toGeoParquetReader's public shape.oxigeo-geoparquet:GeoParquetReader::read_geometries_optional(row_group)andGeoParquetBatchReader::extract_geometries_optional(batch)returnVec<Option<Geometry>>with exactly one entry per row, so geometries stay index-aligned with their property rows;GeoParquetBatchReader::geometry_encoding()exposes the geometry column's declared encoding.oxigeo-geotiff:tiff::is_mask_ifd(&Ifd, ByteOrderType)classifies a directory as a GDAL internal (transparency) mask, with the pure coretiff::is_mask_markers(new_subfile_type, photometric)and the marker constantstiff::SUBFILE_TYPE_TRANSPARENCY_MASK/tiff::PHOTOMETRIC_TRANSPARENCY_MASK.oxigeo-geotiff:CogReader::ifd_count(),CogReader::level_ifd(level)andCogReader::level_ifd_index(level)expose the level → IFD mapping and the raw chain length, so a consumer that wants the mask IFDs — or wants to know how many non-level IFDs a file carries — can still reach them while the level API stays mask-free.oxigeo-geotiff:CogReader::tile_pixel_size(level, tile_y)returns the decoded pixel dimensions of the blockread_tileproduces at that level — the level's ownTileWidth/TileLength, orImageWidth × RowsPerStripnarrowed for the short final strip — so a caller can size an image buffer that cannot disagree with the bytes it gets.oxigeo-gpkg:GeoPackage::scan_table_by_name_typed(table)scans a table likescan_table_by_namebut applies SQLite's REAL type affinity to the result: SQLite stores a lossless40.0in aREAL/DOUBLE/FLOAT-declared column as the integer40, so an untyped scan surfaces it as anInteger— the typed variant restores every such value to the equivalentFloat(viarestore_real_affinity, driven by the declared column types), so40and40.0read back identically, matching what every affinity-aware SQLite consumer sees.scan_table_by_nameitself is unchanged and still returns raw storage classes.
- Renamed the workspace
quick-xmldependency (Cargo.toml) to theoxixml-quickxml-compatpackage (drop-in quick-xml 0.41 compatible shim), keeping the local dependency namequick-xmlso every consuming crate (oxigeo-drivers-advanced, oxigeo-vrt, oxigeo-services, oxigeo-server, oxigeo-metadata, oxigeo-qc) required no source changes. deny.toml: added aquick-xmlentry to[bans].deny, scoped withwrappers = ["inferno"]for the one remaining transitive path (inferno -> pprof -> oxigeo-algorithms's dev-onlypprofdependency); the direct-consumer graph is clear (cargo tree -i quick-xml -e normal --workspaceis empty).oxigeo-gpkg: gated GeoJSON conversion (vector::geojson_convert, and itsoxigeo-geojson-stream/serde_jsondependencies) behind a newgeojson-convertfeature, kept indefaultso no existing build breaks;cargo build --no-default-features(e.g. for wasm) no longer pulls in theregexfamily viaoxigeo-geojson-stream. Consumers that already buildoxigeo-gpkgwithdefault-features = falsewill need to addfeatures = ["geojson-convert"]to keep usingvector::geojson_convert.oxigeo-proj: theoxiprojdependency is nowoptionaland pulled in by thestdfeature instead of being unconditional. Every OxiProj call site already lived in astd-gated module (transform,pipeline,projections, …), so a--no-default-features(no_std + alloc) build was compiling OxiProj purely as dead weight;cargo tree -p oxigeo-proj --no-default-features -e normalnow lists onlybyteorder,serdeandthiserror.default = ["std"]is unchanged, so the default public surface is byte-identical and the 13 in-workspace dependents (all of which use default features or ask forstdexplicitly) need no change. Migration: two trait impls —impl From<oxiproj::TransformError> for Errorandimpl From<oxiproj::ProjError> for Error— are now#[cfg(feature = "std")]and therefore absent from--no-default-featuresbuilds; they could not have been used there anyway, since theoxiprojtypes they convert from were not linked. NoErrorvariant changed: all of them carryString, not OxiProj types.--no-default-features --features proj-dbremains unsupported (it was already failing to compile before this change, for unrelatedallocprelude reasons inepsg::proj_db) — superseded later in this same release:proj-dbnow impliesstdand compiles, see Fixed below.- Dependency bumps:
oxiproj0.1.5 — the OxiProj authority-path correctness release, which fixes upstream the divergent EPSG authority definitions documented in theproj-dbfeature-invariance entry under Fixed (unit-converted ellipsoid axes, method-aware+lat_tsmapping, LCC 1SP, WGS 84-hub datum composition, prime-meridian datum chains, Molodensky-Badekas operations, PROJ's ballpark/fallback selection policy, and grid direction underPROJ_DATA) — plus routine COOLJAPAN ecosystem bumps (oxiarc,oxicode,oxih5,oxionnx,oxisql,oxistore,oxitls; thequick-xml→oxixml-quickxml-compatmigration has its own entry above). oxigeo-gpkg:SqliteHeadergained the public fieldreserved_bytes: u8(byte 20 of the SQLite database header — bytes reserved at the end of every page) and ausable_size()helper; the issue #17 overflow-page fix (see Fixed) computes local-payload thresholds from the usable page size, not the raw page size. Compatibility note: constructingSqliteHeaderwith a struct literal outside the crate now requires the extra field; code that obtains headers throughSqliteReaderis unaffected.oxigeo-wasm:WasmCogViewer,AdvancedCogViewerandBatchTileLoaderhold their cached parsed reader (see the reader-reuse fix under Fixed) inRc/RefCelland therefore no longer implementSend/Sync. Onwasm32-unknown-unknown— the target these#[wasm_bindgen]types exist for — this is inert (single-threaded, driven from JS); only a non-wasm caller holding one behind aSend/Syncbound would notice, and none exists in the workspace.
oxigeo-geoparquet:GeoParquetBatchReader::extract_geometriesnow dispatches on the geometry column's declared encoding instead of downcasting toBinaryArrayunconditionally — a GeoArrow-native file read throughread_all()/next_batch()previously failed with atype_mismatcherror rather than decoding.oxigeo-geoparquet: null geometries no longer silently desynchronise geometries from their property rows — the newread_geometries_optional/extract_geometries_optionalvariants keep each null as aNoneat its original index (the existing null-dropping methods are unchanged).oxigeo-wasm: GDAL internal-mask IFDs (NewSubfileTypebit 2, orPhotometricInterpretation == 4) are no longer counted as overview levels by the browser COG reader — they share the IFD chain with the overviews, sooverviewCountwas inflated and every level index past the first mask was shifted onto the wrong resolution. The chain is still walked through masks, so overviews stored after one are found.oxigeo-wasm:WasmCogViewer.readTile(level, x, y)now honours itslevelargument on the URL path; it previously called a level-0 shortcut, so every overview request silently re-read full-resolution tiles.oxigeo-wasm:WasmCogViewerandAdvancedCogViewerparse the COG once and reuse the reader across tile reads instead of re-opening the file (HEAD request plus a range request per IFD) on every tile — forAdvancedCogViewerthat happened on every tile-cache miss. The cached reader is keyed by URL, so re-opening a different file never serves stale tiles, and a failed open is retried on the next call.oxigeo-wasm: the URL-backed COG path now normalisesModelPixelScaleTag(33550) Y to its magnitude, soWasmCogViewer.pixelScaleY(), thepixelScaleYkey of the metadata JSON, and the Rustpixel_scale_yfield ofCogMetadata/IfdMetadataare never negative. The GeoTIFF spec defines the tag as strictly positive and conforming writers (GDAL included) store it that way, but a few nonconforming writers bake the north-up sign into it; the URL path previously passed that negative value straight through while theopenBytespath already applied.abs(), so the same raster reported opposite signs depending on how it was loaded. Neither path builds a pixel-to-CRS affine transform, so applying the north-up sign when constructing one remains the consumer's responsibility — callers that were compensating for the negative value on the URL path must drop that compensation.oxigeo-geotiff:CogReaderno longer treats GDAL internal masks as pyramid levels. A mask (NewSubfileTypebit 2, orPhotometricInterpretation == 4) shares the IFD chain with the overviews, sooverview_count()counted one extra level per mask and every level index past a mask named the wrong resolution:read_tile(2, …)on a[full, overview, mask, overview]chain returned the mask's pixels, andGeoTiffReader::level_size(2)the mask's dimensions. Levels are now mapped onto non-mask IFDs and every level-indexed path — the block-offset cache, thetile_byte_rangefallback,band_read::LevelGeometry(window/band reads) andGeoTiffReader::level_size/read_window— resolves through that one map, so the geometry and the tile offsets can no longer describe different images. The same map fixes a latent desync of its own: an IFD whoseImageInfofailed to parse was already skipped when counting overviews but not when indexing tile offsets. Behaviour change: on a masked COGCogReader::overview_count(),GeoTiffReader::level_size,cog::get_cog_info'soverview_countand everylevelargument now describe resolutions only — code that compensated for the inflated count (e.g. by subtracting mask IFDs, or by reading level n+1 to get overview n) must drop that compensation. Raw chain access is unchanged:TiffFile::ifds/image_count()still see every IFD, andCogReader::ifd_count()/level_ifd_index()expose the mapping.oxigeo-wasm:AdvancedCogViewerandWasmCogViewernow report the sameoverviewCountfor the same file.AdvancedCogViewer.open()derived it fromTiffFile::image_count(), which counts every IFD including GDAL internal masks, while its ownreadTileindexedCogReader's mask-free levels andWasmCogViewerskipped masks when walking the chain — so on a masked COG the advanced viewer advertised a level its own tile reads rejected. It now takes the count from theCogReaderit reads through.open()also parses the file once instead of twice (it built a wholeTiffFilefor metadata and then re-opened aCogReaderon the first tile read); the reader it parses is handed straight to the tile path.oxigeo-wasm:readTileAsImageData,readTileWithContrast,computeStatsandcomputeHistogramsize their RGBA buffer from the requested level's tile geometry instead of the full-resolutiontileWidth/tileHeightcaptured atopen(). Now that tile reads honour theirlevelargument, a COG whose overviews declare a differentTileWidth/TileLength(gdaladdois free to choose one) produced anImageDataat the wrong dimensions with the tile truncated or three-quarters transparent. Both viewers convert through one shared helper, so they cannot drift apart again. Each path takes the geometry from the very reader that decodes the block —CogReader::tile_pixel_sizeforopenBytes/AdvancedCogViewer(which narrows for the short final strip of a striped level), the URL reader's own per-level record forWasmCogViewer— so buffer and bytes always agree.oxigeo-wasm:AdvancedCogViewer.open()works in a browser for the first time. The viewer parses the COG withoxigeo_geotiff::CogReader, which reads through the synchronousDataSourcetrait, but the only data source it had wasFetchBackend, whoseread_rangeis hard-wired toNotSupported("Synchronous read in WASM - use async methods")— WASM cannot block onfetch()— and which holds no bytes of its own. Everyopen()therefore failed on the parser's very first header read, and so did every tile read behind it (readTileCached,readTileAsImageData,readTileWithContrast,computeStats,computeHistogram,BatchTileLoader): the whole URL path was dead code in the one environment it exists for. A new crate-privatebuffered_sourcemodule inverts the loop instead of making the parser async:BufferedRangeSourceimplementsDataSourceover a cache of already-downloaded ranges and records the ranges it cannot serve, andpull_until_readyre-runs the synchronous operation, fetching the recorded ranges between attempts, until it completes with nothing pending. Fetches are rounded up to 64 KiB and coalesced, so a normally laid-out COG opens in oneHEADplus one range request and a tile read costs at most one more (none at all when its block is already buffered); a server that ignoresRangeand answers200with the whole body is detected from the status and serves everything from that body thereafter. The loop is keyed on the miss log, not on the error:CogReader::openreads overviewImageInfos, the GeoKey directory and the per-level block index best-effort and swallows the failure, so a driver that retried only onErrwould have returned a reader that silently dropped an overview or lost the file'sepsgCode. Termination is bounded in every direction — a transport error and a genuine format error are surfaced as themselves, a round that downloads nothing new stops with a "made no progress" error, and the round count is capped.AdvancedCogViewernow keeps the reader, its buffer and its transport together for the life of the opened URL, so tile reads reuse everything the header walk downloaded. TheopenBytes(in-memory) path is untouched. Native tests drive the whole loop over synthetic TIFF bytes with an in-memory transport; only theweb_sys-backed implementation of the fetch seam — a thin translation of onefetch()response, with the response decoding split out and tested — is browser-only.oxigeo-wasm: thepyramidblock ofAdvancedCogViewer.getMetadata()no longer contradicts theoverviewCountprinted beside it. It was built from aTilePyramidsynthesised from the image's dimensions alone — halving width and height until a single tile remained — which describes a pyramid the file need not contain: a 4096x4096 COG with 256-pixel tiles and no overviews reportednumLevels: 5next tooverviewCount: 0, and every level past 0 named tile grids that the viewer's own tile reads reject. The block is now derived from the levels the file actually has — the same mask-filtered IFD chainoverviewCountcomes from — with each level's own dimensions and block size read back throughCogReader::level_ifd. Value changes (keys are unchanged):numLevelsis now alwaysoverviewCount + 1;tilesPerLevelhas one[tilesX, tilesY]entry per real level, computed from that level's ownImageWidth/ImageLengthandTileWidth/TileLength(for a striped level, image width byRowsPerStrip) instead of from repeatedly halved level-0 dimensions;totalTilesis the sum over those real levels, and remains a count of spatial blocks — a planar (PlanarConfiguration = 2) file storesSamplesPerPixeltimes as many. One key is added:pyramid.levels, an array of{width, height, tileWidth, tileHeight, tilesX, tilesY}in level order.TilePyramiditself is unchanged, still exported and still the type to use for tile-scheme math.WasmCogViewer.metadataJson()emits no pyramid block and is unaffected.oxigeo-proj:--no-default-features --features proj-dbcompiles for the first time (it produced 56 errors before, so no build could ever have depended on its previous behaviour).proj-dbnow impliesstd: the feature is not expressible on ano_std+allocbuild, becauseepsg::proj_dbopens a file-system database (std::path::{Path, PathBuf},std::env::varforPROJ_DATA/PROJ_LIB) and drivesoxisql-sqlite-compat's async engine through itsblockingAPI on acurrent_threadtokio runtime. The alternative — sprinklingallocprelude imports overepsg/proj_db.rs— would only have moved the 56 errors onto thestd::path/std::env/tokio uses underneath them.proj-dbalso spells its OxiProj featureoxiproj?/epsginstead ofoxiproj/epsg: the sole consumer of that feature,transform::crs_to_oxi→oxiproj::Crs::from_epsg, is itself#[cfg(feature = "std")], andstdalready activatesdep:oxiproj, so the sigil-less form only force-enabled a dependency that was enabled anyway — while makingproj-dba second activator of the optionaloxiproj. With the?,oxiprojhas exactly one activator (std), and dropping"std"fromproj-dbin the future would fail loudly on the missingfrom_epsgrather than quietly re-linking OxiProj into ano_stdbuild.cargo tree -p oxigeo-proj --no-default-features -e normalstill lists nooxiproj, and theproj-dbtree still containsoxiproj+oxiproj-db. Nothing changes forstd/default builds:proj-dbwas already a superset of them in practice.oxigeo-proj:--no-default-features --features proj4rs-compatcompiles for the first time (2 errors before, so again no working consumer could exist).impl From<proj4rs::errors::Error> for Erroris gated onproj4rs-compatalone — the conversion needs nothing beyondalloc— but theError::Proj4rsErrorvariant it constructs and theError::from_proj4rsconstructor it calls were both gated onstd, and theformat!it uses came only from thestdprelude. The variant and the constructor are now gatedany(feature = "std", feature = "proj4rs-compat")(purely additive: every configuration that had them keeps them), anderror.rsimportsalloc::formatunderproj4rs-compat. Newtests/proj4rs_compat_test.rspins the three properties — the constructor is reachable, the message survives, and theDisplaystring stays"Proj4rs error: {0}"withthiserror/stdoff.oxigeo-proj:--no-default-features(no_std+alloc) compiles again without astdcrate of its own. It previously compiled only by accident:oxiprojwas a mandatory dependency, which pulledstdinto the compilation, and rustc collects the inherent impls of primitive types from every crate loaded into it — includingstd'simpl f64 { fn sin(…) … }. Makingoxiprojoptional (see Changed above) removed that, and 73 call sites ingeodesic,datum_transform,ups_projection,geoidandoperation_selectionstopped resolvingsin/cos/tan/asin/atan/atan2/sqrt/powf/powi/ln_1p/floor/rem_euclid, none of whichcoreprovides. A new internalmathmodule supplies them through the pure-Rustlibmcrate (a new, non-optional dependency — Cargo cannot express "enable when featurestdis off";libmisno_stdand dependency-free, and its unused code is dropped by the linker instdbuilds) via aFloatExttrait whose signatures mirror the inherent methods exactly, so no call site changed andstdbuilds still use the inherent methods. Unit tests cross-check every shim against the inherent method over a sweep of arguments; agreement is to ~1 ulp, not bit-exact, since Rustlibmand the platform libm are different implementations (powiin particular is apowcall rather than LLVM's repeated squaring). Two caveats worth knowing: only the library build of--no-default-featuresexercises the shim —--all-targets(clippy/tests) puts the dev-dependencies and thereforestdback into the compilation, socargo check -p oxigeo-proj --no-default-featuresis the command that guards it; and a real bare-metal target (--target thumbv7em-none-eabihf) still fails to build, because the workspace-levelbyteorder = "1"keeps its defaultstdfeature — outside this crate to fix.oxigeo-proj: enablingproj-dbno longer changes the result of a coordinate transformation.transform::crs_to_oxiresolved aCrsSource::Epsgthroughoxiproj::Crs::from_epsg(oxiproj's bundled authority database) under that feature and through this crate's own PROJ-verified registry string otherwise, so the same CRS pair produced two different answers depending on the feature set. BecauseCrs::from_epsgitself goes throughlookup_epsg, that branch could only ever fire for codes the embedded registry already carried: it added no coverage, only a second, divergent definition. Two failure modes followed. Asymmetric pairs: a PROJ-string CRS transformed against an EPSG-sourced one combined a datum-bearing definition (+towgs84from the registry string) with a datum-less authority one, and the pipeline applied a one-sided datum shift — transforming from a code's own geodetic base to the code came out 87 m off forEPSG:2039, 226 m forEPSG:2056and 4.8e5 m forEPSG:2314, where PROJ 9.7.0 returns the projection alone (it composes both sides' datum transformations for such a mixed pair). Divergent definitions: oxiproj 0.1.4's authority definitions disagree with PROJ even when both sides are EPSG-sourced —EPSG:2314/EPSG:24382state the ellipsoid's semi-major axis in the CRS's own linear unit (+a=20926348, Clarke's feet) while still saying+units=m,EPSG:6933emits+lat_1instead of+lat_ts(a 2.5e6 m error), andEPSG:2062/EPSG:5469/EPSG:24382fail to build a transformer at all. Under--features proj-dbthis failed all four transform tests ofepsg_verified_registry_extended_test— 560 projection mismatches in each direction, 77 end-to-end mismatches in each direction, 3 transformer-construction failures — against fixtures that pass under default features. Every CRS is now resolved throughCrs::to_proj_string()→oxiproj::Crs::from_projin all configurations;oxiproj::Crs::from_epsgis kept only as a fallback for an EPSG code the embedded registry does not carry (reachable viaDeserialize), soproj-dbstays strictly additive — it widens coverage without moving a number the default build already produces. Default-feature behaviour is unchanged, and theproj-dbrun of that test binary also got ~10x faster. New unconditional regression tests intests/transform_test.rspin theEPSG:2039/EPSG:2056base↔code pivots to their PROJ values in both directions, plus the fallback's presence (proj-db) and absence (default). The upstream oxiproj defects are reported separately.oxigeo-proj: corrected the linear unit of the NAD83 State Plane EPSG registry entries (legacy zones 2222-2289/2195-2204/32164-32166 plus the NAD83(2011) zones 6355-6419) and assorted other regional CRSs whose native unit is US survey feet or international feet. The registered PROJ strings declared+units=mfor zones EPSG itself defines in feet — e.g.EPSG:2222"NAD83 / Arizona East" — so every coordinate run throughoxigeo-proj's embedded registry for these codes was off by the metre/foot conversion factor (~3.28× forus-ft); false-easting/northing and standard-parallel/central-meridian constants are now the exact EPSG values (x_0=2000000.0001016 us-ft, the precise definition for the affected zones, rather than the previousx_0=2000000 m) with tightened decimal precision throughout. Separately, the reported EPSG unit name was hardcoded to"metre"at every projected-CRS registration site regardless of which PROJ string it went with — silently mislabelling 79 US-survey-foot/international-foot CRSs (the whole State Plane(ft)/(ftUS)family) plus two entries expressed via+to_meter=(Indian yard, previously reported as"metre"; Clarke's foot, previously"link"). A newepsg_unit_forhelper now derives the reported unit from each entry's own+units=/+to_meter=token instead of a hardcoded default.tests/epsg_verified_registry_extended_test.rsgained PROJ-verified fixtures over the corrected zones.oxigeo-proj:Transformer::transform_batch's SIMD fast path (Transverse Mercator/UTM, Mercator and Lambert Conformal Conic forward projection) no longer silently mis-projects. Previously the fast path activated whenever the target CRS's+proj=matched a supported kernel, without examining the source CRS or checking whether the kernel could faithfully reproduce the scalar OxiProj pipeline; a newfast_path_applicablegate now declines — falling back to the scalar per-point path — whenever either CRS has a non-Greenwich prime meridian, a non-ENU axis order, a real (non-@null)+nadgrids, or a named datum other than a null-shift one, or wheneverparse_ellipsoidcannot recognise either CRS's ellipsoid. Three concrete bugs this closes: (1) an unrecognised+ellps(e.g.+ellps=clrk66) was silently projected on WGS-84 — measured up to 2.1e5 m off; (2) the generic Transverse Mercator kernel ignored+lat_0, so any CRS whose origin isn't the equator — e.g. the Japan Plane Rectangular CS,EPSG:6669-6687/2443-2461,+lat_0=26..44— came out offset by the meridional arc to that latitude (≈3,985,144 m for+lat_0=36); (3) the Mercator kernel ignored+x_0/+y_0(false easting/northing) entirely and always usedk=1even when+lat_tsshould derive the scale factor, and the shared output step now converts through the target CRS's actual linear unit instead of assuming metres. The dispatch logic also moved out oftransform/mod.rsinto a new internaltransform::simd_dispatchmodule; no public API changed. Newtests/epsg_verified_registry_test.rsandtests/simd_batch_params_test.rspin the fast path against both the scalar path and PROJ-verified fixtures for all four kernel families.oxigeo-proj: the embedded EPSG registry's JGD2011 Japan entries are corrected. Japan Plane Rectangular CS zones I–X (EPSG:6669–6678) were misregistered as "JGD2011 / UTM zone 51N–60N" — a whole-family misassignment that placed Plane Rectangular data ~4,000 km east into the Pacific — and zones XI–XIX (EPSG:6679–6687) were absent entirely. All nineteen zones are now registered from a verified per-zone table with each zone's truelat_0/lon_0origin, and the JGD2011 UTM zones 51N–55N live at their real codesEPSG:6688–6692— the codes the Plane Rectangular family used to squat on.oxigeo-gpkg: table B-tree cells whose payload spills onto SQLite overflow pages are read correctly (issue #17). Two defects combined: the local (on-page) payload size was computed asmin(P, U − 35), but SQLite stores onlyK(orM) bytes locally when a cell overflows — 489 bytes at a 4096-byte page size, not 4061 — and the overflow-page chain was never followed at all, so asqlite_masterrow wider than one page (the reporter's ~5000-character QGIS layer name) failedGeoPackage::from_bytes+load_contents()with "overflow cell needs 4061 bytes inline … but only 3209 available". The reader now computes SQLite's local-payload split against the true usable page size (page_size − reserved_bytes, see Changed) and reassembles the full payload across the chain. Regression tests intests/issue_17_overflow_pages.rs.oxigeo-vrt:SrcRect/DstRectwindows parse the numeric formats GDAL actually writes (issue #18).gdalbuildvrt/gdalwarp -of VRTkeep source and destination windows as doubles and round them only at rasterisation time, so real-world VRTs carry sub-pixel values such asxOff="9783.50000000003"— and parsing those attributes withstr::parse::<u64>rejected every GDAL-produced mosaic with "Invalid u64: invalid digit found in string". The attributes are now parsed asf64and rounded where GDAL rounds. (<WarpMemoryLimit>6.71089e+07</WarpMemoryLimit>was also suspected; it has parsed asf64since 0.2.3, and a scientific-notation test now pins that so the two offenders cannot be conflated.) Regression tests intests/issue_18_gdal_numeric_formats.rs.oxigeo-vrt: mosaic compositing of overlappingComplexSources honours each source's<NODATA>(issue #19). GDAL applies sources in document order and skips any source pixel equal to that source's nodata value — the pixel neither overwrites what is already there nor claims coverage, so a later overlapping source can still supply valid data. Previously the first source to cover a pixel won even when all it had there was nodata, punching holes along the overlap bands of everygdalbuildvrtmosaic. The comparison is made on the decoded sample value for the band's data type, not on raw bytes, so float nodata (including the NaN convention) compares correctly.
0.2.3 - 2026-08-05
Issues #15 and #16. GitHub 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 understood mosaics and pixel-function VRTs but
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, and there was no public API to read
a layer's features regardless of format. Both are now implemented for real —
4 new files in oxigeo-vrt (1,635 lines: warp.rs, warped.rs, srs.rs,
source_dataset.rs) and 2 new files in oxigeo (1,405 lines: layer.rs,
gpkg_schema.rs) — alongside issue #14
("how do I read a GeoTIFF into ndarray::Array2"), which needed no code
change: the readers it asked for (read_band_into, read_window_into,
read_interleaved(_into), read_window_interleaved(_into)) already shipped
in 0.2.2.
Warped VRT support (oxigeo-vrt, #15)
- New
warpmodule:WarpOptions(the parsed<GDALWarpOptions>block),WarpResampleAlg—is_kernel_exact()reports which resample algorithms the engine implements exactly rather than approximates, see the known-limitation note under Fixed below —WarpKernel,WarpBandMapping,InitDest,ReprojectionTransformer,GenImgProjTransformer. - New
srsmodule:resolve_crs, a WKT/PROJ4/EPSG:nCRS-string resolver. - New
source_datasetmodule:SourceDataset, which dispatches a warp's source to a GeoTIFF leaf reader or recurses into a nested VRT (MAX_VRT_NESTING = 16, so a VRT that references itself fails cleanly instead of exhausting the stack). VrtDataset::with_warp_options/is_warped; a newVrtError::EmptyWindowvariant (VrtError::empty_window) that distinguishes "no source covers this window" — legitimate on a warp over a sparse mosaic, GDAL'sERROR_OUT_IF_EMPTY_SOURCE_WINDOW=FALSEbehavior — from a real structural error, so a routine mosaic gap can't also mask genuine failures.oxigeo-vrtgained a new dependency onoxigeo-projto perform the reprojection. Pure Rust:oxigeo-proj's default feature set excludes theoxiproj-db/tokio EPSG-database path, so this does not pull SQLite into a defaultoxigeo-vrtbuild.
Vector layers (oxigeo, #16)
Dataset::layers() -> Result<Vec<Layer>>,Dataset::layer(index),Dataset::layer_by_name(name),Dataset::layer_names();Layer::features() -> Result<LayerFeatures>(eager). Newoxigeo::{Layer, LayerFeatures}, andoxigeo::{Feature, FieldValue, Geometry}re-exported fromoxigeo-core::vectorso reading features needs no directoxigeo-coredependency.- New
crates/oxigeo/src/layer.rs(thelayers()dispatch plus the Shapefile/GeoJSON/GeoPackage readers) andcrates/oxigeo/src/gpkg_schema.rs(aCREATE TABLEcolumn/constraint parser shared by the new layer reader and the existing streaming GeoPackage path, so a schema fix lands in both at once).
- Dependency bump:
scirs2-core0.6.4 → 0.6.5,oxicode0.2.4 → 0.2.5 — routine latest-crates-on-crates.io maintenance.oxicode0.2.5 is a hardening release (DoS/panic/overflow rejections added to its decode paths); neither bump changes any OxiGeo-visible API or behavior.
oxigeo-vrt (#15)
- Every Warped VRT was rejected at parse time. A
VRTWarpedRasterBandlegitimately carries no<SimpleSource>/<ComplexSource>/pixel function — its pixels come entirely from the sibling<GDALWarpOptions>block — butVrtDataset::validateapplied the same "Band must have at least one source or a pixel function" rule regular VRTs need, rejecting every warped VRT GDAL has ever written. The rule is now relaxed exactly when a validated<GDALWarpOptions>block is present (VrtDataset::is_warped); aVRTWarpedDatasetthat carries thesubClassmarker but no warp block is still rejected, since it then has no source for any pixel. - Depth-aware
AUTHORITY/IDresolution in WKT CRS strings. The previous scan returned the firstAUTHORITY[...]/ID[...]node found anywhere in a WKT tree. In aGEOGCS, that is the node nested insideSPHEROID(e.g.AUTHORITY["EPSG","7030"], the ellipsoid's own code), which precedes the CRS's own root-level code (e.g.AUTHORITY["EPSG","4326"]) in the string. A source WKT naming EPSG:4326 was silently resolved as EPSG:7030 — the wrong CRS, and one close enough in practice to distort output rather than fail loudly.srs::resolve_crsnow tracks bracket depth and reads only the direct-childAUTHORITY/IDof the root node. relativeToVRTwas discarded on both read and write. Parsing a<SourceFilename relativeToVRT="1">silently dropped the attribute (every path was treated as absolute), and writing one never emitted it either — so OxiGeo could not read back a VRT written by its ownoxigeo buildvrtwherever that VRT used relative source paths. Both directions now round-trip the attribute.- quick-xml 0.41 entity-reference events were dropped, corrupting escaped
text. quick-xml reports
"/&/"as their ownEvent::GeneralRef, separate from the surroundingEvent::Text; those events fell through the XML parser's catch-all arm and vanished. A<SRS>block written by this crate's ownVrtXmlWriter(which escapes the quotes in a WKT tree) read back with every"missing, and any path containing&silently lost it. VrtReader::read_window's band-to-index conversion was an uncheckedband - 1, an integer-underflow panic waiting for a caller that passed band0; nowchecked_subwith a typedVrtError::band_out_of_rangeon failure.- The
oxigeofacade opened.vrtfiles with a zero-filledDatasetInfo.Dataset::openrouted every VRT through the generic fallback arm ofopen_raster:width()/height()/band_count()all read back0andgeotransform()read backNone, for a file that states all of them in its own header.raster_read'sread_band/read_window/read_interleaved(and their_intoforms) were also hardwired to the GeoTIFF path only.Dataset::opennow parses the VRT header for real metadata via a newextract_vrt_info, and every raster read method dispatches to the VRT reader — including through nested warps and mosaics — whenever the opened dataset is a VRT. - Known limitation, stated rather than hidden:
WarpResampleAlg::is_kernel_exact()istrueonly forNearestNeighbourandBilinear. Cubic, CubicSpline, Lanczos, Average, and Mode all parse correctly and select their named kernel, but the warp engine currently resamples every one of them bilinearly rather than with the kernel it selected.
oxigeo GeoPackage / vector layers (#16)
Dataset::open("x.gpkg")always reported 0 layers. The facade'sopen_vectorhad no GeoPackage arm at all, so every.gpkgfell through to an emptyDatasetInfo::default().open_vectornow calls the newextract_gpkg_infounder the (non-default)gpkgfeature.fidread backNULLon every GeoPackage feature. SQLite stores anINTEGER PRIMARY KEYcolumn asNULLin the row's record payload and keeps the real value only in the row's own 64-bitrowid; naively reading the stored cell therefore always produced a nullfid.gpkg_schemanow detects anINTEGER PRIMARY KEYcolumn at schema-parse time (rowid_alias) and substitutes the row'srowidfor it whenever the stored cell isNULL.- Named table-level constraints were parsed as columns. A
CREATE TABLEbody item such asCONSTRAINT pk_geom_cols PRIMARY KEY (table_name, column_name)was split on its top-level commas exactly like a real column list, producing bogus extra "columns".is_table_constraintnow recognizesPRIMARY KEY/UNIQUE/CHECK/FOREIGN KEY/CONSTRAINT-led body items and skips them. - Known limitation, stated rather than hidden:
layers()covers GeoPackage (featuregpkg, not on by default), Shapefile, and GeoJSON. FlatGeobuf and GeoParquet returnOxiGeoError::NotSupportednaming the unsupported driver; both remain reachable only through the streaming feature API.
0.2.2 - 2026-07-30
Issue #14 fix campaign. GitHub issue #14
reported that Dataset::read_band silently ignored its band argument on
multi-band rasters, returning the whole pixel-interleaved image instead of the
requested band. Root-causing it traced back to oxigeo-drivers/geotiff's
block-decode engine (rewritten from scratch as band_read.rs/band_read/multi.rs),
then surfaced the identical defect pattern — assuming chunky
(PlanarConfiguration=1) interleaving, or the wrong byte order, wherever
multi-band raster data was read — independently re-implemented in a dozen other
crates, plus a handful of unrelated bugs found along the way. 192 files changed;
33 new issue_14_*-named files (30 regression tests, 2 benchmarks, 1 example) plus
dedicated cases embedded in the Node/ML/Jupyter/CLI suites guard against
regressions.
-
BREAKING —
oxigeo::Dataset::read_bandnow returns one band. Up to 0.2.1 it ignored itsbandargument on multi-band rasters and returned the whole pixel-interleaved image (width × height × bandssamples,b0 b1 b2 b0 b1 b2 …), which silently mis-fed every caller that asked for a single band. It now returns exactly that band'swidth × heightsamples. Single-band rasters are unaffected; on a 3-band fileread_band(0)returns a third as many samples as it used to, so a length check finds affected code quickly. -
BREAKING —
DatasetInfois now#[non_exhaustive]. It also gainedimpl Defaultand a newdata_type: Option<RasterDataType>field (the on-disk pixel type, readable before any raster read via the newDataset::data_type()). Downstream struct-literal construction — evenDatasetInfo { field, .. }— no longer compiles; build fromDatasetInfo::default()instead. -
DEFLATE tile decoding is substantially faster. The
oxiarc-*suite moves 0.3.6 → 0.4.0, which rewrites the DEFLATE/zlib decoder (two-level Huffman root+sub-tables, a buffered bit reader with a register-resident accumulator, and an LZ77 history that is the output buffer instead of a ring buffer written twice), and the GeoTIFF driver now uses its new decompress-into-slice entry point. Measured on 256×256 UInt16 DEM tiles withPREDICTOR=2— the layout used by SRTM/Copernicus DEM COGs — decode throughput goes from 99.0 MiB/s on oxiarc-deflate 0.3.6 to 143.7 MiB/s on 0.4.0 (1.45×), and to 177.4 MiB/s (1.79×) throughzlib_decompress_into, which is the path a whole-band read now takes. Whole-band DEFLATE reads additionally perform zero decode-side allocations: one caller-owned scratch buffer serves every tile, where each tile previously grew its ownVecby repeated doubling. Output bytes are unchanged; the decoded-size hint is an optimisation only, and a wrong, absent, or clamped hint falls back to the growable path rather than failing (#14).
-
oxigeo::Datasetinterleaved (multi-band) readers — the supported replacement for the pre-0.2.2read_bandbehaviour, so the breaking change above leaves no gap:read_interleaved(bands) -> Vec<T>andread_interleaved_into(bands, dst)read_window_interleaved(bands, col, row, w, h) -> Vec<T>andread_window_interleaved_into(bands, col, row, w, h, dst)
bandsisOption<&[u32]>:Nonemeans every band in file order (mirroring GDAL'spanBandMap == nullptr), and a slice selects, reorders (&[2,1,0]reads RGB as BGR), subsets (only the named bands are decoded), or repeats band indices. The element type is converted from the file's type while the blocks are decoded, exactly asread_band_intodoes. The*_intoforms allocate a single scratch buffer sized to one horizontal strip of one band — not to the raster — so peak extra memory stays bounded however large the image is; a single-band selection delegates to theread_band_intopath and allocates nothing at all. All four honourDataset::clip's pixel window like every other reader. -
oxigeo::Datasetgained a pre-read type query and zero-allocation single-band/window readers:data_type() -> Option<RasterDataType>reads the on-disk pixel type from the header before any raster read;read_band_into<T: RasterElement>(band, dst)andread_window_into<T>(band, col, row, w, h, dst)decode straight into a caller-owned buffer (the interleaved readers above already build on this same path).RasterElement— seeoxigeo-corebelow — is re-exported at the crate root. -
oxigeo-coregained a typed, zero-copy raster-element layer. The sealedRasterElementtrait (implemented foru8/i8/u16/i16/u32/i32/u64/i64/f32/f64;Copy + Default + Send + Sync + 'static) defines each type's on-disk byte width,RasterDataTypetag, and native-endian byte conversion, plus exact — never lossy throughf64— integer-to-integer conversion via ani128bridge. Built on it:convert_raw_into/convert_raw_into_with/convert_raw_bytes/elements_as_bytes, andRasterBuffer::from_element_slice/copy_to_slice[_with]/to_typed_vec[_with].DataSource/AsyncDataSourcegainedread_range_into/range_slicemethods (default: still allocates internally);FileDataSourcenow issues real positional reads (pread/seek_read) instead of serializing every read through oneMutex<File>, andMmapDataSource/MmapDataSourceRwoverride both for true zero-copy reads straight out of the mapping. -
oxigeo-drivers/geotiffgained a real band-aware, low-allocation read API:band_byte_len/band_pixel_count,read_band_into/read_band_into_typed,read_window/read_window_into/read_window_into_typed,read_bands_into_typed/read_window_bands_into_typed(one block decode shared across every requested band),byte_order(),level_size(level)(exact per-overview dimensions from that level's own IFD, notfull_size / 2^level),read_tile_band_buffer(read_tile_bufferis now itsband = 0shorthand),CogReader::tile_decoded_size/read_tile_into, andcompression::decompress_into/decompress_into_partial. New opt-inparallelfeature fans block decode out across rayon workers (bit-identical to serial).
GeoTIFF driver — the issue #14 root cause (oxigeo-drivers/geotiff)
GeoTiffReader::read_bandnever read itsbandparameter (it was named_band). It sized its output as the whole image (width × height × bytes_per_sample × samples_per_pixel) and copied every decoded tile's raw chunky (PlanarConfiguration=1) bytes into it 1:1 — so every band index returned identical, full-image bytes, and aPlanarConfiguration=2(planar) file was decoded as if it were chunky, scrambling every band. Overview levels (level > 0) additionally walked the primary image's tile grid unconditionally. Replaced by a purpose-built engine (band_read.rs,band_read/multi.rs): aReadPlan/LevelGeometryresolves each level's real geometry and planar config once, anddecode_blockeither de-interleaves the requested band during the scatter (chunky) or reads only that band's own blocks (planar) — the interleaved plane is never materialized, and the band index is validated. Output is now exactly one band'swidth × height × bytes_per_samplebytes.- The TIFF predictor (horizontal-differencing) undo used the wrong stride on
planar files.
CogReader::read_tilealways passedsamples_per_pixelas the predictor stride, but a planar block holds one band, so the correct stride is 1; the wrong stride "subtracts the wrong neighbour from every sample… rows bleed into each other. Nothing errors; the pixels are simply wrong." Fixed via a new per-blockblock_samples_per_pixel(1 when planar). Separately,Compression::Lerccombined with anyPredictoris undefined by spec — no real encoder produces it — but the old driver reversed the predictor over already-decoded LERC floats anyway, corrupting every sample after the first; this combination is now a hard error instead of silent garbage. - Per-tile reads re-parsed the entire
TileOffsets/TileByteCountsarray on every single lookup — measured at 77% (190 of 248 ms) of one band read on an 8000-strip file. A newBlockIndex(cog/block_index.rs) parses each level's offset/count arrays once atopen()for O(1) lookups thereafter, bounded against hostile headers. CogConverter::convert(GeoTIFF→COG) depended on the bug above — it called the oldread_band(0, 0)specifically because it returned the whole interleaved image, and reassembled that into its output. Fixingread_bandin isolation would have silently truncated every multi-band conversion to one band; the converter now reads and re-interleaves each band explicitly.tiff/ifd.rs: several direct-slice-index panics on truncated/malformed IFDs are now typed errors.lerc_codec:serialize_nativeused a hardcodedto_le_bytes(), silently byte-reversing output on big-endian hosts; nowto_ne_bytes().
oxigeo-core foundation
RasterBuffer::convert_tosilently corrupted largeUInt64/Int64values. Its per-pixel path round-tripped every sample throughget_pixel/set_pixel, which decoded/encoded viaf64— exact only to 2^53 — so e.g.(1u64 << 53) + 1silently became1u64 << 53on conversion. Fixed by routing through an exacti128bridge.- Latent undefined behavior in
RasterBuffer::as_slice/as_slice_mut/row_slice. They reinterpreted aVec<u8>'s pointer directly as*const Twithout checking alignment (Vec<u8>only guarantees 1-byte alignment), and ranfrom_raw_partson the zero-length dangling sentinel pointer for empty buffers — UB regardless of length wheneveralign_of::<T>() > 1. It never crashed in practice because production allocators over-align, which is exactly why it stayed latent. Now checks alignment explicitly and short-circuits on zero length. MmapDataSource/MmapDataSourceRw::read_rangecast au64byte offset tousizeunchecked, silently wrapping on 32-bit targets for offsets beyondu32::MAX; now a checkedusize::try_from.oxigeo::Dataset::open(and the standalone format probes) now report a real error instead of silently opening a GeoTIFF/GeoJSON/Shapefile/FlatGeobuf/ GeoParquet file with zeroed-out metadata. The old hand-rolled TIFF header parser only looked at the first 8 KiB/1 MiB of the file; an IFD located past that window (as OxiGeo's own writer produces, which emits pixels before the IFD) silently opened as a 0×0, 0-band dataset instead of erroring. Reading now delegates tooxigeo_geotiff::GeoTiffReaderdirectly, and every format probe changed fromOption(silent empty result) toResult. GeoJSON'sfeature_countsimilarly stopped under-counting large documents past its 64 KiB peek window — it now reportsNoneinstead of a wrong number. BREAKING: a file that previously opened "successfully" with empty/zeroed metadata now returnsErr.
The same defect pattern, independently found and fixed workspace-wide (#14)
- oxigeo-qc: the nodata and radiometric scanners hardcoded little-endian byte
decoding regardless of the file's actual byte order, misclassifying nodata on
big-endian GeoTIFFs; a separate bug assumed chunky layout unconditionally, so
planar files were scanned one plane at a time, attributed to the wrong band, and
reported clean. A new
band_scanmodule centralizes correct band-aware scanning. - oxigeo-server: the WMS/WMTS/XYZ tile handlers assumed power-of-two overview
pyramids (
level = 1 << n); a non-power-of-two chain served the wrong resolution for the requested zoom — a georeferencing error in served imagery — and multi-band pixel windows silently returned all-zero data. An RGB composite's red channel was also read through a different, disagreeing code path than green/blue; all three now share one correctedread_level_bandhelper. - oxigeo-services (WCS):
GetCoverageon a multi-band raster wrote only one band's samples into a buffer sized for all of them, silently truncating/ zero-padding the response. - oxigeo-mobile: tile reads on a planar-layout file interleaved the wrong
bytes into RGBA output; windowed region reads (
oxigeo_dataset_read_region) silently left the output buffer untouched instead of erroring or filling it, once the driver-level bug above was fixed out from under it; overview-level statistics had an off-by-one level and assumed power-of-two dimensions. - oxigeo-wasm: the in-browser COG viewer had two tile-decoding paths that disagreed about whether a tile's byte order still needed swapping, double- or never-swapping depending on which one served the request.
- oxigeo-node:
Dataset::openhand-rolled a de-interleave step assuming the old brokenread_band— every multi-band open hard-failed withFORMAT_ERROR. - oxigeo-cli:
read_band's workaround code (tolerating either a single-band or a whole-interleaved-image result) andread_band_region's ~200-line hand-rolled tile-stitcher/de-interleaver (which, as its own removed comment noted, "could not readPlanarConfiguration = 2files correctly") are both replaced by direct calls to the fixed driver API;profiler.rs's per-band benchmark used to only ever measure band 0. - oxigeo-ml-foundation:
GeoTiffDataset::load_all_bandsmanually de-interleaved aread_band(0, 0)result under the old semantics; onceread_bandwas fixed, that workaround itself became actively wrong and is now removed. - oxigeo-jupyter: the
%statsmagic double-de-interleaved an already single-band buffer for the same reason. - oxigeo-drivers-vrt: pixel accessors used
from_le_bytes/to_le_byteson buffers that are already host-native (fromGeoTiffReader), corrupting values on big-endian hosts.
Unrelated bugs found along the way
- oxigeo-streaming:
ChunkedReader::read_chunkfailed on the first call on every stream (an empty, not-yet-filled buffer was treated as a hard error), and read-ahead prefetch desynced its cursor against any directly-read chunk, failing every subsequent prefetch push. - oxigeo-mbtiles:
MBTilesReader::open_in_memorydeleted only the primary SQLite spill file on cleanup, leaking its-wal/-shm/-journalsiblings into the OS temp directory on every call. - oxigeo-ml:
optimization::iterative_pruningwrote intermediates to fixed, non-unique file paths; concurrent calls could overwrite and corrupt each other's in-progress models. Each call now uses an isolated, auto-cleaned scratch directory. - oxigeo-compress:
LZ4_MAX_OUTPUT_GUESSwas a raw4 * 1024 * 1024 * 1024usizeliteral, which overflowsusizeon 32-bit targets (wasm32, 32-bit ARM/x86); now computed inu64with a saturating cast. - oxigeo-netcdf:
Variable::new/new_coordinate's empty-name error was misclassified as a genericCoreerror instead ofVariableError. oxigeo-compressnow builds forwasm32-unknown-unknown. It failed to compile at all on that target:ahash's defaultruntime-rngfeature pullsgetrandom, which hard-errors on wasm32 unless its JavaScript backend is explicitly opted into (The "wasm_js" backend requires the wasm_js feature). wasm builds of this crate now use ahash'scompile-time-rnginstead, so the hash keys are generated on the host at build time and no JavaScript host is required; every other target keepsruntime-rngand its per-process keys unchanged. The workspaceahashentry moved todefault-features = false(Cargo forbids a member from disabling a workspace dependency's defaults), sooxigeo-{compress,edge,gateway,streaming}now name["std", "runtime-rng"]explicitly — the same feature set as before on native targets. Onlyoxigeo-wasmis built for wasm32 in CI, so this was invisible there.
0.2.1 - 2026-07-28
Production-hardening campaign (2026-07): a workspace-wide, multi-agent defect
sweep across all 76 crates surfaced 342 confirmed defects
(47 critical / 84 high / 83 medium / 33 low). 314 were fixed across 38 crate
lanes (~520 files changed); the remaining 79 were honestly deferred, each left
with a safe typed-error path — a loud Unsupported* / NotImplemented /
DecodingError rather than silent or fabricated data. Quality gates all green:
cargo fmt --check clean; cargo clippy --workspace --all-features --all-targets
0 warnings; cargo nextest run --all-features 17,723 passed / 0 failed /
100 skipped (16,307 passed / 0 failed / 79 skipped on default features); 416
doc tests passing; cargo deny check passing. The categorized list of
deferrals carried to v0.3.0 is in TODO.md.
Format drivers
- oxigeo-jpeg2000: two CRITICAL correctness bugs fixed — multi-tile decode now
Psot-bounds each tile's bitstream and composites it at its real pixel offset (previously every tile silently returned tile 0), and the JP2 box parser now recurses intojp2hsoihdr/colrin spec-conformant.jp2files are read - oxigeo-geotiff: real planar-configuration (
PlanarConfiguration=2) decoding; authoritative EPSG projected/geographic classification; a working JPEG/WebP writer path; the silentGeoKeyDirectoryerror and a policy-violatingexpect()removed; ausize-overflow bug in header-driven allocation fixed - oxigeo (umbrella): fixed GitHub issue #12, "Metadata missing when reading
geotif" — the lightweight
extract_tiff_info()peek parser used byDataset::open()(distinct from the fulloxigeo-geotiffdriver above) only scanned a GeoTIFF's first 8 KiB, soModelPixelScaleTag/ModelTiepointTag/GeoKeyDirectoryTagvalues stored out-of-line past that offset — routine for striped TIFFs with many strips — were silently treated as absent andcrs()/geotransform()/bounds()all returnedNoneeven though the tags were present and well-formed; the peek buffer now extends up to a bounded 1 MiB when a georeferencing tag's value lands past the initial window, a Y-axis sign inversion in the derivedGeoTransformis fixed (ModelPixelScaleTag's Y scale is a positive magnitude per spec butGeoTransform::north_upexpects a negativepixel_height), andbounds()— previously hardcoded toNone— is now derived from the geotransform and raster dimensions; regression testtest_issue_12_far_offset_georeferencingadded - oxigeo-drivers/grib: CRITICAL DRT 5.40 silent-corruption bug fixed — the GRIB2
decoder now dispatches on the Data Representation Template number, so a
JPEG2000/PNG/CCSDS payload can never fall through to the simple-packing
bit-unpacker; DRT 5.40 is wired to a real Pure-Rust JPEG2000 decode via
oxigeo-jpeg2000(new default-onjpeg2000feature) - oxigeo-shapefile (vector drivers): the Polygon reader now reconstructs
multi-part polygons by ESRI ring winding (clockwise = exterior, CCW = hole) with
containment-based hole assignment, emitting
MultiPolygonfor multiple exteriors — a two-island country shapefile round-trips instead of merging its rings - oxigeo-drivers/netcdf & oxigeo-drivers/hdf5: NetCDF-4 reader now recurses
into HDF5 sub-groups (was silently dropping their variables); the HDF5 writer's
chunking/compression/fill-value hints are no longer silently dropped (real chunked
write path plus honest errors for shapes oxih5 cannot represent); real object-header
parsing so
decode_chunk/filter-pipeline/chunking are no longer dead code - oxigeo-drivers/netcdf & oxigeo-drivers/hdf5: attribute decoding now trusts
the dataspace-declared element count (
count × dtype_size) and ignores trailing bytes, so scalar/small numeric attributes written with padded payloads no longer decode as phantom extra elements — this silently disabled CF_FillValue/scale_factorhandling for files written by oxih5 0.2.1, whoseFileWriterpadded sub-8-byte scalar attribute payloads; the writer regression is now root-fixed upstream in oxih5 0.2.2 (this workspace is pinned to it), and the defensive trim stays in place as a belt-and-suspenders guard against older files written by 0.2.1 - oxigeo-drivers/geoparquet: XYZ/XYM geometry decode ambiguity fixed
Algorithms & CRS
- oxigeo (umbrella): CRITICAL
Dataset::clip()bug fixed — clip now records a pixel window that every raster read (read_band/bands/statistics/convert/read_window) crops the source file to, so a clipped dataset no longer silently reprocesses the full raster - oxigeo-algorithms: real NEON SIMD (with scalar-parity tests) for morphology (3×3 erode/dilate) and threshold kernels; a real CSE (let-binding hoisting) + DCE (liveness/reachability) pass for the raster-algebra optimizer
- oxigeo-proj: PROJ
+proj=hgridshift/+proj=vgridshiftpipeline steps now actually apply a grid — newGridRegistry+Pipeline::with_hgrid/with_vgridand evaluators calling the crate's NTv2 grid parser (a sign bug in it was fixed)
Server & OGC services
- oxigeo-server: the
/tiles/{layer}/{z}/{x}/{y}.{fmt}XYZ endpoint now renders real raster data — reads the intersecting source window, reprojects Web-Mercator tiles into the dataset's native CRS (per-pixel inverse warp for non-3857 data), applies the layer colormap/RGB style, and masks off-dataset/nodata pixels as transparent — replacing a hard-coded checkerboard - oxigeo-services: WPS
buffer/clip/unionnow perform real geometry math viaoxigeo-algorithmsand return the computed GeoJSON (previously ignored their inputs); CQL2 gained!=/<>,IN (...), andIS [NOT] NULL
Query engine
- oxigeo-query / oxigeo-index: JOIN output now preserves native column types
instead of stringifying everything; SELECT projection lists are actually applied;
HAVING is executed (including aggregates referenced only by HAVING); the WHERE
evaluator gained
BETWEEN/IN/CASE/CASTwith real type coercion
ML
- oxigeo-ml: model pruning/quantization no longer corrupts ONNX files — a real
ONNX protobuf walker (
optimization/onnx_weights.rs) applies genuine tensor transforms;ModelVersionOrdbug fixed - oxigeo-ml-foundation: the crate now compiles and trains — a genuine trainable
scirs2-neural backend (real forward/backward/optimizer step with explicit gradient
routing) replaces code that referenced removed
randAPIs and mismatched types
Cloud & DB connectors
- oxigeo-postgis:
Transaction::dropnow issues a real implicitROLLBACK(was a log-only message that leaked locks) with a double-take guard - oxigeo-db-connectors: MySQL/TimescaleDB SQL-injection surfaces closed via a new
crate::sqlidentifier-quoting/literal-escaping module plus parameter binding - oxigeo-cloud: CRITICAL rs3gw tokio nested-runtime panic fixed; byte-range reads, the prefetch I/O driver, OAuth2/SAS credential refresh (HttpBackend), and STAC fixes
- oxigeo-cloud-enhanced: fabricated Azure (Cost/Monitor/ML/Synapse) and GCP (Vertex
AI/Dataflow/Cost) clients replaced with real, bearer-token-authenticated REST clients
behind the existing
azure/gcpfeatures — Azure Cost Management queries/forecasts/ budgets/Advisor, Azure Monitor metrics/Log Analytics/alerts/diagnostic settings, Azure ML v2 control-plane compute/model/endpoint/job management, Synapse SQL/Spark pool (ARM) management and Spark job/pipeline submission (Livy); GCP Dataflow template launch with job status/list/metrics/cancel/drain, Vertex AI model/endpoint/training/batch-prediction (long-running-operation polling), and GCP Cost Management via BigQuery billing export plus Cloud Billing budgets/Recommender — every previously-fabricated success/ID/ empty-list is now a real call or an honest typedNotImplemented. True data-plane operations a control-plane REST client can't mint stayNotImplemented(Monitor metric/diagnostic ingestion, Cost alert/export, Synapseexecute_query, MLinvoke_endpoint, GCP cost forecast/export)
HA & infra
- oxigeo-ha: PITR, snapshot, backup, and DR were entirely fabricated (canned bytes,
always-pass tests) — replaced with real WAL + on-disk persistence and injectable
executors; a genuine Raft log-replication module (
failover/log_replication.rs) withAppendEntriesconsistency check, conflict truncation, and majority commit added - oxigeo-cluster (cluster-dist): leader heartbeats now travel over the transport to
followers (real
AppendEntries-style RPC + handler) so followers stop perpetually re-running elections; W-TinyLFU is now reachable and used by the multi-tier cache - oxigeo-kinesis / oxigeo-kafka / oxigeo-pubsub: fake/no-op broker paths replaced with real implementations and honest errors — Firehose transformation now actually happens; Kafka read-process-write exactly-once wired to real transactions
Bindings
- oxigeo-node: multi-band GeoTIFF save (BIP interleave round-trip); GeoJSON parser
handles every geometry type;
CancellationTokenwired into batch/parallel processors doing real chunked multi-threaded per-pixel work - oxigeo-jupyter:
%crs/%bounds/%statsnow read a real parsed GeoTIFF dataset instead of returning hard-coded"(example)"literals - oxigeo-python:
open_raster/create_rasterno longer silently discard thedriver/optionsarguments — a real remote/cloud data-source layer (remote.rs) wiresdriver="COG"and S3/HTTP options through tooxigeo-cloud
no_std & platform
- oxigeo-core / oxigeo-embedded: the no_std/embedded claim is now real
end-to-end — both crates genuinely cross-compile for bare-metal
thumbv7em-none-eabihf(Cortex-M4) andriscv32imac-unknown-none-elf(verified with actual--targetbuilds);parking_lot/crossbeamare std-gated;RealtimeSchedulerdeadline enforcement now actually fires - oxigeo-gpu / oxigeo-gpu-advanced:
reproject_gpu/execute_gpuno longer errorInvalidBufferat runtime — the output buffers now requestMAP_READusage (verified on Metal) - oxigeo-proj: the
no_std(--no-default-features) build was broken — the crate declared#![cfg_attr(not(feature = "std"), no_std)]but failed with 63 errors;extern crate allocis now unconditional and the alloc-prelude imports (String/Vec/Box/ToString/format!) pluscore::f64::constsreplacements were added across the crate, sono_stdgenuinely compiles and its tests pass
Release-verification pass
- oxigeo-cloud: a doctest in the multi-cloud abstraction example was missing a
#[cfg(feature = "s3")]guard, socargo test --docfailed to compile it under default (non-s3) features - oxigeo-drivers-advanced: the GeoPackage doctest in
src/lib.rshad the same bug —gpkg::GeoPackageused with no#[cfg(feature = "geopackage")]guard, because the doc prose wrongly calledgeopackage"enabled by default"; fixed with the guard, the prose, and afn example()/async fn example()in place offn main - 9
rustdoc::private_intra_doc_linksviolations fixed across 8 files in 7 crates —oxigeo-index,oxigeo-gateway,oxigeo-security(×2 files),oxigeo-drivers/hdf5,oxigeo-gpu,oxigeo-ml-foundation,oxigeo-postgis - Publish-order bug:
oxigeo-grib(its default-onjpeg2000feature depends onoxigeo-jpeg2000) was sequenced beforeoxigeo-jpeg2000in both~/work/pub_oxigeo.shandscripts/publish-order.txt— publishing in that order would have failed with an unresolved dependency; both are now correctly ordered - 3 crates were missing
repositorymetadata:oxigeo-geojson-stream,oxigeo-index,oxigeo-noalloc - oxigeo-node: npm
optionalDependencieswere still pinned to0.2.0while the package itself is0.2.1 - Two hardcoded version strings in HTTP
User-Agentheaders (oxigeo-stac,oxigeo-ml) replaced withenv!("CARGO_PKG_VERSION")so they can no longer drift from the crate version
- oxigeo-drivers/zarr: the empty Zarr v2 reader/writer stubs replaced with a working v2 read/write path (chunk-key builder, compressor+filter pipeline, fill values, dimension separator, dtype sizing); the ZEP-0002 v3 sharding codec; the fake ZFP codec made honest (mode-honoring, overflow-checked)
- oxigeo-drivers/geoparquet: the writer now emits real attribute columns and a
covering.bboxcolumn (was silently dropping all attributes); extended-WKB nested geometry encoding; Hive-style + spatial (bbox-grid/quadtree/Z-order) partitioning - oxigeo-geotiff: real LERC decode (BitStuffer2 v1/v2/v3) and a JPEG-in-TIFF read
path that auto-merges shared
JPEGTables(tag 347) - oxigeo-proj: native forward/inverse projections + round-trip tests for Equidistant Conic, Sinusoidal, Mollweide, Robinson, Eckert IV/VI, Cassini-Soldner, and Gauss-Krüger (extended zones)
- oxigeo-drivers/grib: template-based product-definition expansion (PDT 0.0–0.48
coverage) and NetCDF CF-conventions v1.11 parsing (
cf_conventions/v1_11.rs) - oxigeo-gpu: reprojection, raster-algebra, and hillshade WGSL compute shaders;
multi-GPU workload distribution; WebGPU/WASM shader compilation via a compile-time
ShaderRegistry - oxigeo-ml: ONNX model hot-reload (file-watch + atomic swap), content-addressed inference caching (SHA-256 key + LRU), adaptive batch sizing, and model versioning / deterministic A/B testing
- oxigeo / oxigeo-streaming:
DatasetOpenBuilder/DatasetCreateBuilderfluent builders; aFeatureStream/TileStreamstreaming-iterator API - oxigeo-mbtiles / oxigeo-gpkg / oxigeo-pmtiles: a real SQLite-backed MBTiles
writer (now genuinely persists to
.mbtiles); an opt-in R-tree spatial-index writer for GeoPackage - fuzz/: 7 new libFuzzer targets (NetCDF, HDF5 superblock/object-headers, VRT XML, GeoJSON, and more), bringing coverage to 11 format/parser targets
- tests/: the 1,337-line mock re-implementation in
vector_advanced.rsreplaced — 33 tests now exercise the realoxigeo-algorithmsvector stack - oxigeo-gateway serving layer: the previously stubbed
Gateway::serve()(it accepted TCP connections and itshandle_connectiondid nothing) is now a real axum 0.8 HTTP service — a newGatewayServer/GatewayServerBuilderwires the crate's already-implemented components into a running router:- routes:
GET /health,GET /gateway/metrics,POST /graphql(plus a GraphiQL page when introspection is enabled and a/graphql/wssubscription endpoint whenenable_subscriptionsis set — that flag is now actually enforced), aGET /wsWebSocket upgrade (WebSocketManager wiring, defaultEchoHandlerroute, per-user connection caps, ping keepalive, gated onenable_websocket), and a load-balanced reverse-proxy fallback - reverse proxy: a streaming hyper 1 connection client, HTTPS upstreams over the
Pure-Rust OxiTLS (rustls/RustCrypto) probe connector, hop-by-hop header stripping,
FailoverManagerretries that finally honor the previously-ignoredLoadBalancerConfig.retry_attempts, circuit-breaker outcome reporting, and per-attempt request timeouts - pipeline: query-free trace spans (no query strings), API version negotiation +
deprecation headers, the in-house middleware chain (CORS with real
OPTIONSpreflight, compression, response caching, logging, metrics), JWT/API-key/session auth viaMultiAuthenticator(authenticate-if-present plus arequire_authmode, with therequire_mfaflag now enforced), atomic rate limiting withX-RateLimit-*/Retry-Afterheaders, request timeout and body-size limits; arequire_permissionRBAC guard is available for route groups andGatewayErrornow implementsIntoResponse - honesty fixes:
CachingMiddlewareis now a real LRU+TTL cache instead of a no-op stub; compression performs realAccept-Encodingnegotiation; the 1,865-linemiddleware::advancedmodule (request-ID / enhanced-logging / timeout-header / error-handling / histogram-metrics / cache-control) was orphaned — never declared or compiled — and is now wired in, compiling and tested;X-Forwarded-Foris built against a trusted-proxy allowlist (with_trusted_proxies) rather than blindly trusting client-supplied values - honest limitations (v0.3.0+): GraphQL resolvers still serve demo/in-memory data (no
storage backend); middleware-chain hops and proxied requests are buffered (bounded by
max_body_size) while proxy responses stream; there is no WebSocket pass-through proxying, no upstream keep-alive pooling, and response-side transformation is not yet wired (request-side only) - the crate's own test suite grew from 266 to 381 tests (1 → 3 doctests)
- routes:
- oxigeo-services: WFS-T CQL filtering now fails closed on unparseable CQL — an unparseable filter previously failed open, matching every feature and enabling a mass delete/update; it now rejects the request
- Memory-safety (DoS/OOM hardening): header-driven allocation caps added to the
NetCDF, HDF5, GRIB, and GeoTIFF parsers so a crafted header can no longer trigger a
multi-gigabyte allocation; includes the GeoTIFF
usize-overflow fix noted above - oxigeo-gateway: load-balancer health checks now issue genuine HTTP/1.1-over-TCP
requests (real Pure-Rust TLS via the OxiTLS RustCrypto provider for HTTPS) instead of
always returning healthy, so a down backend is correctly marked unhealthy; the
MalwareScannernow actually reads and inspects its input; the gRPC health check fails closed with an honest error rather than reporting unknown backends healthy - oxigeo-observability: health checks do real work (sysinfo disk usage, injectable
connectivity checker) instead of returning hard-coded
Healthy; a stubLabelMatchalert condition that always returnedtruefixed
- oxigeo-db-connectors: default features made Pure-Rust — the C-FFI database backends are now strictly opt-in behind named features
- oxigeo-query:
tokiomoved to dev-dependencies andrayongated behind a default-onparallelfeature, so the SQL engine is consumable fromwasm32-unknown-unknown - Packaging & legal: added
NOTICEandTHIRD_PARTY.md(Apache-2.0 §4(d) attribution + generated third-party license inventory), a committeddeny.toml(advisories + bans + licenses) wired intocargo deny check, an in-repo 75-crate topological publish-order manifest (previously only in an external script), a license note for the vendoredpathfinder_simd, and[package.metadata.docs.rs]fixes on the C-FFI-gated crates - Supply-chain hygiene:
.cargo/audit.toml's advisory allowlist re-verified against the current lockfile and pruned from 21 to 15 entries —aws-lc-sys(RUSTSEC-2026-0044/-0048) andtokio-postgres/postgres-protocol(RUSTSEC-2026-0178/-0179/-0180) are already patched at our pinned versions, andproc-macro-error2(RUSTSEC-2026-0173) is no longer in the dependency graph; the newdeny.toml[bans]list enforces this workspace-wide, andtower-http'scompression-br/compression-gzip/compression-deflatefeatures (unused — noCompressionLayeranywhere — but pulling bannedflate2/brotli/miniz_oxideoutsidedeny.toml's allowed wrapper scoping) are now explicitly excluded in every consumer;SECURITY.md's contact address corrected tosecurity@cooljapan.tech - Dependencies kept current per the Latest Crates Policy (
arrow58 → 59,indicatif0.18 dropping the unmaintainednumber_prefix,oxih5/oxih5-core/oxinetcdf0.2.0 → 0.2.2,scirs2-coreand thescirs2-{neural,autograd,optimize,datasets, metrics,linalg,vision,series}family 0.6.1 → 0.6.4) - A further round of Latest Crates Policy bumps:
base640.22 → 0.23,pollster0.4 → 1.0,las0.9 → 0.10,jsonwebtoken10 → 11,ed25519-dalek2 → 3 (stdfeature dropped,zeroizeretained),azure_core1.0 → 1.1,google-cloud-pubsub1.1 → 1.2,statrs0.18 → 0.19,tokio-tungstenite0.29 → 0.30. Onlylas0.10 required a source change: it replaced the per-pointReader::points()streaming iterator with a batch/buffer API (Reader::read_all()/read_points(n)returning aPointDataslab whose.points()yields the same row-oriented iterator), sooxigeo-3d'sLasReader::read_all/read_nwere updated accordingly; the other eight bumps were drop-in with no source changes required - Dependency hygiene: genuinely-unused dependencies removed from 66 crates'
Cargo.tomlfiles (found viacargo-machete, each removal build-verified);deny.toml's advisory-ignore list pruned from 15 to 7 entries (the other 8 IDs no longer match anything in the currentCargo.lock) and its license allowlist trimmed of entries no longer reachable in the dependency graph; awildcard-dependencycargo-denywarning resolved viaallow-wildcard-paths(three intra-workspace dev-dependencies —oxigeo-3d→oxigeo-copc,oxigeo-dev-tools→oxigeo-algorithms,oxigeo-qc→oxigeo-geojson— are deliberately unpinned path deps so publish ordering doesn't become circular)
-
oxigeo-kafkais retired as a project, effective 0.2.1. The crate has been deleted from the workspace and will receive no further releases; the versions already on crates.io (0.0.1 and 0.2.0) have been yanked. This is a deliberate retirement, not an oversight — the crate is gone on purpose and is not coming back.Removed alongside it: the
kafkafeature of oxigeo-etl (and with itKafkaSource/KafkaSourceConfig,KafkaSink/KafkaSinkConfig, their prelude re-exports, and theKafkavariants ofSourceError/SinkError), thekafkafeature of oxigeo-workflow (which gated anrdkafkadependency that no source file in that crate ever used), and therdkafkaentry in[workspace.dependencies].Reason:
oxigeo-kafkawas the sole mandatory C-toolchain dependency in the entire workspace —rdkafka-sysbuilds librdkafka viacmake— which stands against the COOLJAPAN Pure Rust Policy. At 4,831 lines it was 0.62% of the workspace's ~778k lines of Rust and had zero reverse dependencies inside the workspace: nothing built on it. As a direct result of the removal,cargo check --workspace --all-featuresno longer requirescmakeor a C toolchain and completes clean.Migration: use a dedicated Kafka client (e.g.
rdkafka) directly in your own code, or one of the sibling messaging crates that remain supported —oxigeo-streaming,oxigeo-kinesis,oxigeo-pubsub,oxigeo-mqtt. Workflow definitions can still describe a Kafka endpoint over the wire: the pure-RustIntegrationType::KafkaandMessageQueueType::Kafkametadata enums inoxigeo-workfloware unchanged. -
oxigeo-proj: the
proj-sysfeature and theprojC-bindings dependency (C bindings to the system libproj) removed, per the COOLJAPAN Pure Rust Policy. All coordinate transformation already routed through the pure-Rustoxiprojengine, so the feature was vestigial — it contributed only an unused error variant and itsFrom<proj::ProjError>conversion, and no transformation path ever called the C library. Its one real effect was that--all-featuresbuilds requiredcmakeand a system libproj (theprojcrate builds PROJ from source), which brokecargo test --workspace --all-features. For higher-fidelity CRS coverage use the pure-Rustproj-dbfeature (oxisql PROJ.db reader, ~7500 EPSG codes) instead.
0.2.0 - 2026-07-20
-
Project renamed: OxiGDAL → OxiGeo. Version 0.2.0 is functionally identical to 0.1.7 — this is a rename-only release with no feature or behavior changes beyond identifiers. The GitHub repository has moved to https://github.com/cool-japan/oxigeo (old
oxigdalURLs redirect), and v0.1.7 remains the final release published under the OxiGDAL name.Migration table (old → new):
Area Old (OxiGDAL) New (OxiGeo) Crates (all 74 published) oxigdal,oxigdal-<name>oxigeo,oxigeo-<name>CLI binary oxigdaloxigeoEnvironment variables OXIGDAL_*(e.g.OXIGDAL_CONFIG,OXIGDAL_HOST,OXIGDAL_PORT,OXIGDAL_WORKERS,OXIGDAL_LOG_LEVEL,OXIGDAL_DATA_DIR,OXIGDAL_CACHE_DIR)OXIGEO_*(OXIGEO_CONFIG,OXIGEO_HOST,OXIGEO_PORT,OXIGEO_WORKERS,OXIGEO_LOG_LEVEL,OXIGEO_DATA_DIR,OXIGEO_CACHE_DIR)Python PyPI package oxigdal;import oxigdal; native moduleoxigdal._oxigdalPyPI package oxigeo;import oxigeo; native moduleoxigeo._oxigeonpm @cooljapan/oxigdal;@cooljapan/oxigdal-node(+ platform packages);@cooljapan/oxigdal-geoparquet@cooljapan/oxigeo;@cooljapan/oxigeo-node(+ platform packages);@cooljapan/oxigeo-geoparquetC / mobile FFI symbol prefix oxigdal_; JNI classcom.cooljapan.oxigdal.OxiGDAL; headeroxigdal_mobile.h; include guardOXIGDAL_MOBILE_Hsymbol prefix oxigeo_; JNI classcom.cooljapan.oxigeo.OxiGeo; headeroxigeo_mobile.h; include guardOXIGEO_MOBILE_HRust API types OxiGdal*prefixed types (e.g.OxiGdalError)OxiGeo*(OxiGeoError)WASM artifacts oxigdal_wasm*; napi artifactoxigdal.<triple>.nodeoxigeo_wasm*; napi artifactoxigeo.<triple>.nodeContainer images oxigdal/*; systemd unitoxigdal-server.serviceoxigeo/*; systemd unitoxigeo-server.serviceRuntime identifiers HTTP User-Agent OxiGDAL/1.0; Kafka consumer groupoxigdal-etl; ETL checkpoint diroxigdal-checkpoints; edge cache dir.oxigdal_cache; attestation format idoxigdal-attestationHTTP User-Agent OxiGeo/1.0(theoxigeo-stac/oxigeo-mlagents now report0.2.0); Kafka consumer groupoxigeo-etl; ETL checkpoint diroxigeo-checkpoints; edge cache dir.oxigeo_cache; attestation format idoxigeo-attestation -
The
oxigdal-*0.1.x crates remain published on crates.io for existing users; theoxigeo-*crates supersede them starting with 0.2.0.
0.1.7 - 2026-07-20
- oxigdal-cloud-enhanced: real Azure IMDS managed-identity tokens via
azure_identity::ManagedIdentityCredential, replacing the placeholder-token stub; real GCP metadata-server access/identity tokens plus IAM Credentials API impersonation, withGCE_METADATA_HOSToverridable for mock-server tests - oxigdal-cloud: multicloud
build_backend()factory (S3/GCS/AzureBlob/Http, feature-gated) with a backend cache;get/put/delete/exists_in_providerare now functional against real backends - oxigdal-drivers-advanced: JPEG2000 decode now delegates to
oxigdal-jpeg2000for real decode with full header parsing, replacing the gray-placeholder-pixel stub - oxigdal-services: WFS-T Memory/File transactions fully implemented — insert/update/delete/replace with per-path write serialization
- oxigdal-services: WCS File/Url/Memory coverages now do real GeoTIFF read/write via
oxigdal-geotiff;encode_as_geotiffproduces real GeoTIFF bytes (was stub output) - oxigdal-ml-foundation:
onnx_export.rs— pure-Rust ONNX protobuf encoder (ir_version 8, opset 13), round-trip-validated againstoxionnx - oxigdal-ml-foundation: augmentation noise generation now uses real Gaussian sampling (
scirs2_coreseeded RNG) instead of a synthetic pattern - oxigdal-ml:
OnnxModel::infer_multiband— real multi-channel[1, C, H, W]NCHW tensor inference over aMultiBandBuffer(band-sequential channel order, unpacked back into one output band per channel); previouslyinferaccepted only a single-bandRasterBuffer - oxigdal-workflow: Temporal/Prefect
import_workflowround-trips exporter-generated definitions via metadata headers for lossless ID recovery; export now emits real activity bodies - oxigdal-etl:
calculate_ndvimap transform implemented, with a zero-denominator guard so masked/no-data pixels emit0.0rather thanNaN - oxigdal-cli:
info/statsimplemented for FlatGeobuf, GeoParquet, Zarr, GeoPackage, JPEG2000, COPC, PMTiles, MBTiles (previously "not yet implemented") - oxigdal-algorithms: Lanczos resampling
WrapandMirroredge modes implemented (rem_euclid/ reflect-101) - oxigdal-geojson-stream: TopoJSON writer now emits real arcs for LineString/MultiLineString — open-chain topology with endpoint junctions, no-rotation splitting, and shared-arc dedup via negative reversed indices (was an empty
"arcs": []stub) - oxigdal-gpu: subgroup/warp operations emit native WGSL subgroup builtins with a workgroup-shared-memory emulation fallback; Metal filter/reduction/nearest-neighbor shader generators implemented; ballot/vote/
SimdGroupOperationsupgraded; new execute-and-compare GPU tests (verified on Metal) - oxigdal-bench: raster/io scenarios now do real work (tile reads,
MmapDataSource) instead of synthetic placeholders - oxigdal-wasm:
WasmCogViewer.openBytes— drag-drop local GeoTIFF with full codec support including LZW/Zstd viaCogReader<MemorySource>;readTileElevation(SampleFormat tag 339 parsing);WasmTerrain— hillshade/multidirectional hillshade/slope/aspect/color-relief-shaded (Horn method,ImageDataoutput);WasmProjection+wgs84ToWebMercator/webMercatorToWgs84shims - GeoLab demo (
demo/cog-viewer): rebranded OxiGDAL GeoLab — drag-drop loading, terrain-analysis panel, honest byte counters, all CDN dependencies vendored locally; staged to cooljapan.tech/geolab/ (deploy manual) - oxigdal-security: new
attestationmodule — tamper-evident session ledger: domain-separated blake3 hash chain (SessionLog), Merkle root + per-entry inclusion proofs, Ed25519 session seal (SessionSigner::seal), andverify_attestation()re-verifying chain/root/signature from the attestation JSON alone; golden-fixture and tamper-detection tests; native skeptic's verifier exampleverify_attestation.rs; compiles for wasm32 under--no-default-features --features attestation - oxigdal-wasm:
sentinelmodule (GeoSentinel) —WasmStacClientEarth Search STAC scene-pair search with client-side cloud/nodata/grid filtering; self-contained UTM↔WGS84 (Krüger series, EPSG 326xx/327xx);GeoSentinelchange-detection pipeline: windowed COG reads → BOA offset → NDVI drop → fixed/Otsu threshold → polygonization → Karney geodesic hectares → GeoJSON, plus true-color and diff-heatmap RGBA overlays - oxigdal-wasm:
vaultmodule (GeoVault) —WasmVaultSessionblake3 hash-chained operation log sealed with Ed25519 into attestation JSON,verifyAttestation, blake3fileDigestHexfor dropped files - oxigdal-wasm:
anomalymodule — self-contained Z-score / IQR / modified-Z-score / percentile / σ-bounds detectors (parity-ported fromoxigdal-analytics/oxigdal-qc) with mask,ImageData, and summary-JSON outputs - oxigdal-wasm: COG reader overview-level reads — full per-overview IFD parsing (each level gets its own tile directory, predictor, and sample layout),
read_tile_level, andread_window_u16/read_window_rgb8window assembly; PREDICTOR=2 horizontal-differencing undo (TIFF tag 317) for u8/u16 samples on all tile and window paths - oxigdal-geoparquet: new
plan/pushdownAPIs —plan_pushdown()computes row-group bbox + attribute-statistics pruning and exact column-chunk byte ranges from metadata alone (zero I/O);execute_pushdown()runs pushdown over anyparquet::ChunkReader(GeoParquetReader::read_pushdownis now a thin wrapper) - oxigdal-geoparquet: bbox-column detection now honors GeoParquet 1.1
covering.bboxpaths from thegeometadata (authoritative) with a plainbboxstruct-root fallback — VIDA-style files (5.9 GB / 9,533 row groups) now prune correctly - oxigdal-geoparquet:
AttributeFilter::Cmpscalar comparisons (>,>=,<,<=,<>) with Int64/Float64 literal↔column coercion (a bare integer compares correctly against a Float64 column and a whole-valued decimal against an integer column); multiple filters compose as a conjunction viawith_attribute_filters - oxigdal-wasm-geoparquet (new crate): browser GeoParquet range-request client — remote footer decode,
SparseChunkReaderover prefetched byte ranges, 64 KiB-gap range coalescing, SQLWHERE-fragment → predicate lowering (sqlparser, typed rejections naming unsupported constructs),RecordBatch→ GeoJSON conversion, andRemoteGeoParquetopen/plan/query with byte and request accounting (npm:@cooljapan/oxigdal-geoparquet) - GeoSentinel demo (
demo/geosentinel): in-browser Sentinel-2 change detection — STAC pair search, streamed COG windows, NDVI-drop polygons with geodesic hectares, GeoJSON export, before/after crossfade; staged to cooljapan.tech/geosentinel/ (deploy manual) - GeoVault demo (
demo/geovault): sovereign clean-room workstation — CSP-enforced zero egress, live session ledger, seal → attestation download, independentverify.htmlverifier; synthetic Site K-7 DEM via newoxigdal-geotiffexamplegeovault_scene.rs; staged to cooljapan.tech/geovault/ (deploy manual) - GeoParquet Live demo (
demo/geoparquet): bounding-box + SQL attribute queries against the 5.9 GB VIDA GeoParquet via predicate pushdown over HTTP ranges — row-group strip visualization, plan-cost preview before any fetch, Cache API footer caching, offline sample + newoxigdal-geoparquetexamplegenerate_sample.rs; staged to cooljapan.tech/geoparquet/ (deploy manual) - oxigdal-server: new example
render_hero.rs(DEM → combined hillshade → colormap → PNG) - docs.rs metadata added to all 64 remaining publishable crates (21 curated for Pure-Rust-only docs builds)
- New
CONTRIBUTING.mdandCODE_OF_CONDUCT.md
- oxigdal-cloud-enhanced:
reqwestmade optional, gated behind thegcpfeature - oxigdal-ml-foundation: weights save/load moved to
oxicode(COOLJAPAN no-bincode policy) - oxigdal-services: Database transactions/feature-sources/SQL count moved behind new non-default
postgisfeature (oxigdal-postgispool,ST_GeomFromGeoJSON/ST_AsGeoJSON); WCSUrlcoverage fetch moved behind new non-defaultremotefeature - oxigdal-drivers-advanced:
jpeg2000feature is now dependency-gated (pulls inoxigdal-jpeg2000only when enabled) - oxigdal-security: dependencies split behind new
enterprise/tls/attestationfeatures (default enables all three) — the heavyweight server-side surface (tokio, dashmap, petgraph, scirs2-core, oxiarc-zstd, regex, parking_lot, uuid, chrono, crypto stack) is now optional underenterprise;tlsimpliesenterprise;attestationpulls onlyblake3+ed25519-dalek, keeping the wasm32 surface lean - GeoLab demo: shared
@cooljapan/oxigdalWASM package rebuilt (pkg refresh) — GeoLab, GeoSentinel, and GeoVault all serve the same refreshed package - Examples/benches reorganized: 31 orphaned top-level examples wired into
oxigdal-examples(API rot fixed, 5 duplicates pruned); 11 benches wired intooxigdal-bench - README: stats refreshed, doc links updated, GeoLab hero image made clickable, new
## Demosection with native-render gallery (docs/media/); section grown to## Demoswith hero/GIF/gallery/honest-notes blocks for GeoSentinel, GeoVault, and GeoParquet Live - Dependencies bumped to latest per the Latest Crates Policy:
oxiproj/oxiproj-core0.1.1 → 0.1.2,oxisql-core/oxisql-sqlite-compat0.3.2 → 0.4.0,oxinetcdf0.1.4 → 0.2.0,oxih50.1.4 → 0.2.0 — version-onlyCargo.tomlchanges; theoxih5/oxinetcdfjump to 0.2.0 was verified source-compatible with theoxigdal-drivers/hdf5/oxigdal-netcdfdriver code (no driver-side changes required)
Parallel multi-lane defect sweep across the workspace: 233 verified defects fixed across 69 crates (correctness, unwrap-elimination, clippy, doc/README accuracy). Headline items:
Format drivers
- oxigdal-geotiff: floating-point predictor (TIFF
Predictor=3) decode and encode now actually implemented — was previously a silent no-op that passed float32/float64 tile data through unmodified, corrupting round-trips of predictor-encoded float COGs - oxigdal-jpeg2000: MQ arithmetic decoder
INITDECprocedure brought into ITU-T T.800 Annex C spec conformance - oxigdal-drivers/gml:
srsDimensionattribute now parsed, so 3D coordinate geometries are no longer silently treated as 2D - oxigdal-drivers-advanced (VRT):
FirstValidpixel-function compositing fixed for multi-byte sample types (u16/f32/f64 — was only correct for single-byte u8 samples);BandMathpixel function now substitutesB10and higher band variables (previously onlyB1–B9were recognized, silently dropping bands past 9 from expressions) - oxigdal-drivers/hdf5 and oxigdal-netcdf: both drivers re-backed by the real
Pure-Rust
oxih5 0.1.4/oxinetcdf 0.1.4crates (crates.io, no libhdf5/libnetcdf FFI).oxigdal-drivers/hdf5previously read a customOXIGDAL_HDF5_METADATA_V1JSON sidecar and returned zeros for real.h5files; it now reads and writes genuine HDF5 viaoxih5.oxigdal-netcdfnow reads genuine NetCDF-4/CF files viaoxinetcdf. Public API is unchanged (Hdf5Reader::open,Attribute/AttributeValue/Datatype/Hdf5Version/Hdf5Writer,NetCdfReader::open); 730 tests passing across the 4 affected crates, clippy clean. Honest limitations carried forward:oxih50.1.4 fully reads v0-superblock.h5files, while v2/v3-superblock files open but currently yield an empty tree (best-effort, never faked); the writer produces contiguous real HDF5 (chunk/ compression hints are dropped, values are correct); the NetCDF reader surfaces the root group, andscale_factor/add_offset/_FillValueare exposed as attributes but not auto-applied
Algorithms
- oxigdal-algorithms: the raster/DSL calculator's algebraic optimizer no longer folds
x * 0/0 * xto a constant0.0— sinceNaN * 0.0 == NaNandInf * 0.0 == NaN, the previous simplification silently discarded NoData/Inf semantics in NoData-masked raster expressions; covered by a new NaN-semantics regression test - oxigdal-algorithms: Weiler-Atherton polygon clipping's concave-region fallback path no longer silently returns a geometrically wrong (angularly-sorted) shape — the mismatch is now surfaced as an explicit condition rather than masked as a plausible-looking result; full boundary-walk reconstruction for concave fallbacks remains future work (see TODO.md)
Security
- oxigdal-security: RBAC
resource_patternmatching is now actually consulted by the authorization check — was previously parsed and stored but never read, a privilege-widening bug that let any pattern-scoped permission match every resource - oxigdal-gateway: TOTP verification switched to a constant-time comparison and gained a ±1 time-step (30s) clock-skew tolerance window per RFC 6238 §5.2; backup-code and SMS-challenge comparisons are now constant-time as well
Cloud & infra
- oxigdal-server:
server.tomlis now actually loaded viaOXIGDAL_CONFIGin Docker/Kubernetes deployments — was previously parsed and then discarded, silently running on built-in defaults regardless of the mounted config file - oxigdal-stac: implicit
reqwestfeature pull replaced with an explicitasyncfeature (withreqwestkept as a backwards-compatible alias) — the HTTP client and itsaws-lc-systransitive dependency are no longer pulled in for consumers who never use the async surface - oxigdal-streaming: Kafka/Kinesis connector commit-strategy and consumer-lease correctness fixes
- oxigdal-query:
GROUP BYexecution implemented in the SQL executor (was previously a no-op that ignored the clause)
Bindings
- oxigdal (umbrella):
DatasetWriter::finalize()now writes a real format, or returns a typed error, instead of emitting a fakeOXIG-prefixed placeholder blob on unsupported paths
no_std & platform
- oxigdal-core: now compiles under
--no-default-features --features alloc(no_std +alloc, nostd) — the build previously failed under this combination, blockingoxigdal-embedded/oxigdal-noallocno_std consumers
- oxigdal-etl:
transform_crsnow implemented viaoxigdal_proj::transform_epsg, offloaded totokio::task::spawn_blocking— previously panicked with "Cannot start a runtime from within a runtime" when invoked inside any Tokio runtime, becausetransform_epsgopens the bundled PROJ database and builds its own current-thread runtime internally; this is a real bug fix, not a hardening change - oxigdal-etl:
calculate_bboxfixed — was unconditionally returning[0, 0, 0, 0] - oxigdal-ml-foundation: unavailable
scirs2input-gradient paths now return honest typed errors instead of silently returning zero gradients - oxigdal-gpkg: tile matrix set
srs_idnow writes the real EPSG:4326 SRS encoding via newint2_st()helper (was a hardcoded placeholder value of4) - oxigdal-cli:
mergeplaceholder test replaced with a real assertion - oxigdal-wasm: COG IFD parser —
BitsPerSample/SampleFormatentries carrying one SHORT per sample (count > 1, e.g. RGB TCI COGs) were read as inline scalars, yielding a garbage bit depth from the offset word and silently disabling predictor undo for multi-band tiles; arrays now go through offset-following array reads (first entry authoritative) - oxigdal-drivers/flatgeobuf: reader and writer now produce and parse the real FlatBuffers wire format — size-prefixed
Header/Featuretables per the official FlatGeobuf schema, written viaflatbuffers::FlatBufferBuilderand read back through a new bounds-checked vtable walker (fbsmodule) — instead of an ad-hoc custom binary layout; files are now interoperable with GDAL and other FlatGeobuf tooling. Newtests/real_format.rsindependently walks the on-disk bytes to confirm they are genuine FlatBuffers, not just round-trippable against this crate's own reader - oxigdal-geotiff: LERC decode (TIFF Compression tag 34887) now implements the real Esri/GDAL LERC2 bit-stuffed block format — header parsing, run-length-encoded validity mask,
BitStuffer2variable-bit-width unpacking, and exact dequantization — via a newlerc_codec::lerc2decoder; previously the codec only round-tripped its own raw-value payload and returned an explicit error on genuine GDAL/Esri-produced LERC streams. LERC encoding to the interoperable bit-stuffed format remains explicitly unimplemented (typed error, not a fabricated blob) - oxigdal-jpeg2000: Tier-2 packet-header parsing (new
tier2::layout/tier2::packet/tier2::tilemodules) now drives code-block decoding from the real per-(resolution, subband, code-block) precinct geometry and COD progression order, replacing a naive even-division byte split across code-blocks that did not reflect the actual packet structure of real JPEG2000 codestreams. Supports LRCP/RLCP progression, single quality layer, maximum-size precincts, and the reversible 5/3 wavelet; unsupported progression orders or multi-layer streams now return a typedUnsupportedFeatureerror instead of mis-decoding silently - oxigdal-drivers/hdf5: the ScaleOffset (
H5Z_SCALEOFFSET, id 6) and N-Bit (H5Z_NBIT, id 5) filters now implement libhdf5's actual on-diskcd_values/per-chunk layouts (matchingH5Zscaleoffset.c/H5Znbit.c) instead of an invented header format, so chunks produced by h5py/netcdf-c decode correctly and chunks written here are byte-compatible with libhdf5; a newfilters::pipeline_messageparser decodes the real Object Header Filter Pipeline message (both v1 and v2 on-disk layouts) that supplies each filter's parameters - oxigdal-embedded: the
powermodule now makes explicit thatPowerManagerperforms no hardware power/clock transitions unless a board-supportPowerControlleris installed (new trait extension point) — CPU-frequency scaling and clock/power gating are SoC-vendor-specific and were previously implied rather than actually performed;request_mode_strictadded for callers where a silent no-op would be a correctness bug - oxigdal-algorithms: both raster-algebra expression front-ends (the Pest-based
dslparser and the hand-written raster calculator parser) are recursive descent and had no bound on input nesting depth — a deeply nested expression such as((((...))))or a long-----xunary chain aborted the whole process with a stack overflow (SIGABRT), an unrecoverable crash reachable from untrusted expression text. Both now enforce a measuredMAX_EXPRESSION_DEPTH(64) before recursing, returning the typedAlgorithmError::NestingTooDeepinstead of crashing; wired through tooxigdal-node's error mapping as well - Test fixtures: two
oxigdal-cliintegration tests silently depended on demo fixtures excluded by.gitignore(demo/cog-viewer/*.zarr,*.fgb), so they only passed on machines where a developer had manually regenerated the fixture locally and failed deterministically on a clean checkout (previously misdiagnosed as a Linux-only flake).test_read_zarr_info_demo_fixtureis fixed by committing the actualiron-belt.zarrfixture;test_read_flatgeobuf_info_demo_fixtureis fixed by falling back to an equivalent in-process synthesized FlatGeobuf fixture when the demo file is absent, keeping the test self-contained either way - README: quickstart example now compiles as written (
crs()returnsOption) - Hygiene: removed a stray rustc-ICE dump, auto-fix-generated logs/backups, and 3 stray
.bakfiles from cratesrc/trees;.gitignorehardened;.cargo/config.tomlstalerusqlite/proj-sysentries removed;pypi-publish.ymlstaleopenssl-develstep removed;pyproject.tomlandpackage.jsonsynced to 0.1.7
0.1.6 - 2026-06-15
- oxigdal-shapefile: Non-UTF-8 DBF encoding support via
encoding_rs—resolve_cpg()maps CPG file labels,resolve_ldid()maps LDID byte to IANA encoding,decode()transcodes byte slices;ShapefileReader::open_with_encoding()andDbfReader::read_with_encoding()accept an explicit encoding override (PR #10) - oxigdal-proj:
wkt_to_proj_string()— converts an OGC WKT-1/WKT-2 CRS string to a PROJ string, enablingfrom_wktCRS objects to work directly withTransformer(PR #9) - oxigdal-analytics:
LocalMoranI::calculate_with_permutations()— permutation-based significance testing for Local Moran's I spatial autocorrelation (pseudo-p-values under conditional randomisation) - oxigdal-cache-advanced: W-TinyLFU eviction policy —
WTinyLfuEviction<K>(window + protected/probationary segmented LRU) backed byCountMinSketchfrequency estimator for O(1) admit decisions - oxigdal-copc:
WaveformPacket— LiDAR point-format 9 and 10 full-waveform data types (byte-offset, packet-size, return-point-waveform-location, XYZ(t) parametric vector) - oxigdal-drivers/hdf5: HDF5 v2/v3 superblock parser —
SuperblockV2,read_superblock_v2(),validate_superblock_checksum()(Jenkins lookup3 hash), enabling full HDF5 V2/V3 file support - oxigdal-index: Delaunay triangulation —
triangulate(points)(Bowyer-Watson),Triangulation::convex_hull()returning vertex indices in CCW order - oxigdal-qc:
BatchRunner/BatchReport/SeverityCounts— batch QC over directories;GpkgValidator/GpkgValidationResult— structural GeoPackage validation;StacValidator/StacValidationResult— STAC item/collection schema validation;RadiometricValidator/RadiometricValidationResult/BandRange/SensorProfile— per-band range validation against sensor profiles (Sentinel-2, Landsat-8/9, custom) - oxigdal-sensors:
MaximumLikelihoodclassifier — Gaussian MLC with per-class prior support andsingular_covarianceerror variant for degenerate covariance matrices - oxigdal-streaming:
KvStateBackend— OxiStore-backed persistent state backend for stateful streaming pipelines (replaces in-memory HashMap state) - oxigdal-terrain: GLCM texture derivatives —
glcm_texture(),GlcmTextures(contrast, dissimilarity, homogeneity, energy, correlation, ASM),GlcmOffsetdirection enum; TPI variants —tpi_annulus(),tpi_standardized(),landform_classification_tpi(), parallel editionstpi_annulus_parallel()/tpi_standardized_parallel(); geomorphons landform classifier —geomorphons()(Jasiewicz & Stepinski 2013, 10-class); cost distance / least-cost path —cost_distance(),least_cost_path() - oxigdal-temporal: Whittaker smoother and Savitzky-Golay filter for time-series gap filling (
WhittakerSmoother,SavitzkyGolay), completing thegap_fillingmodule - oxigdal-metadata: DOI/INSPIRE metadata transform support —
transform_doi_locator(), enabling ISO 19115 locator URIs to be mapped to DOI/INSPIRE-compliant identifiers - oxigdal-algorithms: Viewshed curvature/refraction constants extracted —
EARTH_RADIUS_M(IUGG 2015, 6 371 000 m) andREFRACTION_COEFF(k = 0.13, standard atmosphere) replace magic numbers in viewshed analysis - oxigdal (umbrella): GPX, KML, and TopoJSON formats now supported in
open()/ vector streaming — detected by file extension and routed to the appropriate parser - oxigdal-drivers/geotiff:
compress_webp_with_params()— WebP compression with explicit quality/lossless parameters;image-webp 0.2added as workspace dep - oxigdal-pmtiles:
MbTilesConn— OxiSQL-backed MBTiles adapter (open(),open_memory(),query_count(),query_text(),query_blob()) used internally by PMTiles MBTiles export
- SQLite backend:
rusqliteandlibsqlite3-sys(C FFI) fully eliminated from the entire workspace; all SQLite access now goes throughoxisql-sqlite-compat 0.1.5(pure-Rust Limbo engine). Affected crates:oxigdal-db-connectors,oxigdal-gpkg,oxigdal-drivers-advanced,oxigdal-mbtiles,oxigdal-pmtiles - oxigdal-security: TLS stack migrated from
ring/webpki-rootstooxitls-core+oxitls-adapter-rustls-rustcrypto+oxitls-webpki-roots— 100% Pure Rust by default;tlsfeature gating maintained; PBKDF2 key derivation moved fromring::pbkdf2topbkdf2::pbkdf2_hmac::<sha2::Sha256> - oxigdal-security:
ring = "0.17"replaced withpbkdf2 = "0.13"in workspace dependencies;argon2,aes-gcm,chacha20poly1305retained as pure-Rust alternatives - oxigdal-drivers-advanced:
rusqlite/geopackagefeature made optional (removed fromdefaultclosure); GeoPackage connection now usesSqliteConnectionBlocking - oxigdal-workflow:
rdkafkamoved behindkafkafeature; newhttp-client,kafka,integrations, andfullfeature flags scirs2-core/scirs2-neural/scirs2-autograd/scirs2-optimize/scirs2-datasets/scirs2-metrics/scirs2-linalg/scirs2-vision/scirs2-seriesupdated 0.4.4 → 0.5.0oxionnxupdated 0.1.3 → 0.1.4oxiarc-*suite updated 0.3.0 → 0.3.3 (archive, core, deflate, lzw, lz4, zstd, bzip2, lzhuf, snappy, brotli)oxicodeupdated 0.2.3 → 0.2.4- Workspace: ~35 inline dependency declarations migrated to
*.workspace = true(workspace policy compliance) oxigdal-kafkaandoxigdal-offlineremoved fromdefault-members(C FFI crates excluded from default workspace builds per Pure Rust Policy)mimallocchanged todefault-features = falseto avoid C dependency in default build- Workspace
[patch.crates-io]: addedoxitls-core,oxitls-adapter-rustls-rustcrypto,oxitls-webpki-rootslocal checkout paths - MSRV: minimum supported Rust version raised 1.85 → 1.89 — the
time 0.3.49dependency requires Rust ≥1.88; standardized on 1.89 to align with the active oxi-ecosystem cluster
- Pure Rust Policy:
ring,rusqlite/libsqlite3-sys,rdkafka-sysremoved from default feature closure — workspace default build is now 100% C/FFI-free oxigdal-gpkgchange-tracking tests: 11 tests#[ignore]ed with explanation comment noting Limbo does not yet fireAFTER INSERT/UPDATE/DELETEtriggers; remaining test verifies schema creation path
- Replaced
ring 0.17(RUSTSEC-2023-advisory dependent) with pure-Rustpbkdf2 0.13+ existingargon2/aes-gcm/chacha20poly1305alternatives aws-lc-sys,rustls-webpki,rsaadvisories (RUSTSEC-2026-0044/0048/0049/0097-0099/0104, RUSTSEC-2023-0071) remain in.cargo/audit.tomlallowlist — all transitive via AWS SDK / rumqttc / azure_core, not directly controllable
0.1.5 - 2026-05-22
- oxigdal-gpu: WGSL uniform layout in
RayMarchUniforms— removed stray_pad1: f32that shifted every field by 4 bytes and caused the compute kernel to readmax_steps≈ 1.05×10⁹, hangingdevice.poll(wait_indefinitely)indefinitely on macOS Metal. The previously-timing-outtest_ray_march_gpu_matches_cpu_when_backend_presentnow passes in 0.127s.
0.1.4 - 2026-04-19
- Wave 1 Algorithms Depth (
oxigdal-algorithms): Weiler-Atherton polygon clipping (general polygon-polygon clipping with hole support), Karney's geodesic area formula (sub-meter accuracy on WGS84 ellipsoid), DE-9IM (Dimensionally Extended 9-Intersection Model) topological predicates, marching squares contour extraction for raster isolines - Wave 1 ML Migration (
oxigdal-ml): Migrated fromorttooxionnx— Pure Rust ONNX inference runtime aligned with COOLJAPAN Pure Rust Policy; cloud detection, super-resolution, and ONNX model loading now useoxionnx - Wave 2 R-tree Enhancements (
oxigdal-index): Node deletion with tree rebalancing, STR (Sort-Tile-Recursive) bulk loading for O(n log n) construction, k-nearest neighbor search with priority queue, R-tree serialization/deserialization - Wave 2 SIMD Resampling (
oxigdal-algorithms): AVX2 and NEON intrinsics for bilinear and bicubic resampling kernels; auto-detects CPU features at runtime - Wave 2 Raster Polygonization (
oxigdal-algorithms): Vector polygon extraction from labeled raster regions with boundary tracing and hole detection - Wave 2 Topology-Preserving Simplification (
oxigdal-algorithms): Visvalingam-Whyatt and Douglas-Peucker variants that preserve shared boundaries across adjacent polygons - Wave 2 NoAlloc Geometry Types (
oxigdal-noalloc):FixedLineString<N>,FixedRing<N>,BBox3D,Mercatorprojection helpers,geohashneighbour enumeration — all zero-allocation, const-generic capacity - Wave 2 PMTiles Reader Completion (
oxigdal-pmtiles): Full tile retrieval pipeline with OxiARC decompression (gzip/brotli/zstd), FNV-1a content deduplication on reads, directory navigation for root + leaf directories - Wave 2 COPC Reader (
oxigdal-copc): Cloud Optimized Point Cloud reader with EPT hierarchy traversal, octree-based spatial queries, and HTTP range request support - Wave 2 GeoPackage B-tree + 3D WKB (
oxigdal-gpkg): B-tree index support for attribute queries, Well-Known Binary 3D geometry parsing (PointZ, LineStringZ, PolygonZ, etc.)
- pyo3 0.28 Migration (
oxigdal-python): Full migration from pyo3 0.24 to 0.28 — updatedBound<'py, T>lifetime parameters, newIntoPyObjecttrait usage, migrated GIL handling APIs - Clippy Cleanup (
oxigdal-drivers/geojson): Streaming test suite clippy cleanup — removed unused imports, fixed.collect()redundancies, corrected error propagation patterns - GeoTIFF Metadata Optimizer (
oxigdal-geotiff): Improvements to COG metadata optimizer and validator for tile ordering and overview consistency - ML Error Types (
oxigdal-ml): Refined error taxonomy andOnnxModelAPI for the oxionnx migration
- All ONNX inference now routes through
oxionnx(Pure Rust) — no C++ ONNX Runtime dependency - Doc examples and subcrate READMEs updated to reference v0.1.4
0.1.3 - 2026-03-21
- Fixed all wgpu 29 API breaking changes:
Instance::newnow takesInstanceDescriptorby value;InstanceDescriptorusesnew_without_display_handle()instead ofDefault::default();bind_group_layoutsnow&[Option<&BindGroupLayout>]— across all GPU and GPU-advanced crates including benchmarks - Fixed
libsqlite3-sysversion conflict: downgradedrusqlite0.39→0.37 andlibsqlite3-sys0.37→0.35 forproj-syscompatibility - Fixed macOS
librocksdb-sysdynamic library loading via.cargo/config.tomlwithDYLD_LIBRARY_PATH - Fixed 6 critical bugs in
oxiarc-brotli(local patch via[patch.crates-io]):- Encoder
write_window_bitswrong bit pattern range and encoding - Decoder
read_window_bitsincorrect bit-to-lgwin mapping - Missing ISEMPTY=0 bit in
encode_meta_blockfor non-empty last blocks BrotliParams::validate()incorrect lgwin range checkwrite_code_length_valuevalues 1 and 5 swapped- Huffman decoder EOF/single-symbol edge cases causing "no matching code found" errors
- Encoder
- Fixed
pipeline_builder.rsclippy:.map(|l| Some(l))→.map(Some)
- All compression/decompression now uses locally-patched
oxiarc-brotli(via[patch.crates-io])
0.1.2 - 2026-03-17
- Geometry Validation & Operations (
oxigdal-index):validation.rswith 7ValidationIssuevariants (unclosed ring, self-intersection, hole orientation, etc.),operations.rswith centroid, area (Shoelace), perimeter, point-in-polygon (ray casting), Douglas-Peucker simplification, Graham scan convex hull,is_convex,distance,ring_bbox,buffer_bbox - PMTiles v3 Writer (
oxigdal-pmtiles):PmTilesBuilderwithadd_tile/buildAPI, Hilbert curve tile ID encoding (hilbert.rs), LEB128 varint encode/decode (varint.rs), content deduplication by FNV-1a hash, PMTiles v3 header/directory serialization - Umbrella Crate Integration (
oxigdal): 7 new feature-gated re-exports (gpkg,pmtiles,mbtiles,copc,index,noalloc,services),convert.rswithDatasetFormatdetection (12 formats),ConversionPlan,can_convert,supported_conversions - Subcrate READMEs: Added README.md for oxigdal-copc, oxigdal-geojson, oxigdal-gpkg, oxigdal-index, oxigdal-mbtiles, oxigdal-noalloc, oxigdal-pmtiles
- Refactored
ogc_features.rs(oxigdal-services): Split 1,981-line monolithic file into 7 focused modules (error.rs,types.rs,query.rs,crs.rs,server.rs,cql.rs,mod.rs) per 2,000-line policy; zero breaking changes - Refactored
epsg.rs(oxigdal-proj): Split 1,873-line file into 5 modules (types.rs,geographic.rs,projected.rs,utm.rs,mod.rs); zero breaking changes - 3 new
DatasetFormatvariants (oxigdal):PMTiles,MBTiles,Copcwith format detection support - Workspace now has 76 crates (~565K total SLoC, ~540K Rust)
- Clippy
should_implement_trait(oxigdal-netcdf): RenamedCfVersion::from_str→parse_versionandCellMethodName::from_str→parse_methodto avoid confusion withstd::str::FromStr
0.1.1 - 2026-03-11
- EPSG Database Expansion (
oxigdal-proj): Expanded from 20 to 211+ EPSG definitions including all 120 WGS84 UTM zones (32601-32660 North, 32701-32760 South), JGD2011, GDA2020, CGCS2000, polar stereographic projections, and State Plane zones - JPEG2000 EBCOT Tier-1 Decoder (
oxigdal-jpeg2000): Full MQ arithmetic coder with Significance Propagation, Magnitude Refinement, and Cleanup passes; split into submodules (mq.rs,contexts.rs,passes.rs,decoder.rs) - GeoTIFF Floating-Point Predictor (
oxigdal-geotiff): Implemented TIFF Technical Note 3 predictor (horizontal differencing + byte reordering) for Float32/Float64 with full round-trip support - Streaming Raster Reader Integration (
oxigdal-streaming): Real GeoTIFF driver integration replacing placeholder metadata/data; format detection, metadata from real files, chunk reading via CogReader - Pure Rust Compression Migration: Replaced
flate2(C) withoxiarc-deflateandzstd(C) withoxiarc-zstdin GeoTIFF driver per COOLJAPAN Pure Rust Policy - CLI Command Implementations (
oxigdal-cli): Functionalinspect(reads headers/metadata),convert(GeoTIFF-to-COG), andbuildvrt(generates VRT XML) commands - Compression Benchmarks (
oxigdal-compress): Real codec benchmarks for deflate, lzw, zstd, bzip2, and lz4 via oxiarc ecosystem - Driver Test Coverage: 20+ integration tests per driver for GeoTIFF, Shapefile, and GeoJSON including round-trip, edge cases, error handling, and multi-band/multi-feature scenarios
- DEM CLI Terrain Analysis (
oxigdal-cli): Activated all 6 terrain operations (hillshade,slope,aspect,TRI,TPI,roughness) — previously blocked bybail!("not yet implemented"); slope percent/degree modes and zero-for-flat aspect option added - DSL Statistical Functions (
oxigdal-algorithms): Implementedmedian(sort-based),mode(frequency-map with f64::to_bits), andpercentile(NumPy-compatible linear interpolation) in DSL function evaluator - DSL For-Loop Support (
oxigdal-algorithms):Expr::ForLoopnow evaluates via child scope iteration with 1M-iteration guard against OOM - WASM Huffman Decompression (
oxigdal-wasm): Implemented full round-trip Huffman decompression — frequency table stored in compressed header, tree reconstructed on decode, single-symbol edge case handled - WASM Huffman Decoder (
oxigdal-wasm): Canonical Huffman encoding/decoding for WebAssembly compression - Server-Side Map Rendering (
oxigdal-server): Tile rendering pipeline with dynamic styling - Delta Encoding (
oxigdal-compress): Delta-of-delta and XOR-delta encoding for time-series raster data - Grouped Aggregation Engine (
oxigdal-analytics): SQL-style GROUP BY aggregation with min/max/sum/mean/count/variance/stddev - HDF5 SWMR Protocol (
oxigdal-hdf5): Single Writer Multiple Reader protocol for concurrent HDF5 access - FlatGeobuf Spatial Indexing (
oxigdal-flatgeobuf): Hilbert R-tree spatial indexing improvements
- Compilation Blocker: Fixed workspace version mismatch (0.3.0 → 0.1.1) that blocked all compilation
- oxiarc-deflate Bug: Fixed
rle_encode_lengthsHuffman run-length encoding overflow for large homogeneous datasets; applied local patch via[patch.crates-io] - Dependency Versions: Corrected oxiarc-* (0.3.0 → 0.2.2), oxicode (0.3.0 → 0.1.1), rs3gw (0.3.0 → 0.1.0), scirs2-core (corrected to 0.3.1)
- Security: Updated quinn-proto (RUSTSEC-2026-0037, DoS vulnerability, CVSS 8.7) and yanked wasm-bindgen 0.2.111 → 0.2.114
- Invalid crates.io Category: Fixed
science::geo→sciencein oxigdal crate metadata - JPEG2000 Module Conflict: Removed duplicate
tier1.rsconflicting withtier1/directory module - File Size Policy: Split
reader.rs(2099 lines) intoreader/mod.rs+reader/tests.rsto comply with 2000-line limit - Hardcoded Version Strings: Replaced hardcoded
"0.1.0"strings withenv!("CARGO_PKG_VERSION")in oxigdal-hdf5 and oxigdal-mobile - Test Isolation: Fixed
oxigdal-edgeintegration test race condition using unique temp dirs withAtomicU64counter - ml-foundation Doctest: Added
#[cfg(not(feature = "ml"))] impl Dataset for GeoTiffDatasetstub to satisfy trait bound in non-ml builds
- Refactored
calculator.rs(oxigdal-algorithms): Split 1,982-line monolithic file into 7 focused modules (ast.rs,lexer.rs,parser.rs,optimizer.rs,evaluator.rs,ops.rs,mod.rs) per 2,000-line policy; zero breaking changes - Dependency Updates: Arrow ecosystem 57→58, sysinfo 0.36→0.38, criterion 0.7→0.8, tokio-tungstenite 0.25→0.28 (API fix applied), redis 0.27→1.0, all SciRS2 subcrates 0.2.0→0.3.1
- Workspace now has 69 crates (~505K total SLoC, ~480K Rust)
- All internal crates use
version.workspace = true - CHANGELOG, README, and publish script updated for v0.1.1
0.1.0 - 2026-02-22
The Independence Release -- First public release of OxiGDAL, a pure Rust reimplementation of GDAL for cloud-native geospatial computing.
This release represents the culmination of intensive development across multiple
phases, delivering ~495,961 SLoC of production-ready Rust code in 68
workspace crates (474,600 lines of Rust across 1,739 .rs files) with zero
C/C++/Fortran dependencies in default features. Estimated development cost:
$18.3M equivalent (COCOMO model).
Core Library (oxigdal-core)
- Core geospatial data types:
BoundingBox,GeoTransform,RasterDataType,RasterBuffer - Abstract I/O traits:
AsyncDataSource,Dataset,RasterDataset,VectorDataset - Storage backends:
LocalFileBackend,S3Backend,HttpBackendwith HTTP range request support RangeCoalescerfor intelligent HTTP request batching and optimization- Arrow-backed
GeoBufferfor zero-copy columnar data operations - Comprehensive error handling with
OxiErrorusingthiserror(no unwrap policy enforced workspace-wide) no_stdcompatible core types for embedded systems- Memory-efficient buffer operations with type-safe pixel access
Algorithms (oxigdal-algorithms)
- SIMD-optimized raster processing: resampling (nearest, bilinear, cubic, Lanczos), reprojection, hillshade, slope, aspect, contour generation
- Vector algorithms: topology operations (split, merge, simplify), buffering, convex hull, spatial joins, dissolve, and clipping
- Raster algebra DSL powered by a Pest grammar parser
- Portable SIMD with feature-gated AVX2, AVX-512, and ARM NEON paths
- Optional Rayon-based parallelism (
parallelfeature) - Terrain analysis: aspect (0-360 degrees), slope (degrees or percent), curvature (profile and planform), hillshade with configurable azimuth/altitude
- Zonal statistics by polygon zones with support for categorical and continuous data
- Douglas-Peucker simplification, positive/negative buffering, boolean operations (union, intersection, difference), spatial predicates (intersects, contains, within, touches, crosses, overlaps, disjoint)
- Pure Rust PROJ reimplementation with zero C dependencies
- 20+ map projections: Transverse Mercator (UTM 1-60), Web Mercator (EPSG:3857), Lambert Conformal Conic, Albers Equal Area, Polar Stereographic, Azimuthal Equidistant, Oblique Mercator, Japan Plane Rectangular (I-XIX zones, JGD2000/JGD2011)
- Complete WKT2 (ISO 19162:2019) parser with WKT1 (OGC 01-009) and ESRI WKT backward compatibility
- 211+ embedded EPSG CRS definitions with O(1) lookup
- Datum transformations: 7-parameter Helmert (Bursa-Wolf), 3/5-parameter Molodensky, NTv2 grid interpolation, NADCON (NAD27-NAD83)
- Automatic transformation path finding between arbitrary CRS pairs
- SIMD-vectorized batch transforms: < 10ms for 1 million points
- Accuracy within 0.001m of the reference PROJ implementation
- GeoTIFF / COG (
oxigdal-geotiff): Cloud-Optimized GeoTIFF reader/writer with tiled access, BigTIFF (> 4GB), overview generation, GeoTIFF 1.1 GeoKey directory, compression codecs (DEFLATE, LZW, ZSTD, PackBits, JPEG), horizontal differencing predictor, LRU tile cache - GeoJSON (
oxigdal-geojson): RFC 7946 compliant reader/writer, streaming parser for large files, all geometry types, GeoArrow zero-copy conversion, configurable coordinate precision - Shapefile (
oxigdal-shapefile): SHP/SHX/DBF reader/writer with full attribute table support and legacy format compatibility - FlatGeobuf (
oxigdal-flatgeobuf): Packed Hilbert R-tree spatial index, streaming feature reads, spatial filtering during decode - GeoParquet (
oxigdal-geoparquet): WKB and GeoArrow encoding, row group statistics with bbox metadata, spatial predicate pushdown, parallel row group reading, ZSTD compression (10x faster than GeoPandas for large datasets) - Zarr v2/v3 (
oxigdal-zarr): Array/group hierarchies, zarr.json manifest (v3), codec pipeline with compression chain, sharding extension, byte shuffle and delta filters, consolidated metadata, parallel chunk loading - HDF5 (
oxigdal-hdf5): Hierarchical data structures, chunking and compression, dataset attributes, group navigation - NetCDF (
oxigdal-netcdf): CF (Climate and Forecast) conventions, unlimited dimensions, group hierarchies, variable metadata extraction - GRIB (
oxigdal-grib): GRIB1/GRIB2 meteorological data, parameter tables, level types - JPEG2000 (
oxigdal-jpeg2000): Tier-1 entropy coding, wavelet transforms (DWT), codestream parsing - VRT (
oxigdal-vrt): Virtual raster datasets, on-the-fly processing, band mathematics, source mosaicking
Advanced Drivers (oxigdal-drivers-advanced): Extended format support and
driver plugin architecture
- PostgreSQL / PostGIS (
oxigdal-postgis): Native geometry types (WKB/EWKB), GiST/BRIN spatial index integration, bulk COPY protocol, connection pooling viadeadpool-postgres - MySQL: Async connector with spatial type mapping (GEOMETRY, POINT, LINESTRING, POLYGON), R-tree spatial index, bulk insert
- MongoDB: Document-based geospatial storage with GeoJSON support
- ClickHouse: Columnar analytics for geospatial OLAP workloads
- Cassandra / ScyllaDB: Wide-column store for time-series geospatial data
- SQLite / SpatiaLite: Feature-gated (C dependency, not in defaults per Pure Rust Policy), R*-tree spatial index, single-file deployment
- Redis (via
oxigdal-gateway): In-memory caching for tile and query results - DuckDB support via query engine integration
- AWS S3: Full S3 API with range requests for COG byte-range access, multipart upload/download
- Azure Blob Storage: Azure SDK integration with Data Lake support
- Google Cloud Storage: GCS backend with authenticated access
- RS3GW (
oxigdal-rs3gw): S3-compatible gateway adapter (MinIO, DigitalOcean Spaces) - Automatic retry with exponential backoff, client-side caching layer
- Deep cloud integrations: AWS Athena, Glue, Lambda, SageMaker, CloudWatch, Cost Explorer; GCP BigQuery, Pub/Sub
- Streaming Pipelines (
oxigdal-streaming): Real-time data processing with backpressure, windowing (tumbling, sliding, session), watermarks for late data handling, stateful operators, metrics reporting - Apache Kafka (
oxigdal-kafka): Producer/consumer for geospatial event streams, key-based partitioning, schema registry, exactly-once semantics - AWS Kinesis (
oxigdal-kinesis): Kinesis Data Streams integration with shard parallelism and checkpointing - Google Cloud Pub/Sub (
oxigdal-pubsub): GCP message queue with subscription management and acknowledgment - MQTT (
oxigdal-mqtt): Lightweight IoT messaging with sensor data types, QoS 0/1/2, topic-based routing, retained messages; customSensorValuedeserializer for robust handling ofserde_json/arbitrary_precision
- SQL-like query language for geospatial data with
sqlparserintegration - Cost-based query optimizer with pluggable rule system
- Optimization rules: Common Subexpression Elimination (CSE), join reordering, projection pushdown, predicate pushdown, filter fusion
- Spatial join algorithms: indexed nested loop, spatial hash join
- Arrow-based columnar execution engine
- ML Runtime (
oxigdal-ml): ONNX Runtime 2.0 integration for cross-platform inference with multi-backend support (CUDA, ROCm, Vulkan, Metal, OpenCL, WebGPU, DirectML), batch preprocessing with automated batch size tuning, INT8/FP16 quantization, ResNet/UNet/Transformer/LSTM architectures - ML Foundation (
oxigdal-ml-foundation): Deep learning training infrastructure with transfer learning, training loops, Adam/SGD optimizers, early stopping, data augmentation, model checkpointing; SciRS2 backend for Pure Rust numerical operations
- GPU Core (
oxigdal-gpu): WGPU-based GPU computing with Vulkan, Metal, DX12, and WebGPU backends; shader compilation for raster operations - GPU Advanced (
oxigdal-gpu-advanced): Multi-GPU load balancing, memory pool management, shader optimization, ML inference pipeline with kernel fusion, automatic backend detection - Optional CUDA backend support
- HTTP Server (
oxigdal-server): Axum-based REST API for tiles, features, and metadata; XYZ tile endpoint; rendering pipeline with on-the-fly processing - API Gateway (
oxigdal-gateway): Rate limiting (Governor), JWT/OAuth2 authentication, GraphQL (async-graphql), WebSocket proxying, Redis-backed sessions - WebSocket (
oxigdal-ws,oxigdal-websocket): Real-time bidirectional protocol for live geospatial data feeds with backpressure handling
- Security (
oxigdal-security): Encryption at rest (AES-256-GCM, ChaCha20-Poly1305), Argon2id password hashing, TLS 1.3 viarustls, RBAC/ABAC access control, audit logging for compliance (SOC2, GDPR readiness) - High Availability (
oxigdal-ha): Raft-based consensus, WAL replication, automatic failover, health monitoring, circuit breaker pattern - Observability (
oxigdal-observability): OpenTelemetry tracing and metrics, Prometheus exposition, Jaeger backend, structured logging viatracing - Clustering (
oxigdal-cluster): Node management, distributed locking, health checks, failure detection - Distributed (
oxigdal-distributed): Arrow Flight-based data transfer, work-stealing scheduler, task graph optimization, fault-tolerant retry
- ETL (
oxigdal-etl): Extract-Transform-Load pipelines with source/sink abstraction, data validation, incremental processing - Workflow (
oxigdal-workflow): DAG-based workflow engine (Petgraph), cron scheduling, dependency management, state checkpointing
- 3D / Point Cloud (
oxigdal-3d): LAS/LAZ point cloud processing, 3D Tiles 1.0 (B3DM, I3DM, PNTS), glTF export, Delaunay triangulation, terrain mesh - Terrain (
oxigdal-terrain): DEM processing, hydrological modeling (flow direction, flow accumulation), watershed delineation, viewshed analysis, terrain ruggedness and topographic position indices - Temporal (
oxigdal-temporal): Time-series datacube operations, temporal aggregation, change detection, gap filling and interpolation - Analytics (
oxigdal-analytics): Spatial statistics, hot spot analysis (Getis-Ord Gi*), clustering, zonal operations, performance profiling - STAC (
oxigdal-stac): SpatioTemporal Asset Catalog 1.0.0 client, catalog/collection/item API, spatial/temporal search - Metadata (
oxigdal-metadata): ISO 19115:2014, ISO 19139 XML, FGDC CSDGM, metadata extraction and transformation between standards - Sensors (
oxigdal-sensors): IoT sensor observation types, calibration, data ingestion - Quality Control (
oxigdal-qc): Data validation, anomaly detection, quality score calculation
- WASM (
oxigdal-wasm): WebAssembly target with IndexedDB storage, Web Worker support,WasmCogViewerJavaScript/TypeScript API, CanvasImageDataintegration, bundle size < 1MB gzipped - PWA (
oxigdal-pwa): Progressive Web App with offline-first architecture, Service Worker caching, installable web apps - Offline (
oxigdal-offline): Offline data sync with conflict resolution, operation queue, delta sync - Node.js (
oxigdal-node): N-API bindings vianapi-rsfor Node.js 16+, async Promise-based API, CommonJS and ESM - Python (
oxigdal-python): PyO3/Maturin bindings,oxigdal.open()universal opener,read_geoparquet()/read_geotiff()/read_zarr(), NumPy array returns, CRS class, algorithm bindings, manylinux2014/macOS/Windows wheels - Jupyter (
oxigdal-jupyter):evcxrkernel integration withplottersvisualization, rich display for rasters and vectors - Mobile (
oxigdal-mobile,oxigdal-mobile-enhanced): iOS (Swift FFI) and Android (Kotlin/JNI), background processing, battery/network-aware scheduling - Embedded (
oxigdal-embedded):no_stdsupport withheaplessandembedded-hal - Edge (
oxigdal-edge): Edge computing platform with minimal footprint, offline-first local database cache, streaming sensor ingestion
- CLI (
oxigdal-cli):oxigdal info,convert,dem,rasterize,warpcommands via Clap - Dev Tools (
oxigdal-dev-tools): File watching (notify), progress bars (indicatif), diff utilities, pretty tables (comfy-table) - Benchmarks (
oxigdal-bench,benchmarks/): Criterion-based benchmarks with flamegraph profiling (pprof) - Examples (
oxigdal-examples): Runnable examples for COG tile serving, GeoParquet creation, format conversion, satellite processing
- Compression (
oxigdal-compress): Pure Rust compression via OxiArc ecosystem (Deflate, LZ4, Zstd, BZip2, LZW, LZH); legacy codec support (flate2 rust_backend, zstd, brotli, snappy) - Data Synchronization (
oxigdal-sync): CRDT-based sync (OR-Set), Merkle tree verification, vector clocks for causality tracking, offline queue - Caching (
oxigdal-cache-advanced): Multi-tier caching (in-memory LRU, on-disk, distributed Redis), cache warming strategies - Services (
oxigdal-services): WMS 1.3.0, WFS 2.0.0, health check endpoints
- COG Viewer (
demo/cog-viewer/): Browser-based Cloud-Optimized GeoTIFF viewer with JavaScript frontend, MapLibre GL and Leaflet integration
- Edition set to Rust 2024 (
edition = "2024") with minimum supported Rust version 1.85 - Workspace-wide lint configuration:
clippy::unwrap_used = "deny",clippy::panic = "deny"enforced across all 68 crates - All compression defaults use Pure Rust backends (COOLJAPAN Policy); C-based compression libraries are feature-gated or being phased out
oxicodereplacesbincodefor binary serialization (COOLJAPAN Policy)OxiArcecosystem (oxiarc-*) replaces thezipcrate for archive handling (COOLJAPAN Pure Rust Policy)- Arrow ecosystem pinned to version 57 across all crates for consistency (upgraded to 58 in v0.1.1)
- Release profile configured with LTO, single codegen unit, and
opt-level = 3 SensorValuedeserialization rewritten with customDeserializeimpl to handleserde_json/arbitrary_precisioncorrectly (replaced derived#[serde(untagged)]deserialization)- Edge binary database cache updated for latest schema
- Eliminated 1,143 out of 1,145
unwrap()calls across the entire codebase (99.83% reduction); remaining 2 are in non-compiled doc comments - Resolved all 16 rustdoc warnings (feature-gated module links, HTML tags in doc comments)
- Fixed
SensorValueenum deserialization ordering for correct serde roundtrip underarbitrary_precision - Fixed all Clippy warnings to achieve zero actionable warnings
- All files refactored to stay under 2,000 lines (maximum observed: 1,976)
- Resolved compilation errors in calculator and buffer modules
- Cleared stale build cache artifacts causing phantom compilation errors
- Fixed Pub/Sub error types and integration test reliability
- Fixed query optimizer rules (CSE, join reordering, projection pushdown)
- Fixed WebSocket protocol handling
- Fixed streaming metrics reporter
- Encryption at rest via AES-256-GCM and ChaCha20-Poly1305
- Password hashing with Argon2id
- TLS 1.3 transport via
rustls(no OpenSSL dependency) - JWT and OAuth2 authentication in the API gateway
- Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC)
- Audit logging for compliance (SOC2, GDPR readiness)
- HMAC-SHA256 message authentication for inter-service communication
- All cryptographic operations use pure Rust crates (
ring,rustls,aes-gcm,chacha20poly1305,argon2) - Minimal unsafe code (< 1% of codebase), fully audited and documented
- Vulnerability scanning integrated via
cargo-audit
Benchmarks Achieved
- COG tile access: < 10ms (local SSD), < 100ms (cloud S3/GCS)
- Metadata reading: < 5ms for typical GeoTIFF headers
- GeoParquet reading: 10x faster than GeoPandas for large datasets
- PROJ transformations: < 10ms for 1 million points (WGS84 to UTM)
- Docker image size: < 50MB (vs 1GB+ with traditional GDAL)
- WASM bundle: < 1MB gzipped (vs impossible with C-based GDAL)
Statistics
- Total SLoC: 495,961 (2,042 files)
- Rust Code: 474,600 lines across 1,739
.rsfiles - Workspace Crates: 68
- Format Drivers: 11 (GeoTIFF, COG, GeoJSON, GeoParquet, Zarr, FlatGeobuf, Shapefile, NetCDF, HDF5, GRIB, JPEG2000, VRT)
- Map Projections: 20+ implemented, 211+ EPSG codes embedded
- Estimated Cost: $18,275,174 (COCOMO model)
Platform Support
- Operating Systems: Linux (x86_64, aarch64), macOS (x86_64, aarch64/M1+), Windows (x86_64)
- WebAssembly:
wasm32-unknown-unknowntarget - Mobile: iOS (arm64, simulator), Android (arm64-v8a, armeabi-v7a, x86_64)
- Embedded:
no_stdsupport for microcontrollers
COOLJAPAN Ecosystem Compliance
- Pure Rust Policy: 100% Rust in default features (C/Fortran feature-gated)
- No Unwrap Policy: Zero
unwrap()in production code (clippy::unwrap_used = "deny") - Workspace Policy: All dependencies use workspace inheritance
- Latest Crates Policy: All dependencies at latest available versions
- COOLJAPAN Integration: SciRS2-Core, OxiCode (not bincode), OxiArc (not zip), OxiFFT (not rustfft), OxiZ (not Z3)
- JPEG2000 support is basic (tier-1 only, no tier-2 optimizations yet)
- Some transitive dependencies have unmaintained advisories (tracked):
rustls-pemfile(RUSTSEC-2025-0134),sled(RUSTSEC-2025-0057 fxhash),evcxr(json 0.12.4),indicatif(number_prefix 0.4.0) - Embedded platforms require nightly Rust for some features
See MIGRATION.md for detailed migration guide from GDAL C/C++, Rasterio, GeoPandas, and PROJ.
- v0.2.0 (Q2 2026): Additional projections (100+ total), GPU acceleration expansion, ML pipeline enhancements
- v0.3.0 (Q3 2026): Real-time streaming improvements, enhanced JPEG2000, cloud-native tile server
- v1.0.0 (Q4 2026): Production stability, LTS commitment, enterprise compliance certifications
Development Team: COOLJAPAN OU (Team Kitasan)
- GDAL Project: Original inspiration and reference implementation
- GeoRust Community: Ecosystem collaboration and shared crates
- PROJ: Coordinate transformation reference and test suite
- Rust Community: Language, tooling, and ecosystem support
- Specifications: GeoTIFF, COG, OGC (WMS/WFS), STAC, ISO 19115, RFC 7946
- Testing Data: USGS Earth Explorer, Copernicus, OpenStreetMap
- Homepage: https://github.com/cool-japan/oxigeo
- Documentation: https://docs.rs/oxigeo
- Issue Tracker: https://github.com/cool-japan/oxigeo/issues