Skip to content

Commit ea5cfaf

Browse files
Merge pull request #70 from NREL/rjf/gbfs-cli
scaffold a CLI for bambam GBFS downloads
2 parents b2df19c + 40882fb commit ea5cfaf

File tree

9 files changed

+168
-1
lines changed

9 files changed

+168
-1
lines changed

rust/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ geozero = { version = "0.14.0", features = ["with-wkb"] }
3434
gtfs-structures = "0.43.0"
3535
h3o = { version = "0.8.0", features = ["serde", "geo"] }
3636
hex = "0.4.3"
37+
humantime = "2.3.0"
3738
inventory = { version = "0.3.21" }
3839
itertools = { version = "0.14.0" }
3940
jsonpath-rust = { version = "1.0.4" }
@@ -52,6 +53,7 @@ rstar = { version = "0.12.0" }
5253
serde = { version = "1.0.160", features = ["derive"] }
5354
serde_json = { version = "1.0" }
5455
serde_arrow = { version = "0.13.7", features = ["arrow-55"] }
56+
serde_with = { version = "3.0", features = ["chrono_0_4"] }
5557
shapefile = { version = "0.7.0", features = ["geo-types"] }
5658
skiplist = "0.5.1"
5759
thiserror = "2.0.12"

rust/bambam-gbfs/Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ license = "BSD-3-Clause"
66
description = "GBFS Extensions for The Behavior and Advanced Mobility Big Access Model"
77

88
[dependencies]
9+
env_logger = { workspace = true }
910
routee-compass-core = { workspace = true }
1011
serde = { workspace = true }
1112
serde_json = { workspace = true }
13+
serde_with = { workspace = true }
14+
chrono = { workspace = true }
15+
humantime = { workspace = true }
16+
log = { workspace = true }
1217
geo = { workspace = true }
13-
reqwest = { workspace = true }
18+
reqwest = { workspace = true }
19+
clap = { workspace = true }
20+
kdam = { workspace = true }

rust/bambam-gbfs/README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# bambam-gbfs
2+
3+
tooling for building and running BAMBAM models from GBFS sources.
4+
5+
## CLI Usage
6+
7+
example CLI calls:
8+
9+
### top-level GBFS CLI
10+
11+
```
12+
% ./target/release/bambam-gbfs
13+
GBFS Extensions for The Behavior and Advanced Mobility Big Access Model
14+
15+
Usage: bambam-gbfs <COMMAND>
16+
17+
Commands:
18+
download runs a GBFS download, writing data from some source URL to an output directory
19+
help Print this message or the help of the given subcommand(s)
20+
21+
Options:
22+
-h, --help Print help
23+
-V, --version Print version
24+
```
25+
26+
### GBFS download command
27+
28+
```
29+
% cd rust
30+
% cargo build -r
31+
% ./target/release/bambam-gbfs download --help
32+
runs a GBFS download, writing data from some source URL to an output directory
33+
34+
Usage: bambam-gbfs download [OPTIONS] --gbfs-url <GBFS_URL>
35+
36+
Options:
37+
-g, --gbfs-url <GBFS_URL>
38+
a GBFS API URL
39+
-o, --output-directory <OUTPUT_DIRECTORY>
40+
output directory path [default: .]
41+
-c, --collect-duration <COLLECT_DURATION>
42+
duration to collect data rows. provide in human-readable time values 2m, 30s, 2h, 2days... [default: 10m]
43+
-h, --help
44+
Print help
45+
-V, --version
46+
Print version
47+
```
48+
49+
### running GBFS download with arguments
50+
51+
this isn't yet implemented so with debug logging enabled, the command will run the download function, log the arguments, then panic when it hits the todo!() line.
52+
53+
```
54+
% RUST_LOG=debug ./target/release/bambam-gbfs download -g https://example.com
55+
[2025-12-22T17:15:15Z DEBUG bambam_gbfs::app::download::run] run_gbfs_download with url=https://example.com, out_dir=".", duration (seconds)=600
56+
57+
thread 'main' (2745448) panicked at bambam-gbfs/src/app/download/run.rs:20:5:
58+
not yet implemented: download + post-processing logic
59+
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
60+
```
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod run;
2+
3+
pub use run::run_gbfs_download;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use std::path::Path;
2+
3+
use chrono::TimeDelta;
4+
5+
/// downloads GBFS data for some duration. aggregates the resulting rows and writes them
6+
/// to files to be consumed by BAMBAM.
7+
///
8+
/// # Arguments
9+
/// * url - URL to the GBFS dataset
10+
/// * out_dir - output directory to write the processed GBFS data
11+
/// * dur - how long to poll the GBFS API
12+
///
13+
/// # Result
14+
/// If successful, returns nothing, otherwise an error
15+
pub fn run_gbfs_download(url: &str, out_dir: &Path, dur: &TimeDelta) -> Result<(), String> {
16+
let dur_secs = dur.as_seconds_f64();
17+
log::debug!(
18+
"run_gbfs_download with url={url}, out_dir={out_dir:?}, duration (seconds)={dur_secs}"
19+
);
20+
todo!("download + post-processing logic")
21+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::path::Path;
2+
3+
use chrono::TimeDelta;
4+
use clap::{Parser, Subcommand};
5+
use serde::{Deserialize, Serialize};
6+
7+
/// command line tool providing GBFS processing scripts
8+
#[derive(Parser)]
9+
#[command(author, version, about, long_about = None)]
10+
#[command(propagate_version = true)]
11+
pub struct GbfsCliArguments {
12+
/// select the GBFS operation to run
13+
#[command(subcommand)]
14+
pub op: GbfsOperation,
15+
}
16+
17+
#[derive(Debug, Clone, Serialize, Deserialize, Subcommand)]
18+
pub enum GbfsOperation {
19+
/// runs a GBFS download, writing data from some source URL
20+
/// to an output directory.
21+
Download {
22+
/// a GBFS API URL
23+
#[arg(short, long)]
24+
gbfs_url: String,
25+
/// output directory path.
26+
#[arg(short, long, default_value_t = String::from("."))]
27+
output_directory: String,
28+
/// duration to collect data rows. provide in human-readable time values
29+
/// 2m, 30s, 2h, 2days...
30+
#[arg(short, long, value_parser = parse_duration, default_value = "10m")]
31+
collect_duration: TimeDelta,
32+
},
33+
}
34+
35+
impl GbfsOperation {
36+
pub fn run(&self) -> Result<(), String> {
37+
match self {
38+
GbfsOperation::Download {
39+
gbfs_url,
40+
output_directory,
41+
collect_duration,
42+
} => crate::app::download::run_gbfs_download(
43+
gbfs_url,
44+
Path::new(output_directory),
45+
collect_duration,
46+
),
47+
}
48+
}
49+
}
50+
51+
fn parse_duration(s: &str) -> Result<chrono::TimeDelta, String> {
52+
let std_duration =
53+
humantime::parse_duration(s).map_err(|e| format!("Invalid duration: {}", e))?;
54+
chrono::TimeDelta::from_std(std_duration).map_err(|e| format!("TimeDelta out of range: {}", e))
55+
}

rust/bambam-gbfs/src/app/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
mod gbfs_cli;
2+
3+
pub mod download;
4+
pub use gbfs_cli::GbfsCliArguments;

rust/bambam-gbfs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod app;
12
pub mod model;

rust/bambam-gbfs/src/main.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use bambam_gbfs::app::GbfsCliArguments;
2+
use clap::Parser;
3+
4+
fn main() {
5+
env_logger::init();
6+
let args = GbfsCliArguments::parse();
7+
match args.op.run() {
8+
Ok(_) => log::info!("finished."),
9+
Err(e) => {
10+
log::error!("failed running bambam_gbfs: {e}");
11+
std::process::exit(1);
12+
}
13+
}
14+
}

0 commit comments

Comments
 (0)