Guides
Using the Rust library
Stream current BAG voorkomens into your own code, or run conversions from Rust.
The bagkit command is a thin layer over the bagkit crate. Everything it does is available as a library, and you can plug your own code into the same parallel pipeline.
[dependencies]anyhow = "1"bagkit = { version = "0.1", default-features = false }Without default features the crate reads extracts and writes CSV. Enable sqlite or parquet for those formats; see crate features.
Stream voorkomens
Section titled “Stream voorkomens”Extract::for_each calls your closure with every current voorkomen of the object types you select, in document order:
//! Prints the naam of every current woonplaats.//!//! cargo run --example stream_localities -- lvbag-extract-nl.zip 2026-10-08use bagkit::{ extract::{Extract, ObjectType, Selection}, xml::BagObject,};
fn main() -> anyhow::Result<()> { let mut args = std::env::args().skip(1); let input = args.next().unwrap_or("lvbag-extract-nl.zip".into()); let date = args.next().unwrap_or("2026-10-08".into());
let extract = Extract::new(vec![input.into()], &date)?; let mut localities = Vec::new(); extract.for_each(Selection::only(&[ObjectType::Locality]), |object| { if let BagObject::Locality(locality) = object { localities.push(locality.name); } Ok(()) })?; localities.sort(); println!( "{} woonplaatsen, from {:?}", localities.len(), localities.first() ); Ok(())}Inflating and parsing runs on every core; your closure runs on the calling thread. Only documents of the selected types are read.
Convert on worker threads
Section titled “Convert on worker threads”For heavier work per voorkomen, implement a Converter. It runs on the worker threads, once per voorkomen, and emits items that arrive at your writer closure in document order:
//! Counts current nummeraanduidingen per postcode, converting voorkomens on worker threads.//!//! cargo run --example count_postcodes -- lvbag-extract-nl.zip 2026-10-08use anyhow::Result;use bagkit::{ extract::{Converter, Extract, ObjectType, Part, Selection}, xml::BagObject,};use std::collections::BTreeMap;
/// Emits the postcode of every current nummeraanduiding.struct Postcodes;
impl Converter for Postcodes { type State = (); type Item = String;
fn begin(&self) {}
fn convert( &self, _: &mut (), object: BagObject, emit: &mut dyn FnMut(String) -> Result<()>, ) -> Result<()> { if let BagObject::Address(address) = object && let Some(postcode) = address.postcode { emit(postcode)?; } Ok(()) }}
fn main() -> Result<()> { let mut args = std::env::args().skip(1); let input = args.next().unwrap_or("lvbag-extract-nl.zip".into()); let date = args.next().unwrap_or("2026-10-08".into());
let extract = Extract::new(vec![input.into()], &date)?; let mut counts = BTreeMap::new(); // Read both parts, in order, to see every voorkomen. for part in [Part::First, Part::Second] { extract.read( part, Selection::only(&[ObjectType::Address]), &Postcodes, &mut |postcode| { *counts.entry(postcode).or_insert(0u64) += 1; Ok(()) }, )?; } let busiest = counts.iter().max_by_key(|(_, count)| **count); println!("{} postcodes; the busiest is {busiest:?}", counts.len()); Ok(())}State holds per-document state, for example to batch items; begin creates it and end can flush it.
Run a conversion
Section titled “Run a conversion”convert::convert does what bagkit convert does:
//! Writes an adreslijst as CSV, like `bagkit convert --shape addresses --format csv`.//!//! cargo run --example write_address_list -- lvbag-extract-nl.zip 2026-10-08use bagkit::{ convert::{self, Options, Shape}, dataset::Format, extract::{Extract, Selection},};
fn main() -> anyhow::Result<()> { let mut args = std::env::args().skip(1); let input = args.next().unwrap_or("lvbag-extract-nl.zip".into()); let date = args.next().unwrap_or("2026-10-08".into());
let extract = Extract::new(vec![input.into()], &date)?; let options = Options { output: "adreslijst.csv".into(), force: true, format: Format::Csv, shape: Shape::Addresses, selection: Selection::all(), cache_mib: 64, }; let stats = convert::convert(&extract, &options)?; println!("{} rows", stats.rows.unwrap_or_default()); Ok(())}These examples live in the repository’s examples/ directory, where cargo test compiles them. Run one from a checkout of the repository:
cargo run --example stream_localities -- lvbag-extract-nl.zip 2026-10-08Modules
Section titled “Modules”| Module | Purpose |
|---|---|
extract |
Read the current voorkomens of selected object types in parallel |
xml |
The typed voorkomens: Locality (woonplaats), PublicSpace (openbare ruimte), Address (nummeraanduiding), AddressableObject (adresseerbaar object) and Building (pand) |
tables, address_list |
Shape voorkomens into rows |
dataset |
Table definitions, values and formats |
output, sqlite, parquet |
Write rows |
convert |
Run all of the above |
query, api, server |
Look adressen up in SQLite output and serve the HTTP API |
© 2026 David Stotijn · MIT License.