korp-collectors
The standalone LEAF of korp's open-data collector layer. One object-safe
Collector trait + per-access-METHOD import collectors (Swedish + EU open data),
parsing pure-Rust → Apache Arrow → (optionally) Apache Iceberg via skade.
Plus a RegionSource seam that FETCHES OSM extracts on demand.
This crate depends on neither korp nor facett, so both can consume it —
which breaks the facett ⇄ korp dependency cycle. (Mirrors how
znippy-zoomies/osm-katana and Skidbladnir were extracted as leaves.) The
ravens Hugin & Munin collect — hence "collectors" (née connectors).
Collectors (all Direction::Source)
| name | kind | parses | feature |
|---|---|---|---|
dcat-ap-se | DCAT-AP-SE crawl | RDF/XML + SPARQL-JSON → dataset/distribution registry | parsers |
pxweb-jsonstat2 | PxWeb / JSON-stat2 | statistical cube → code-col-per-dim + value | parsers |
eurostat-sdmx, oecd-sdmx, ecb-sdmx, insee-sdmx, ons-sdmx, istat-sdmx, worldbank-sdmx | SDMX-JSON 2.1 / 1.0 | generalised series/observation dim split → per-(dims × time × measure) value rows — one collector, any SDMX provider (Eurostat/OECD/ECB/IMF/INSEE France/ONS UK/ISTAT Italy/World Bank/EEA), zero new parsing — presets differ only in branding + endpoint | parsers |
entsoe, entsoe-generation, entsoe_day_ahead, nordpool, energy-charts-power | ENERGY market | ENTSO-E market-document XML → time-series points (SE1–SE4 ⇄ EIC): entsoe load/generic quantity, entsoe-generation per-psrType volume (A75), entsoe_day_ahead day-ahead SPOT PRICES (A44, price.amount per zone × hour → entsoe_day_ahead_prices; the open free-token price route, token via ENTSOE_TOKEN); Nord Pool day-ahead zones (licensed twin), Fraunhofer generation-mix | parsers |
opensky-flights, opensky-europe-bbox, opensky-tracks | OPEN FLIGHT recorder (ADS-B) | OpenSky states/all positional-array → flight position table → osm3d-flight scene (live auth: OAuth2 client-credentials, 2025 Keycloak, via OPENSKY_CLIENT_ID/OPENSKY_CLIENT_SECRET). opensky-tracks = the recorded flight PATH: /tracks path[] → ordered (time, lat, lon, baro_altitude, heading, on_ground) waypoint trajectory → opensky_tracks table → osm3d-flight replay (basic auth via OPENSKY_USER/OPENSKY_PASS) | parsers |
smhi-hydroobs | WATER | SMHI hydrological observations (river discharge / water level) — station value series → Arrow | parsers |
metno-forecast | WEATHER | Met.no Locationforecast point forecast → per-time forecast table (temp, wind, humidity, pressure, cloud) → osm3d scene | parsers |
gbif-occurrences | biodiversity | GBIF occurrence search (keyless, CC0/CC-BY) → occurrence point table (taxon + WGS84 fix + country) → osm3d-poi scene | parsers |
worldbank-indicators, restcountries | reference / stats | World Bank Open Data ([meta,data] envelope) → per-(country, year, indicator); RESTCountries → ISO-3166 country join spine | parsers |
mobility-database, mobility-database-rt | transit META-catalogue | Mobility Database (CC0, the TransitFeeds successor) → typed feed-descriptor rows that auto-drive the GTFS collectors; bearer-token via MOBILITY_DB_TOKEN. mobility-database enumerates STATIC GTFS feeds (/v1/gtfs_feeds); mobility-database-rt enumerates GTFS-Realtime feeds (/v1/gtfs_rt_feeds, id, entity_type vp/tu/sa, provider, country, .pb URL) that drive the gtfs-rt-vehicles protobuf reader for live vehicle positions | parsers |
erddap-emodnet-physics, erddap-noaa-coastwatch | ERDDAP scientific data | ERDDAP .json table (columnNames + columnTypes + rows matrix) → typed Arrow, uniform over any dataset id on any ERDDAP server (EMODnet ocean + global ocean/atmos); .csv variant via the csv collector | parsers |
eea-wise-water, destatis-genesis, cbs-odata | EU open data | EEA WISE water quality (Waterbase via the Discodata SQL API, /results) → per-(site, determinand, year) rows; Destatis GENESIS-Online table catalogue (/List, token via DESTATIS_GENESIS_TOKEN); CBS Open Data (Statistics Netherlands) OData TypedDataSet (/value) → typed observations | parsers |
polisen-events, kolada-kpi, smhi-metobs, smhi-weather, ais-positions, marinetraffic-ais, gbfs-citybikes, osm-overpass, bolagsverket-organisationer | generic REST/JSON | config-driven JSON field-map → Arrow | parsers |
usgs-earthquakes | USGS GeoJSON feed | FeatureCollection Point features → quake rows (id, place, magnitude, depth, lon/lat, epoch-ms time) → osm3d scene | parsers |
rss-atom | RSS/Atom feed | RSS 2.0 / Atom 1.0 syndication (dialect-agnostic quick-xml) → per-item rows (feed_title, id, title, link, summary, author, published, categories JSON) | parsers |
openaq-air-quality | OpenAQ REST | air-quality results[] JSON → measurement rows (station, parameter, value, unit, lon/lat, country, city, UTC time; nested coordinates+date.utc flatten) → osm3d scene | parsers |
energy-charts-power | Energy-Charts REST | generation-mix JSON — parallel unix_seconds[] axis ZIPPED against each production_types[].data[] by index → per-(time, type) power rows (Europe-wide via ?country=; CC-BY) | parsers |
wfs | OGC WFS | GetFeature GML FeatureCollection → feature rows (WKT geometry + properties JSON) → osm3d scene | parsers |
gtfs-rt-vehicles | GTFS-Realtime feed | VehiclePositions protobuf FeedMessage → live transit vehicle position table (entity/vehicle/route/trip id, lat/lon, bearing, speed, timestamp, stop) via a hand-rolled protobuf reader (no prost dep) → osm3d-flight scene | parsers |
trafikverket-trainpos | Trafikverket | POST XML <REQUEST> → JSON position table | parsers |
osm-geoparquet, osm-admin-boundaries | OSM bulk file | PBF → WKB ways / admin MultiPolygons (via osm-katana) | osm |
csv | flat file | RFC 4180 (+ ;/TAB, BOM, Latin-1, decimal-comma) → typed Arrow via a two-pass schema sniff | csv |
mainframe-copybook | flat file | IBM EBCDIC fixed-width decoded against a COBOL copybook (PIC X/9/S9V, COMP-3 packed, zoned, COMP binary) → Arrow (COMP-3 → Decimal128) | mainframe |
fixed-width | flat file | ASCII/Latin-1 fixed-width decoded against a COBOL copybook layout (DISPLAY usage) | mainframe |
copernicus-cds, dwd-icon | NetCDF/GRIB weather model | hand-rolled pure-Rust GRIB2 + NetCDF-classic decode → a variable/time/bbox metadata catalog (discipline/param, reference time, Ni/Nj, lat/lon bbox, num_points) AND the data-ARRAY unpack: GRIB2 Section 5/6/7 simple (5.0) + complex (5.2) + complex-with-spatial-differencing (5.3) packing with Section-6 bitmaps, NetCDF-classic variable arrays (fixed + record dims). params.mode="values" emits per-point grid values. Copernicus CDS (retrieve/v1, key from CDSAPI_KEY) + DWD ICON. Only NetCDF-4/HDF5 stays deferred | gribnc |
copernicus-era5, copernicus-cams | Copernicus reanalysis (value-first) | ERA5 climate reanalysis (CDS retrieve/v1, key from CDSAPI_KEY) + CAMS atmosphere/air-quality (ADS retrieve/v1, key from ADSAPI_KEY) → per-point grid VALUE rows by default (reuses the gribnc GRIB2 Section 5/6/7 + NetCDF value unpack, mode="values") | gribnc |
Everything parses in pure Rust → Arrow RecordBatch. The memory BatchSink
buffers batches for assertion; the skade-sink feature adds the Iceberg backend
- the path-agnostic
run()runner.
The RegionSource seam (feature osm)
pub trait RegionSource: Send {
/// Ensure the OSM source data for a named region is available locally,
/// FETCHING it if missing. Returns the local .osm.pbf path.
fn ensure(&self, region: &str) -> Result<std::path::PathBuf, String>;
fn name(&self) -> &str;
}
CachedLocalSource { dir }— resolves an already-present{region}-latest.osm.pbf(no fetch).HttpOsmSource { base_url, cache_dir }— the REAL connector: downloads{base_url}/{region}-latest.osm.pbf(Geofabrik-style) intocache_dir, skipping the download when already cached.
This is what lets a facett region picker be "fed by a connector fetching data" without facett depending on korp.
Features
parsers(default) — trait/registry surface + shared HTTP core + all light pure-Rust parsers + the pure OSM admin tag-filter/trie. No heavy geo dep.osm— the OSM geometry collectors (viaosm-katana) + theRegionSourceseam.csv— the RFC-4180 (+ dialects) CSV collector (csv1.4 +encoding_rs).mainframe— the EBCDIC + COBOL-copybook + COMP-3 fixed-width collector and its ASCII fixed-width cousin (fully hand-rolled, no new deps).gribnc— the NetCDF/GRIB weather-model collector (Copernicus CDS + DWD ICON): hand-rolled pure-Rust GRIB2 + NetCDF-classic decoders (no new deps) → a variable/time/bbox metadata catalog and the data-array unpack. GRIB2 Section 5/6/7 decodes simple (5.0), complex (5.2) and complex-with-spatial-differencing (5.3) packing with Section-6 bitmaps (a 0-bit is a missing point, never a fake 0.0); NetCDF-classic reads variable data arrays (fixed + record dims, numeric types). Only NetCDF-4/HDF5 stays deferred behind a would-be optional, non-default nativenetcdf(libnetcdf C) feature.skade-sink—BatchSink::iceberg+ therun()Iceberg runner (skade 0.5).znippy— the collected-data converter (znippy_pack): pack parsed Arrow batches (or raw byte blobs) into a znippy archive — compact, blake3-checksummed, OpenZL-compressed storage/transport — and read them back byte-identical. Reuses theznippy-commonpack format (no bespoke compression) +arrow-ipc.pack_tables/unpack_tablesfor namedRecordBatchstreams;pack_blobs/unpack_blobfor raw payloads.daemon— the collector daemon architecture (daemon.rs+iceberg_daemons.rs): the object-safe Config + State + Collector trio, theCollectorCtxseams (WarehouseSink, the GET/POSTFetcher, theSecretStore(API keys via an injectable seam — env default, vault later), the jera phase handle, the BIOS bus) and theOffline → Downloading → Indexing → Onlinestate machine (empty-is-RED →Failed). Each registered daemon self-registers its live phase + facet intocollector-register.redb(register.rs), so aRegisterReaderin ANOTHER process — korp's Collectors panel, orkorp-collectors status— sees it.daemon_registry()lists 21 warehouse-sink daemons under this feature (+gtfs-stopsundergtfs, +osm-cutunderosm-daemon), among them the Swedish real-time triopolisen-eventsandsmhi-weather(GET JSON) andtrafikverket-trainpos(a POST XML<REQUEST>with its key in the body). Pureparsers— no korp, no facett; a daemon runs standalone (produces Arrow to the warehouse with no korp server) and, when korp is present, is visible through the shared register.service/systemd— run a daemon (or a whole nursery of them) as its own supervised process / systemd unit (collector-<name>.service), via Skidbladnir; thekorp-collectorsbinary is the runner (serve/status/run/install).testmatrix— the tests'functional_statusself-reports become real nornir test-matrix rows. Compiled OUT of the default build (no nornir dep otherwise).
Test
cargo test # parsers (offline inject/assert)
cargo test --features osm # + OSM ways + RegionSource fetch/cache/parse
cargo test --features znippy # + znippy converter byte-identical round-trip
cargo test --all-features # + skade Iceberg round-trip + testmatrix
The RegionSource test spins an in-process HTTP server serving a bundled
454-byte .osm.pbf fixture, proves fetch → cache-hit (a request counter) →
osm-katana parses ≥1 real way. Deterministic, no external network.
License
MIT OR Apache-2.0.