gistools/readers/gtfs/schedule/
fare_transfer_rules.rs

1use crate::readers::csv::parse_csv_as_record;
2use alloc::{string::String, vec::Vec};
3use s2json::MValueCompatible;
4
5/// Duration limit type for how transfer durations are measured.
6/// Required if `duration_limit` is defined, forbidden otherwise.
7///
8/// 0 - Between departure of current leg & arrival of next leg
9/// 1 - Between departure of current leg & departure of next leg
10/// 2 - Between arrival of current leg & departure of next leg
11/// 3 - Between arrival of current leg & arrival of next leg
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
13pub enum GTFSDurationLimitType {
14    /// Between departure of current leg & arrival of next leg
15    DepCurrentArrNext = 0,
16    /// Between departure of current leg & departure of next leg
17    DepCurrentDepNext = 1,
18    /// Between arrival of current leg & departure of next leg
19    ArrCurrentDepNext = 2,
20    /// Between arrival of current leg & arrival of next leg
21    ArrCurrentArrNext = 3,
22}
23impl From<i8> for GTFSDurationLimitType {
24    fn from(s: i8) -> Self {
25        match s {
26            1 => GTFSDurationLimitType::DepCurrentDepNext,
27            2 => GTFSDurationLimitType::ArrCurrentDepNext,
28            3 => GTFSDurationLimitType::ArrCurrentArrNext,
29            _ => GTFSDurationLimitType::DepCurrentArrNext,
30        }
31    }
32}
33
34/// Fare transfer type describing how costs are processed between consecutive legs:
35///
36/// 0 = (A) + (AB)
37/// 1 = (A) + (AB) + (B)
38/// 2 = (AB)
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
40pub enum GTFSFareTransferType {
41    /// A + AB
42    FromLegPlusTransfer = 0, // A + AB
43    /// A + AB + B
44    FromLegTransferToLeg = 1, // A + AB + B
45    /// AB
46    TransferOnly = 2, // AB
47}
48impl From<i8> for GTFSFareTransferType {
49    fn from(s: i8) -> Self {
50        match s {
51            1 => GTFSFareTransferType::FromLegTransferToLeg,
52            2 => GTFSFareTransferType::TransferOnly,
53            _ => GTFSFareTransferType::FromLegPlusTransfer,
54        }
55    }
56}
57
58/// # Fare Transfer Rules
59///
60/// **Optional**
61/// Defines the cost of transferring between fare legs specified in `fare_leg_rules.txt`.
62/// Matching uses:
63/// - from_leg_group_id
64/// - to_leg_group_id
65/// - transfer_count
66/// - duration_limit
67/// - duration_limit_type
68/// - fare_transfer_type
69/// - fare_product_id
70///
71/// **Primary Key**: (from_leg_group_id, to_leg_group_id, fare_product_id, transfer_count, duration_limit)
72#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
73pub struct GTFSFareTransferRule {
74    /// **Optional**
75    /// The pre-transfer fare leg group (`fare_leg_rules.leg_group_id`).
76    /// - If no exact match is found, empty corresponds to all leg groups not listed under `from_leg_group_id`.
77    pub from_leg_group_id: Option<String>,
78    /// **Optional**
79    /// The post-transfer fare leg group (`fare_leg_rules.leg_group_id`).
80    /// - If no exact match is found, empty corresponds to all leg groups not listed under `to_leg_group_id`.
81    pub to_leg_group_id: Option<String>,
82    /// **Conditionally Forbidden / Required**
83    /// Defines how many consecutive transfers this rule may be applied to.
84    /// - `-1` means no limit.
85    /// - `1` or more = the transfer count this rule applies to.
86    ///
87    /// Forbidden if `from_leg_group_id !== to_leg_group_id`.
88    /// Required if `from_leg_group_id === to_leg_group_id`.
89    pub transfer_count: Option<i32>,
90    /// **Optional**
91    /// Duration limit (in seconds) for the transfer. Empty means no limit.
92    pub duration_limit: Option<i32>,
93    /// **Conditionally Required**
94    /// Defines how to measure the `durationLimit`.
95    /// - Required if `durationLimit` is defined.
96    /// - Forbidden if `durationLimit` is empty.
97    pub duration_limit_type: Option<i8>, // ?: GTFSDurationLimitType;
98    /// **Required**
99    /// Indicates how to combine transfer costs:
100    /// - 0 = from-leg cost + transfer cost
101    /// - 1 = from-leg + transfer + to-leg cost
102    /// - 2 = transfer cost only
103    pub fare_transfer_type: i8, // GTFSFareTransferType;
104    /// **Optional**
105    /// Fare product ID for the transfer. If empty, cost is 0 (no transfer cost).
106    pub fare_product_id: Option<String>,
107}
108impl GTFSFareTransferRule {
109    /// Create a new GTFSFareTransferRule
110    pub fn new(source: &str) -> Vec<GTFSFareTransferRule> {
111        let mut res = Vec::new();
112        for record in parse_csv_as_record::<GTFSFareTransferRule>(source, None, None) {
113            res.push(record);
114        }
115        res
116    }
117    /// Get the duration_limit_type
118    pub fn duration_limit_type(&self) -> Option<GTFSDurationLimitType> {
119        self.duration_limit_type.map(GTFSDurationLimitType::from)
120    }
121    /// Get the fare_transfer_type
122    pub fn fare_transfer_type(&self) -> GTFSFareTransferType {
123        self.fare_transfer_type.into()
124    }
125}