From e498ab8f2c8d8de62cb3d4f750f5458e45e2b69d Mon Sep 17 00:00:00 2001 From: meszarosdezso Date: Wed, 16 Mar 2022 09:42:23 +0100 Subject: [PATCH] Add support for pathways.txt --- fixtures/basic/pathways.txt | 2 + src/enums.rs | 43 +++++++++++++++++++ src/gtfs.rs | 16 ++++++- src/gtfs_reader.rs | 3 ++ src/objects.rs | 85 +++++++++++++++++++++++++++++++++++++ src/raw_gtfs.rs | 5 ++- src/tests.rs | 18 +++++++- 7 files changed, 169 insertions(+), 3 deletions(-) create mode 100644 fixtures/basic/pathways.txt diff --git a/fixtures/basic/pathways.txt b/fixtures/basic/pathways.txt new file mode 100644 index 0000000..bbd8e58 --- /dev/null +++ b/fixtures/basic/pathways.txt @@ -0,0 +1,2 @@ +pathway_id,from_stop_id,to_stop_id,mode,is_bidirectional,length,traversal_time,stair_count,max_slope,min_width,signposted_as,reversed_signposted_as +pathway1,stop1,stop3,1,0,,,,,,, diff --git a/src/enums.rs b/src/enums.rs index 48204f9..299ad13 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -523,3 +523,46 @@ pub enum TransferType { #[serde(rename = "3")] Impossible, } + +/// Type of pathway between [from_stop] and [to_stop] +#[derive(Debug, Serialize, Deserialize, Derivative, Copy, Clone, PartialEq)] +#[derivative(Default)] +pub enum PathwayMode { + /// A walkway + #[serde(rename = "1")] + #[derivative(Default)] + Walkway, + /// Stairs + #[serde(rename = "2")] + Stairs, + /// Moving sidewalk / travelator + #[serde(rename = "3")] + MovingSidewalk, + /// Escalator + #[serde(rename = "4")] + Escalator, + /// Elevator + #[serde(rename = "5")] + Elevator, + /// A pathway that crosses into an area of the station where a + /// proof of payment is required (usually via a physical payment gate) + #[serde(rename = "6")] + FareGate, + /// Indicates a pathway exiting an area where proof-of-payment is required + /// into an area where proof-of-payment is no longer required. + #[serde(rename = "7")] + ExitGate, +} + +/// Indicates in which direction the pathway can be used +#[derive(Debug, Serialize, Deserialize, Derivative, Copy, Clone, PartialEq)] +#[derivative(Default)] +pub enum PathwayDirectionType { + /// Unidirectional pathway, it can only be used from [from_stop_id] to [to_stop_id]. + #[serde(rename = "0")] + #[derivative(Default)] + Unidirectional, + /// Bidirectional pathway, it can be used in the two directions. + #[serde(rename = "1")] + Bidirectional, +} diff --git a/src/gtfs.rs b/src/gtfs.rs index 167ca7a..6859ea1 100644 --- a/src/gtfs.rs +++ b/src/gtfs.rs @@ -49,7 +49,11 @@ impl TryFrom for Gtfs { /// /// It might fail if some mandatory files couldn’t be read or if there are references to other objects that are invalid. fn try_from(raw: RawGtfs) -> Result { - let stops = to_stop_map(raw.stops?, raw.transfers.unwrap_or_else(|| Ok(Vec::new()))?)?; + let stops = to_stop_map( + raw.stops?, + raw.transfers.unwrap_or_else(|| Ok(Vec::new()))?, + raw.pathways.unwrap_or(Ok(Vec::new()))?, + )?; let frequencies = raw.frequencies.unwrap_or_else(|| Ok(Vec::new()))?; let trips = create_trips(raw.trips?, raw.stop_times?, frequencies, &stops)?; @@ -231,6 +235,7 @@ fn to_map(elements: impl IntoIterator) -> HashMap { fn to_stop_map( stops: Vec, raw_transfers: Vec, + raw_pathways: Vec, ) -> Result>, Error> { let mut stop_map: HashMap = stops.into_iter().map(|s| (s.id.clone(), s)).collect(); @@ -244,6 +249,15 @@ fn to_stop_map( .and_modify(|stop| stop.transfers.push(StopTransfer::from(transfer))); } + for pathway in raw_pathways { + stop_map + .get(&pathway.to_stop_id) + .ok_or_else(|| Error::ReferenceError(pathway.to_stop_id.to_string()))?; + stop_map + .entry(pathway.from_stop_id.clone()) + .and_modify(|stop| stop.pathways.push(Pathway::from(pathway))); + } + let res = stop_map .into_iter() .map(|(i, s)| (i, Arc::new(s))) diff --git a/src/gtfs_reader.rs b/src/gtfs_reader.rs index 1211542..dfb7b43 100644 --- a/src/gtfs_reader.rs +++ b/src/gtfs_reader.rs @@ -160,6 +160,7 @@ impl RawGtfsReader { fare_attributes: self.read_objs_from_optional_path(p, "fare_attributes.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"), feed_info: self.read_objs_from_optional_path(p, "feed_info.txt"), read_duration: Utc::now().signed_duration_since(now).num_milliseconds(), files, @@ -244,6 +245,7 @@ impl RawGtfsReader { "fare_attributes.txt", "frequencies.txt", "transfers.txt", + "pathways.txt", "feed_info.txt", "shapes.txt", ] { @@ -278,6 +280,7 @@ impl RawGtfsReader { ), 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"), feed_info: self.read_optional_file(&file_mapping, &mut archive, "feed_info.txt"), shapes: self.read_optional_file(&file_mapping, &mut archive, "shapes.txt"), read_duration: Utc::now().signed_duration_since(now).num_milliseconds(), diff --git a/src/objects.rs b/src/objects.rs index 75d76ba..f5722e3 100644 --- a/src/objects.rs +++ b/src/objects.rs @@ -188,6 +188,9 @@ pub struct Stop { /// Transfers from this Stop #[serde(skip)] pub transfers: Vec, + /// Pathways from this stop + #[serde(skip)] + pub pathways: Vec, } impl Type for Stop { @@ -718,3 +721,85 @@ impl fmt::Display for FeedInfo { write!(f, "{}", self.name) } } + +/// A graph representation to describe subway or train, with nodes (the locations) and edges (the pathways). +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct RawPathway { + /// Uniquely identifies the pathway + #[serde(rename = "pathway_id")] + pub id: String, + /// Location at which the pathway begins + pub from_stop_id: String, + /// Location at which the pathway ends + pub to_stop_id: String, + /// Type of pathway between the specified (from_stop_id, to_stop_id) pair + pub mode: PathwayMode, + /// Indicates in which direction the pathway can be used + pub is_bidirectional: PathwayDirectionType, + /// Horizontal length in meters of the pathway from the origin location to the destination location + pub length: Option, + /// Average time in seconds needed to walk through the pathway from the origin location to the destination location + pub traversal_time: Option, + /// Number of stairs of the pathway + pub stair_count: Option, + /// Maximum slope ratio of the pathway + pub max_slope: Option, + /// Minimum width of the pathway in meters + pub min_width: Option, + /// String of text from physical signage visible to transit riders + pub signposted_as: Option, + /// Same than the signposted_as field, but when the pathways is used backward + pub reversed_signposted_as: Option, +} + +/// Pathway going from a stop to another. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct Pathway { + /// Uniquely identifies the pathway + pub id: String, + /// Location at which the pathway ends + pub to_stop_id: String, + /// Type of pathway between the specified (from_stop_id, to_stop_id) pair + pub mode: PathwayMode, + /// Indicates in which direction the pathway can be used + pub is_bidirectional: PathwayDirectionType, + /// Horizontal length in meters of the pathway from the origin location to the destination location + pub length: Option, + /// Average time in seconds needed to walk through the pathway from the origin location to the destination location + pub traversal_time: Option, + /// Number of stairs of the pathway + pub stair_count: Option, + /// Maximum slope ratio of the pathway + pub max_slope: Option, + /// Minimum width of the pathway in meters + pub min_width: Option, + /// String of text from physical signage visible to transit riders + pub signposted_as: Option, + /// Same than the signposted_as field, but when the pathways is used backward + pub reversed_signposted_as: Option, +} + +impl Id for Pathway { + fn id(&self) -> &str { + &self.id + } +} + +impl From for Pathway { + /// Converts from a [RawPathway] to a [Pathway] + fn from(raw: RawPathway) -> Self { + Self { + id: raw.id, + to_stop_id: raw.to_stop_id, + mode: raw.mode, + is_bidirectional: raw.is_bidirectional, + length: raw.length, + max_slope: raw.max_slope, + min_width: raw.min_width, + reversed_signposted_as: raw.reversed_signposted_as, + signposted_as: raw.signposted_as, + stair_count: raw.stair_count, + traversal_time: raw.traversal_time, + } + } +} diff --git a/src/raw_gtfs.rs b/src/raw_gtfs.rs index 9d9ca2e..0f57cb5 100644 --- a/src/raw_gtfs.rs +++ b/src/raw_gtfs.rs @@ -31,6 +31,8 @@ pub struct RawGtfs { pub frequencies: Option, Error>>, /// All Transfers, None if the file was absent as it is not mandatory pub transfers: Option, Error>>, + /// All Pathways, None if the file was absent as it is not mandatory + pub pathways: Option, Error>>, /// All FeedInfo, None if the file was absent as it is not mandatory pub feed_info: Option, Error>>, /// All StopTimes @@ -57,7 +59,8 @@ impl RawGtfs { " Frequencies: {}", optional_file_summary(&self.frequencies) ); - println!(" Transfers {}", optional_file_summary(&self.transfers)); + println!(" Transfers: {}", optional_file_summary(&self.transfers)); + println!(" Pathways: {}", optional_file_summary(&self.pathways)); println!(" Feed info: {}", optional_file_summary(&self.feed_info)); } diff --git a/src/tests.rs b/src/tests.rs index 14e89fb..2d71f6d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -175,6 +175,22 @@ fn read_transfers() { ); } +#[test] +fn read_pathways() { + let gtfs = Gtfs::from_path("fixtures/basic").expect("impossible to read gtfs"); + + let pathways = >fs.get_stop("stop1").unwrap().pathways; + + assert_eq!(1, pathways.len()); + assert_eq!("stop3", pathways[0].to_stop_id); + assert_eq!(PathwayMode::Walkway, pathways[0].mode); + assert_eq!( + PathwayDirectionType::Unidirectional, + pathways[0].is_bidirectional + ); + assert_eq!(None, pathways[0].min_width); +} + #[test] fn read_feed_info() { let gtfs = Gtfs::from_path("fixtures/basic").expect("impossible to read gtfs"); @@ -273,7 +289,7 @@ fn display() { #[test] fn path_files() { let gtfs = RawGtfs::from_path("fixtures/basic").expect("impossible to read gtfs"); - assert_eq!(gtfs.files.len(), 12); + assert_eq!(gtfs.files.len(), 13); } #[test]