gistools/readers/gtfs/schedule/stops.rs
1use crate::readers::csv::parse_csv_as_record;
2use alloc::{collections::BTreeMap, string::String};
3use s2json::{MValueCompatible, VectorFeature, VectorGeometry, VectorPoint};
4
5/// Properties object from GTFS stops
6#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
7pub struct GTFSStopProperties {
8 /// Stop ID
9 pub stop_id: String,
10 /// Stop code
11 pub stop_code: String,
12 /// Stop name
13 pub stop_name: String,
14 /// TTS stop name
15 pub tts_stop_name: String,
16 /// Stop description
17 pub stop_desc: String,
18 /// Stop zone id
19 pub zone_id: String,
20 /// Stop url
21 pub stop_url: String,
22 /// Location type
23 pub location_type: i8,
24 /// Parent station
25 pub parent_station: String,
26 /// Stop timezone
27 pub stop_timezone: String,
28 /// Wheelchair boarding
29 pub wheelchair_boarding: i8,
30 /// Level id
31 pub level_id: String,
32 /// Platform code
33 pub platform_code: String,
34}
35impl From<>FSStop> for GTFSStopProperties {
36 fn from(stop: >FSStop) -> Self {
37 GTFSStopProperties {
38 stop_id: stop.stop_id.clone(),
39 stop_code: stop.stop_code.clone(),
40 stop_name: stop.stop_name.clone(),
41 tts_stop_name: stop.tts_stop_name.clone(),
42 stop_desc: stop.stop_desc.clone(),
43 zone_id: stop.zone_id.clone(),
44 stop_url: stop.stop_url.clone(),
45 location_type: stop.location_type.unwrap_or(0),
46 parent_station: stop.parent_station.clone(),
47 stop_timezone: stop.stop_timezone.clone(),
48 wheelchair_boarding: stop.wheelchair_boarding.unwrap_or(0),
49 level_id: stop.level_id.clone(),
50 platform_code: stop.platform_code.clone(),
51 }
52 }
53}
54
55/// Location type. Valid options:
56/// - 0 or empty = Stop/Platform,
57/// - 1 = Station,
58/// - 2 = Entrance/Exit,
59/// - 3 = Generic Node,
60/// - 4 = Boarding Area.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
62pub enum GTFSStopLocationType {
63 /// Stop
64 Stop = 0,
65 /// Station
66 Station = 1,
67 /// Entrance
68 Entrance = 2,
69 /// Generic Node
70 GenericNode = 3,
71 /// Boarding Area
72 BoardingArea = 4,
73}
74impl From<i8> for GTFSStopLocationType {
75 fn from(value: i8) -> Self {
76 match value {
77 1 => GTFSStopLocationType::Station,
78 2 => GTFSStopLocationType::Entrance,
79 3 => GTFSStopLocationType::GenericNode,
80 4 => GTFSStopLocationType::BoardingArea,
81 _ => GTFSStopLocationType::Stop,
82 }
83 }
84}
85
86/// # Stop Information
87///
88/// ## Details
89/// **Conditionally Required** - Stops where vehicles pick up or drop off riders.
90/// Also defines stations, entrances, etc.
91#[derive(Debug, Default, Clone, PartialEq, MValueCompatible)]
92pub struct GTFSStop {
93 /// **Required**
94 /// Identifies a location: stop/platform, station, entrance/exit, generic node, or boarding area.
95 /// Must be unique across:
96 /// - stops.stop_id
97 /// - locations.geojson id
98 /// - location_groups.location_group_id
99 ///
100 /// Multiple routes may use the same `stop_id`.
101 pub stop_id: String,
102 /// **Optional**
103 /// Short text or a number that identifies the location for riders.
104 pub stop_code: String,
105 /// **Conditionally Required**
106 /// Name of the location. Required if `location_type` is 0, 1, or 2. Optional otherwise.
107 pub stop_name: String,
108 /// **Optional**
109 /// Readable version of the stop_name for text-to-speech systems.
110 pub tts_stop_name: String,
111 /// **Optional**
112 /// Description providing useful information about the location.
113 /// Should not be a duplicate of `name`.
114 pub stop_desc: String,
115 /// **Conditionally Required**
116 /// Latitude of the location. Required if `location_type` is 0, 1, or 2. Optional otherwise.
117 pub stop_lat: Option<f64>,
118 /// **Conditionally Required**
119 /// Longitude of the location. Required if `location_type` is 0, 1, or 2. Optional otherwise.
120 pub stop_lon: Option<f64>,
121 /// **Optional**
122 /// Identifies the fare zone for a stop.
123 pub zone_id: String,
124 /// **Optional**
125 /// URL of a web page about this location.
126 pub stop_url: String,
127 /// **Optional**
128 /// Location type. Valid options:
129 /// 0 or empty = Stop/Platform,
130 /// 1 = Station,
131 /// 2 = Entrance/Exit,
132 /// 3 = Generic Node,
133 /// 4 = Boarding Area.
134 pub location_type: Option<i8>,
135 /// **Conditionally Required**
136 /// Defines hierarchy between different locations. Required if `location_type` is 2, 3, or 4.
137 pub parent_station: String,
138 /// **Optional**
139 /// Timezone of the location. Inherits from parent station if not specified.
140 pub stop_timezone: String,
141 /// **Optional**
142 /// Indicates whether wheelchair boardings are possible at this location.
143 /// For parentless stops: 0 = no info, 1 = possible, 2 = not possible.
144 /// For child stops, entrance/exits: inherits or overrides parent station accessibility.
145 pub wheelchair_boarding: Option<i8>,
146 /// **Optional**
147 /// Level of the location. References levels.level_id.
148 pub level_id: String,
149 /// **Optional**
150 /// Platform identifier for a platform stop.
151 pub platform_code: String,
152}
153impl GTFSStop {
154 /// Create a new GTFSStop
155 pub fn new(source: &str) -> BTreeMap<String, GTFSStop> {
156 let mut res = BTreeMap::new();
157 for record in parse_csv_as_record::<GTFSStop>(source, None, None) {
158 res.insert(record.stop_id.clone(), record);
159 }
160 res
161 }
162 /// Get the location_type
163 pub fn get_location_type(&self) -> Option<GTFSStopLocationType> {
164 self.location_type.map(GTFSStopLocationType::from)
165 }
166 /// Convert to a feature
167 pub fn to_feature(&self) -> Option<VectorFeature> {
168 if self.stop_lon.is_none() || self.stop_lat.is_none() {
169 return None;
170 }
171 let properties: GTFSStopProperties = self.into();
172 Some(VectorFeature {
173 properties: properties.into(),
174 geometry: VectorGeometry::new_point(
175 VectorPoint::new_xy(self.stop_lon.unwrap(), self.stop_lat.unwrap(), None),
176 None,
177 ),
178 ..Default::default()
179 })
180 }
181}