Low-level geospatial coordinate primitives for Rust — China datums (GCJ-02/BD-09), angle encodings, coordinate parsing/formatting, and geodesy utilities. The crate abstracts only geo-related complexity, as primitives for higher-level libraries to consume.
Status: Early development, released incrementally. The public API is
0.xand may change between minor versions. See ROADMAP.md for the planned release order.
The current release ships the core data model and the China datums:
- Coordinate model:
Coordinate,Crs,Height,LatLon;Approx<T>; typedError/Result;Length/LengthUnit; angle encodings (Dd/Dms/Ddm);Fixobservation metadata. - China datums:
Wgs84↔Gcj02↔Bd09(exact forward transforms, approximate inverses with explicit error bounds) andBaiduMercator. - Distance: spherical
haversine_distance. - Optional
serdesupport (serdefeature).
The core path ships next: angle/unit conversions, DD/DMS/DDM formatting, text + geo:-URI parsing, and Plus Code. Geodesy (ECEF/frames/Karney geodesics/Helmert datums), the remaining grids (UTM/MGRS/Geohash/Maidenhead), interchange ingestion (GeoJSON/WKT/GPX/KML, NMEA), and runtime CRS conversion are scaffolded but deferred — see ROADMAP.md. EXIF GPS extraction is out of scope: it belongs to a separate library that consumes this crate's primitives.
The API is exposed to Python, Kotlin, Swift, and TypeScript with full
capability parity via UniFFI, generated
from the separate geocoordinates-ffi crate (Java consumes the Kotlin/JVM
artifact directly). The bindings track the released surface and gate each
release; today that covers the China-datum core: WGS-84 ↔ GCJ-02 ↔ BD-09
conversions, Baidu Web Mercator, out_of_china, and haversine distance.
Because the Rust API is idiomatic, the FFI surface is deliberately flattened:
generics (Approx<T>), traits, Deref, and operator overloads do not cross the
boundary. Their equivalents are concrete records (e.g. ApproxWgs84 { lat, lon, max_error_m }) and free functions returning primitives (distance is returned in
meters). Approximate inverses keep their _fast / _refined names and carry
max_error_m. The published geocoordinates crate is unaffected — the bindings
build from their own cdylib/staticlib crate.
Java is served by the Kotlin/JVM bindings (directly callable from Java), so it has no separate generated surface. TypeScript targets WebAssembly (browser / bundler) via
ubrn; initialize it withawait uniffiInitAsync()before use (seecrates/geocoordinates-ffi/web/).
Generate the bindings (needs the Rust toolchain and just):
just bindings # all languages -> bindings/<lang>/
just bindings-python # a single languagePython example:
import geocoordinates_ffi as gc
gcj = gc.wgs84_to_gcj02(gc.Wgs84(lat=39.915, lon=116.404))
wgs = gc.gcj02_to_wgs84_refined(gcj) # approximate inverse
print(wgs.lat, wgs.lon, "±", wgs.max_error_m, "m")Each language's bindings are published to its registry on every v* release, using
that ecosystem's native tooling. See PUBLISHING.md for the release
workflows and registry setup.
| Language | Registry | Package | Install |
|---|---|---|---|
| Python | PyPI | geocoordinates-rs |
pip install geocoordinates-rs |
| TypeScript / WASM | npm | geocoordinates-rs |
npm install geocoordinates-rs |
| Kotlin / Java | Maven Central † | io.github.justin13888:geocoordinates |
add the coordinate to Gradle / Maven |
| Swift | GitHub Releases | GeoCoordinates |
.package(url: "https://github.com/justin13888/geocoordinates-rs", from: "0.1.1") |
† Maven Central publishing is pending registry credentials — the JVM workflow builds the artifact but skips the upload until the Central Portal token and GPG key are configured (see PUBLISHING.md).
The native Rust crate is published separately to crates.io (
cargo add geocoordinates).
- Rust (rustup) — toolchain (pinned via
rust-toolchain.toml) - just — command runner
- Lefthook — git hooks manager
- cargo-llvm-cov — code coverage
- convco — Conventional Commits linter
Dev tools (just, cargo-mutants, convco) are pinned in .mise.toml; run
mise install to provision them, or install them yourself — .mise.toml is inert without mise.
cargo build
just check # fmt-check + clippy + tests| Command | Description |
|---|---|
cargo build |
Build the crate |
just test |
Run tests |
just fmt |
Format code |
just lint-fix |
Lint and auto-fix |
just coverage |
Report code coverage |
just check |
fmt-check + lint + test (CI parity) |
just bindings |
Generate FFI bindings (all languages) |
This project uses Lefthook. Pre-commit hooks auto-fix formatting and linting on staged files. The commit-msg hook validates each message against Conventional Commits (via convco). Pre-push hooks lint the pushed commits and run format checks, Clippy, tests, and a coverage report.
GitHub Actions runs format checks, Clippy, tests, and a coverage report on pushes to master and pull requests, and lints PR commit messages against Conventional Commits (via convco). A separate FFI workflow builds the bindings cdylib, generates bindings for all four languages, and runs a Python smoke test. Both workflows cancel superseded in-flight runs when a PR is updated.
Releases are automated with release-plz and driven by
Conventional Commits. As commits land on master,
release-plz maintains a release PR that bumps the version and updates
CHANGELOG.md. Merging that PR cuts the release: it tags vX.Y.Z, creates
a GitHub release, and publishes to crates.io.
To honor the staged ROADMAP, the release PR is merged at milestone boundaries rather than on every commit. Publishing uses crates.io Trusted Publishing (OIDC) — no API token is stored in the repository.
This project uses cargo-llvm-cov for LLVM-based code coverage. No minimum threshold is enforced yet — tests and a threshold are added once the API stabilizes.
just coverageLicensed under either of MIT or Apache-2.0 at your option.