Skip to content

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.

Cargo.toml
[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.

Extract::for_each calls your closure with every current voorkomen of the object types you select, in document order:

examples/stream_localities.rs
//! Prints the naam of every current woonplaats.
//!
//! cargo run --example stream_localities -- lvbag-extract-nl.zip 2026-10-08
use 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.

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:

examples/count_postcodes.rs
//! Counts current nummeraanduidingen per postcode, converting voorkomens on worker threads.
//!
//! cargo run --example count_postcodes -- lvbag-extract-nl.zip 2026-10-08
use 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.

convert::convert does what bagkit convert does:

examples/write_address_list.rs
//! Writes an adreslijst as CSV, like `bagkit convert --shape addresses --format csv`.
//!
//! cargo run --example write_address_list -- lvbag-extract-nl.zip 2026-10-08
use 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-08
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