- Rust 100%
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 theosmnodecachecrate 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.