1
0
Fork
You've already forked osm-node-store
0
efficient node store for OSM data
  • Rust 100%
2026年05月19日 17:48:53 -06:00
.github/workflows chore: add release tooling 2026年05月18日 15:34:39 -06:00
benches feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
docs docs: mark ADR 0005 superseded by ADR 0006 2026年05月19日 15:11:02 -06:00
examples examples: add osmpbf based example 2026年05月19日 17:47:57 -06:00
src feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
.gitignore docs: example and benchmark in README 2026年05月19日 17:48:14 -06:00
Cargo.lock feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
Cargo.toml feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
CHANGELOG.md feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
CONTEXT.md feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
CONTRIBUTING.md feat!: shrink to osm-node-store 0.3.0 per ADR 0006 2026年05月19日 15:04:34 -06:00
deny.toml chore: rename crate to osm-pbf-kit 2026年05月18日 16:08:43 -06:00
LICENSE init shared OSM PBF core crate 2026年05月17日 23:07:17 -06:00
README.md docs: example and benchmark in README 2026年05月19日 17:48:14 -06:00
skills-lock.json chore: check in local agent and skill tooling state 2026年05月19日 14:13:52 -06:00

osm-node-store

A small NodeLookup trait with a missing-means-None contract, plus a few backing strategies. Pair it with any PBF parser to build two-pass workflows.

I built this for Cosmo and OOPS - I hope it's useful for others as well!

Context

Every OSM tool that resolves way geometry from a PBF file needs an id → (lon, lat) map that can answer "I don't have this node" distinctly from "I have this node at (0, 0)."

The NodeLookup trait

The NodeLookup trait offers Option<(f64, f64)>. Missing always means None. You can use it with your choice of storage backend:

Backend Storage Per-node cost Best for
Sparse sorted (id, coord) file, binary-searched ~16 B extracts (sparse subset of global IDs)
Dense mmap'd file indexed directly by ID + 1-bit-per-ID presence bitmap ~8 B + 1 bit planet-scale or dense ID ranges
Memory HashMap<u64, _> hasher-dependent tests, small datasets
osmnodecache adapter (feature-gated) upstream osmnodecache file + separate presence bitmap ~8 B + 1 bit drop-in for projects already on osmnodecache

The presence bitmap lets the dense backends honor the missing-means-None contract. Sparse and memory get presence for free from their data structures.

Pick a backend by ID density, not raw dataset size — for a regional extract: sparse; dense for a planet-sized extract.

Feature Flags

  • osmnodecache — optional dense-cache adapter using the osmnodecache crate plus a presence bitmap.

The crate has no default features.

osm-node-store = "0.3"
osm-node-store = { version = "0.3", features = ["osmnodecache"] }

Example

Two-pass way resolution with osmpbf. A runnable version lives at examples/two_pass_ways.rs:

cargo run --release --example two_pass_ways -- path/to/extract.osm.pbf
use osm_node_store::{
 node_store::{NodeLookup, NodeStoreWriter},
 Result,
};
use osmpbf::{Element, ElementReader};
fn main() -> Result<()> {
 let path = "great-britain-latest.osm.pbf";
 // Pass 1: stream every node into the store.
 // Sparse requires ascending IDs — PBFs from `osmium sort` satisfy this.
 let mut writer = NodeStoreWriter::new_sparse()?;
 ElementReader::from_path(path)?.for_each(|element| match element {
 Element::Node(n) => {
 writer.put_coord(n.id() as u64, (n.lon(), n.lat())).unwrap();
 }
 Element::DenseNode(n) => {
 writer.put_coord(n.id() as u64, (n.lon(), n.lat())).unwrap();
 }
 _ => {}
 })?;
 let reader = writer.finalize()?;
 // Pass 2: resolve way geometries.
 ElementReader::from_path(path)?.for_each(|element| {
 if let Element::Way(way) = element {
 for node_id in way.refs() {
 match reader.get_coord(node_id as u64) {
 Some((lon, lat)) => { /* use coord */ }
 None => { /* node not in extract */ }
 }
 }
 }
 })?;
 Ok(())
}

Development

See CONTRIBUTING.md.