COOLJAPAN
← All posts

OxiProj 0.1.4 Released — License Files, Trusted Publishing, and a Documentation Reality Check

OxiProj 0.1.4 is a maintenance release: two Python-packaging fixes (missing LICENSE files in the maturin sdist, PyPI trusted publishing), a corrected cs2cs Quick Start example, ecosystem dependency bumps across seven COOLJAPAN crates, and a documentation-accuracy pass that corrects the README's own parity-corpus numbers. No public API changes, no behavior changes, no source changes to any library crate.

release oxiproj proj gis cartography geospatial pure-rust maintenance

Sometimes the most important fix in a release isn’t in the code — it’s in what the documentation says about the code.

Today we released OxiProj 0.1.4 — a maintenance, packaging, and documentation-accuracy release on top of 0.1.3. There is no new roadmap surface area here and no public API change. What shipped instead: two Python-packaging fixes, a corrected CLI example, dependency bumps across seven upstream COOLJAPAN crates, and a documentation pass that caught OxiProj’s own README understating its test coverage.

OxiProj is still exactly what it was on day one: a 100% Pure-Rust reimplementation of PROJ 9.8.0 — not a binding to libproj, no proj-sys, no C toolchain. Every crate is still #![forbid(unsafe_code)], wasm included. 0.1.4 doesn’t touch any of that; it fixes how OxiProj packages itself and how honestly it describes itself.

Why a “boring” release is worth reading

Dependency drift, packaging gaps, and stale documentation are the boring failure modes that quietly erode trust in a library long before a real bug does. 0.1.4 closes out four of them:

Technical Deep Dive: auditing the audit

The differential-parity corpus (crates/oxiproj/tests/data/parity_corpus.txt) is OxiProj’s evidence that its projections match PROJ-C output at 1e-9° tolerance. README.md and TODO.md described it as 1009 records covering 149 of the 157 proj -l map projections, with eight named projections — omerc, nzmg, mod_krovak, s2, spilhaus, calcofi, som, lsat — flagged as outside the corpus, five of them “not yet regenerated.”

None of that was true of the file that actually shipped in 0.1.2. Re-verified for 0.1.4 against Homebrew PROJ 9.7.0: the committed corpus holds 1049 records and covers all 157 of 157 proj -l map projections, zero exclusions — every one of those eight “missing” projections was already present with real forward and inverse values. Regenerating the corpus from scratch reproduces the committed file byte-for-byte (SHA-256 aef3ba24…), and the live differential suite expands those 1049 records into 1972 OxiProj-vs-PROJ measurements: 203 families bit-faithful, 4 quarantined at 1e-9-or-tighter iteration residuals (eqc, imw_p, misrsom, som), 0 failing.

Two more corrections rode along with it: the reference oracle was mislabeled “Homebrew PROJ 9.8.0” in two places when the corpus header itself records PROJ 9.7.0 as what it was actually checked against (OxiProj still ports the 9.8.0 lineage — only the citation was wrong); and the downstream engine-cutover status still gated M16 as “blocked pending sign-off to modify the separate oxigdal project,” when that project renamed itself OxiGDAL → OxiGeo at its own v0.2.0 and the cutover is landed there — oxigeo-proj runs OxiProj as its default engine, ships without proj-sys, and keeps proj4rs only behind an opt-in compatibility feature.

Getting Started

Nothing changes in how you reach for OxiProj:

cargo add oxiproj
use oxiproj::{Crs, Transformer, Coordinate};

fn main() -> Result<(), oxiproj::TransformError> {
    // WGS84 geographic (lon, lat in degrees) -> Web Mercator (meters).
    let wgs84 = Crs::wgs84()?;
    let webmerc = Crs::web_mercator()?;
    let t = Transformer::new(wgs84, webmerc)?;

    // GIS convention: x = longitude, y = latitude (degrees).
    let input = Coordinate { x: 9.0, y: 48.0 };
    let output = t.transform(&input)?;
    println!("Web Mercator: x = {}, y = {}", output.x, output.y);

    // Build a CRS directly from a proj-string, then transform into it.
    let utm32 = Crs::from_proj("+proj=utm +zone=32 +datum=WGS84")?;
    let to_utm = Transformer::new(Crs::wgs84()?, utm32)?;
    let projected = to_utm.transform(&input)?;
    println!("UTM 32N: x = {}, y = {}", projected.x, projected.y);

    Ok(())
}

And from the CLI, the corrected example — note the axis order:

# Convert WGS84 → UTM Zone 33N (EPSG:4326 is lat,lon per authority axis order)
echo "52.52 13.41" | cs2cs EPSG:4326 EPSG:32633

What’s New in 0.1.4

Tips

This is the foundation

0.1.4 leans on the same COOLJAPAN stack as always — oxisql-sqlite-compat for the C-free EPSG engine, oxih5 for GGXF grids, oxiblas for Helmert LSQ, oxicuda for GPU reprojection, oxiz for SMT-verified Helmert bounds, oxiarc-deflate/lzw for compressed grids, and oxihttp for optional network grid fetch — with the same north star: letting OxiGeo (the renamed OxiGDAL) run its coordinate engine fully C-free.

Repository: https://github.com/cool-japan/oxiproj

Star the repo if you think a library’s documentation should be held to the same accuracy bar as its code. The era of “close enough” changelog figures is over — Pure Rust means you get to verify everything, including what we say about it.

KitaSan at COOLJAPAN OÜ August 17, 2026

↑ Back to all posts