From 4debdb1e1d30773a332dd17e6145f041b4728e57 Mon Sep 17 00:00:00 2001 From: antoine-de Date: Wed, 7 Feb 2024 12:13:49 +0100 Subject: [PATCH] Implement Fares v1 Implement Fares v1 as defined in https://gtfs.org/schedule/reference/#fare_attributestxt There was already a FareAttribute, but no matching FareRule, so this adds FareRule. Fare v2 can also be implemented later if needed. I did not try to be clever in the final `Gtfs` modelization as it is not really needed for now, but maybe later we'll need to change the model a bit. --- Cargo.toml | 2 +- fixtures/fares_v1/agency.txt | 1 + fixtures/fares_v1/fare_attributes.txt | 2 ++ fixtures/fares_v1/fare_rules.txt | 3 ++ fixtures/fares_v1/readme.md | 1 + fixtures/fares_v1/routes.txt | 3 ++ fixtures/fares_v1/stop_times.txt | 1 + fixtures/fares_v1/stops.txt | 3 ++ fixtures/fares_v1/transfers.txt | 3 ++ fixtures/fares_v1/trips.txt | 1 + src/enums.rs | 2 +- src/gtfs.rs | 8 +++++ src/gtfs_reader.rs | 3 ++ src/objects.rs | 17 ++++++++++- src/raw_gtfs.rs | 2 ++ src/tests.rs | 44 +++++++++++++++++++++++++++ 16 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 fixtures/fares_v1/agency.txt create mode 100644 fixtures/fares_v1/fare_attributes.txt create mode 100644 fixtures/fares_v1/fare_rules.txt create mode 100644 fixtures/fares_v1/readme.md create mode 100644 fixtures/fares_v1/routes.txt create mode 100644 fixtures/fares_v1/stop_times.txt create mode 100644 fixtures/fares_v1/stops.txt create mode 100644 fixtures/fares_v1/transfers.txt create mode 100644 fixtures/fares_v1/trips.txt diff --git a/Cargo.toml b/Cargo.toml index 07d1c04..be78d54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] description = "Read GTFS (public transit timetables) files" name = "gtfs-structures" -version = "0.39.1" +version = "0.40.0" authors = ["Tristram Gräbener ", "Antoine Desbordes "] repository = "https://github.com/rust-transit/gtfs-structure" license = "MIT" diff --git a/fixtures/fares_v1/agency.txt b/fixtures/fares_v1/agency.txt new file mode 100644 index 0000000..e180ed1 --- /dev/null +++ b/fixtures/fares_v1/agency.txt @@ -0,0 +1 @@ +agency_name,agency_url,agency_timezone,agency_lang \ No newline at end of file diff --git a/fixtures/fares_v1/fare_attributes.txt b/fixtures/fares_v1/fare_attributes.txt new file mode 100644 index 0000000..168d867 --- /dev/null +++ b/fixtures/fares_v1/fare_attributes.txt @@ -0,0 +1,2 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +presto_fare,3.2,CAD,1,,7200 \ No newline at end of file diff --git a/fixtures/fares_v1/fare_rules.txt b/fixtures/fares_v1/fare_rules.txt new file mode 100644 index 0000000..2f1cd7d --- /dev/null +++ b/fixtures/fares_v1/fare_rules.txt @@ -0,0 +1,3 @@ +fare_id,route_id,origin_id,destination_id +presto_fare,line1,ttc_subway_stations,ttc_subway_stations +presto_fare,line2,ttc_subway_stations,ttc_subway_stations \ No newline at end of file diff --git a/fixtures/fares_v1/readme.md b/fixtures/fares_v1/readme.md new file mode 100644 index 0000000..aa3fd16 --- /dev/null +++ b/fixtures/fares_v1/readme.md @@ -0,0 +1 @@ +Dataset constructed from the [GTFS fares v1 example](https://gtfs.org/schedule/examples/fares-v1/). diff --git a/fixtures/fares_v1/routes.txt b/fixtures/fares_v1/routes.txt new file mode 100644 index 0000000..fe6383a --- /dev/null +++ b/fixtures/fares_v1/routes.txt @@ -0,0 +1,3 @@ +agency_id,route_id,route_type +TTC,Line1,1 +TTC,Line2,1 \ No newline at end of file diff --git a/fixtures/fares_v1/stop_times.txt b/fixtures/fares_v1/stop_times.txt new file mode 100644 index 0000000..5c62c72 --- /dev/null +++ b/fixtures/fares_v1/stop_times.txt @@ -0,0 +1 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_time_desc,pickup_type,drop_off_type,timepoint \ No newline at end of file diff --git a/fixtures/fares_v1/stops.txt b/fixtures/fares_v1/stops.txt new file mode 100644 index 0000000..ad2343b --- /dev/null +++ b/fixtures/fares_v1/stops.txt @@ -0,0 +1,3 @@ +stop_id,stop_name,stop_lat,stop_lon,zone_id +Bloor,Bloor Station,,43.670049,-79.385389,ttc_subway_stations +Yonge,Yonge Station,,43.671049,-79.386789,ttc_subway_stations \ No newline at end of file diff --git a/fixtures/fares_v1/transfers.txt b/fixtures/fares_v1/transfers.txt new file mode 100644 index 0000000..65ed790 --- /dev/null +++ b/fixtures/fares_v1/transfers.txt @@ -0,0 +1,3 @@ +from_stop_id,to_stop_id,from_route_id,to_route_id,transfer_type +Bloor,Yonge,line1,line2,0 +Yonge,Bloor,line2,line1,0 \ No newline at end of file diff --git a/fixtures/fares_v1/trips.txt b/fixtures/fares_v1/trips.txt new file mode 100644 index 0000000..357b85f --- /dev/null +++ b/fixtures/fares_v1/trips.txt @@ -0,0 +1 @@ +route_id,service_id,trip_id,trip_headsign,trip_short_name,direction_id,block_id,wheelchair_accessible,bikes_allowed,trip_desc,shape_id diff --git a/src/enums.rs b/src/enums.rs index 21befa8..408f3d1 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -23,7 +23,7 @@ pub enum ObjectType { } /// Describes the kind of [Stop]. See `location_type` -#[derive(Derivative, Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Derivative, Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derivative(Default(bound = ""))] pub enum LocationType { /// Stop (or Platform). A location where passengers board or disembark from a transit vehicle. Is called a platform when defined within a parent_station diff --git a/src/gtfs.rs b/src/gtfs.rs index 99f8bbc..1e0bef9 100644 --- a/src/gtfs.rs +++ b/src/gtfs.rs @@ -39,6 +39,8 @@ pub struct Gtfs { pub shapes: HashMap>, /// All fare attributes by `fare_id` pub fare_attributes: HashMap, + /// All fare rules by `fare_id` + pub fare_rules: HashMap>, /// All feed information. There is no identifier pub feed_info: Vec, } @@ -57,6 +59,11 @@ impl TryFrom for Gtfs { let frequencies = raw.frequencies.unwrap_or_else(|| Ok(Vec::new()))?; let trips = create_trips(raw.trips?, raw.stop_times?, frequencies, &stops)?; + let mut fare_rules = HashMap::>::new(); + for f in raw.fare_rules.unwrap_or_else(|| Ok(Vec::new()))? { + (*fare_rules.entry(f.fare_id.clone()).or_default()).push(f); + } + Ok(Gtfs { stops, routes: to_map(raw.routes?), @@ -64,6 +71,7 @@ impl TryFrom for Gtfs { agencies: raw.agencies?, shapes: to_shape_map(raw.shapes.unwrap_or_else(|| Ok(Vec::new()))?), fare_attributes: to_map(raw.fare_attributes.unwrap_or_else(|| Ok(Vec::new()))?), + fare_rules, feed_info: raw.feed_info.unwrap_or_else(|| Ok(Vec::new()))?, calendar: to_map(raw.calendar.unwrap_or_else(|| Ok(Vec::new()))?), calendar_dates: to_calendar_dates( diff --git a/src/gtfs_reader.rs b/src/gtfs_reader.rs index 0e391ea..f35f0fb 100644 --- a/src/gtfs_reader.rs +++ b/src/gtfs_reader.rs @@ -176,6 +176,7 @@ impl RawGtfsReader { agencies: self.read_objs_from_path(p.join("agency.txt")), shapes: self.read_objs_from_optional_path(p, "shapes.txt"), fare_attributes: self.read_objs_from_optional_path(p, "fare_attributes.txt"), + fare_rules: self.read_objs_from_optional_path(p, "fare_rules.txt"), frequencies: self.read_objs_from_optional_path(p, "frequencies.txt"), transfers: self.read_objs_from_optional_path(p, "transfers.txt"), pathways: self.read_objs_from_optional_path(p, "pathways.txt"), @@ -262,6 +263,7 @@ impl RawGtfsReader { "stop_times.txt", "trips.txt", "fare_attributes.txt", + "fare_rules.txt", "frequencies.txt", "transfers.txt", "pathways.txt", @@ -297,6 +299,7 @@ impl RawGtfsReader { &mut archive, "fare_attributes.txt", ), + fare_rules: self.read_optional_file(&file_mapping, &mut archive, "fare_rules.txt"), frequencies: self.read_optional_file(&file_mapping, &mut archive, "frequencies.txt"), transfers: self.read_optional_file(&file_mapping, &mut archive, "transfers.txt"), pathways: self.read_optional_file(&file_mapping, &mut archive, "pathways.txt"), diff --git a/src/objects.rs b/src/objects.rs index 9c65582..00d59f7 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -564,7 +564,7 @@ impl Id for Shape { } /// Defines one possible fare. See -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] pub struct FareAttribute { /// Unique technical (not for the traveller) identifier for the FareAttribute #[serde(rename = "fare_id")] @@ -596,6 +596,21 @@ impl Type for FareAttribute { } } +/// Defines one possible fare. See +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct FareRule { + /// ID of the referenced FareAttribute. + pub fare_id: String, + /// ID of a [Route] associated with the fare class + pub route_id: Option, + /// Identifies an origin zone. References a [Stop].zone_id + pub origin_id: Option, + /// Identifies an destination zone. References a [Stop].zone_id + pub destination_id: Option, + /// Identifies the zones that a rider will enter while using a given fare class. References a [Stop].zone_id + pub contains_id: Option, +} + /// A [Frequency] before being merged into the corresponding [Trip] #[derive(Debug, Serialize, Deserialize, Default)] pub struct RawFrequency { diff --git a/src/raw_gtfs.rs b/src/raw_gtfs.rs index 0efad21..12e6695 100644 --- a/src/raw_gtfs.rs +++ b/src/raw_gtfs.rs @@ -27,6 +27,8 @@ pub struct RawGtfs { pub shapes: Option, Error>>, /// All FareAttribates, None if the file was absent as it is not mandatory pub fare_attributes: Option, Error>>, + /// All FareRules, None if the file was absent as it is not mandatory + pub fare_rules: Option, Error>>, /// All Frequencies, None if the file was absent as it is not mandatory pub frequencies: Option, Error>>, /// All Transfers, None if the file was absent as it is not mandatory diff --git a/src/tests.rs b/src/tests.rs index 82251ef..f5d9fbe 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use crate::objects::*; use crate::Gtfs; use crate::RawGtfs; @@ -470,3 +472,45 @@ fn sorted_shapes() { ] ); } + +#[test] +fn fare_v1() { + let gtfs = Gtfs::from_path("fixtures/fares_v1").expect("impossible to read gtfs"); + + let mut expected_attributes = HashMap::new(); + expected_attributes.insert( + "presto_fare".to_string(), + FareAttribute { + id: "presto_fare".to_string(), + currency: "CAD".to_string(), + price: "3.2".to_string(), + payment_method: PaymentMethod::PreBoarding, + transfer_duration: Some(7200), + agency_id: None, + transfers: Transfers::Unlimited, + }, + ); + assert_eq!(gtfs.fare_attributes, expected_attributes); + + let mut expected_rules = HashMap::new(); + expected_rules.insert( + "presto_fare".to_string(), + vec![ + FareRule { + fare_id: "presto_fare".to_string(), + route_id: Some("line1".to_string()), + origin_id: Some("ttc_subway_stations".to_string()), + destination_id: Some("ttc_subway_stations".to_string()), + contains_id: None, + }, + FareRule { + fare_id: "presto_fare".to_string(), + route_id: Some("line2".to_string()), + origin_id: Some("ttc_subway_stations".to_string()), + destination_id: Some("ttc_subway_stations".to_string()), + contains_id: None, + }, + ], + ); + assert_eq!(gtfs.fare_rules, expected_rules); +}