Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
description = "Read GTFS (public transit timetables) files"
name = "gtfs-structures"
version = "0.30.0"
version = "0.31.0"
authors = ["Tristram Gräbener <[email protected]>", "Antoine Desbordes <[email protected]>"]
repository = "https://github.com/rust-transit/gtfs-structure"
license = "MIT"
Expand Down
3 changes: 3 additions & 0 deletions fixtures/basic/transfers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from_stop_id,to_stop_id,transfer_type,min_transfer_time
stop3,stop5,0,60
stop1,stop2,3,
18 changes: 18 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,21 @@ impl Serialize for Transfers {
}
}
}
/// Defines the type of a [StopTransfer]
#[derive(Debug, Serialize, Deserialize, Derivative, Copy, Clone, PartialEq)]
#[derivative(Default)]
pub enum TransferType {
/// Recommended transfer point between routes
#[serde(rename = "0")]
#[derivative(Default)]
Recommended,
/// Departing vehicle waits for arriving one
#[serde(rename = "1")]
Timed,
/// Transfer requires a minimum amount of time between arrival and departure to ensure a connection.
#[serde(rename = "2")]
MinTime,
/// Transfer is not possible at this location
#[serde(rename = "3")]
Impossible,
}
26 changes: 21 additions & 5 deletions src/gtfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl TryFrom<RawGtfs> 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<Gtfs, Error> {
let stops = to_stop_map(raw.stops?);
let stops = to_stop_map(raw.stops?, raw.transfers.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)?;

Expand Down Expand Up @@ -228,11 +228,27 @@ fn to_map<O: Id>(elements: impl IntoIterator<Item = O>) -> HashMap<String, O> {
.collect()
}

fn to_stop_map(stops: Vec<Stop>) -> HashMap<String, Arc<Stop>> {
stops
fn to_stop_map(
stops: Vec<Stop>,
raw_transfers: Vec<RawTransfer>,
) -> Result<HashMap<String, Arc<Stop>>, Error> {
let mut stop_map: HashMap<String, Stop> =
stops.into_iter().map(|s| (s.id.clone(), s)).collect();

for transfer in raw_transfers {
stop_map
.get(&transfer.to_stop_id)
.ok_or(Error::ReferenceError(transfer.to_stop_id.to_string()))?;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

stop_map
.entry(transfer.from_stop_id.clone())
.and_modify(|stop| stop.transfers.push(StopTransfer::from(transfer)));
}

let res = stop_map
.into_iter()
.map(|s| (s.id.clone(), Arc::new(s)))
.collect()
.map(|(i, s)| (i, Arc::new(s)))
.collect();
Ok(res)
}

fn to_shape_map(shapes: Vec<Shape>) -> HashMap<String, Vec<Shape>> {
Expand Down
3 changes: 3 additions & 0 deletions src/gtfs_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ impl RawGtfsReader {
shapes: self.read_objs_from_optional_path(p, "shapes.txt"),
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"),
feed_info: self.read_objs_from_optional_path(p, "feed_info.txt"),
read_duration: Utc::now().signed_duration_since(now).num_milliseconds(),
files,
Expand Down Expand Up @@ -242,6 +243,7 @@ impl RawGtfsReader {
"trips.txt",
"fare_attributes.txt",
"frequencies.txt",
"transfers.txt",
"feed_info.txt",
"shapes.txt",
] {
Expand Down Expand Up @@ -275,6 +277,7 @@ impl RawGtfsReader {
"fare_attributes.txt",
),
frequencies: self.read_optional_file(&file_mapping, &mut archive, "frequencies.txt"),
transfers: self.read_optional_file(&file_mapping, &mut archive, "transfers.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(),
Expand Down
38 changes: 38 additions & 0 deletions src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ pub struct Stop {
pub level_id: Option<String>,
/// Platform identifier for a platform stop (a stop belonging to a station)
pub platform_code: Option<String>,
/// Transfers from this Stop
#[serde(skip)]
pub transfers: Vec<StopTransfer>,
}

impl Type for Stop {
Expand Down Expand Up @@ -634,6 +637,41 @@ impl Frequency {
}
}

/// Transfer information between stops before merged into [Stop]
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct RawTransfer {
/// Stop from which to leave
pub from_stop_id: String,
/// Stop which to transfer to
pub to_stop_id: String,
/// Type of the transfer
pub transfer_type: TransferType,
/// Minimum time needed to make the transfer in seconds
pub min_transfer_time: Option<u32>,
}

#[derive(Debug, Default, Clone)]
/// Transfer information between stops
pub struct StopTransfer {
/// Stop which to transfer to
pub to_stop_id: String,
/// Type of the transfer
pub transfer_type: TransferType,
/// Minimum time needed to make the transfer in seconds
pub min_transfer_time: Option<u32>,
}

impl StopTransfer {
/// Converts from a [RawTransfer] to a [StopTransfer]
pub fn from(transfer: RawTransfer) -> Self {
Self {
to_stop_id: transfer.to_stop_id,
transfer_type: transfer.transfer_type,
min_transfer_time: transfer.min_transfer_time,
}
}
}

/// Meta-data about the feed. See <https://gtfs.org/reference/static/#feed_infotxt>
#[derive(Debug, Serialize, Deserialize)]
pub struct FeedInfo {
Expand Down
3 changes: 3 additions & 0 deletions src/raw_gtfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct RawGtfs {
pub fare_attributes: Option<Result<Vec<FareAttribute>, Error>>,
/// All Frequencies, None if the file was absent as it is not mandatory
pub frequencies: Option<Result<Vec<RawFrequency>, Error>>,
/// All Transfers, None if the file was absent as it is not mandatory
pub transfers: Option<Result<Vec<RawTransfer>, Error>>,
/// All FeedInfo, None if the file was absent as it is not mandatory
pub feed_info: Option<Result<Vec<FeedInfo>, Error>>,
/// All StopTimes
Expand All @@ -55,6 +57,7 @@ impl RawGtfs {
" Frequencies: {}",
optional_file_summary(&self.frequencies)
);
println!(" Transfers {}", optional_file_summary(&self.transfers));
println!(" Feed info: {}", optional_file_summary(&self.feed_info));
}

Expand Down
34 changes: 33 additions & 1 deletion src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,38 @@ fn read_fare_attributes() {
);
}

#[test]
fn read_transfers() {
let gtfs = Gtfs::from_path("fixtures/basic").expect("impossible to read gtfs");
assert_eq!(1, gtfs.get_stop("stop3").unwrap().transfers.len());
assert_eq!(1, gtfs.get_stop("stop1").unwrap().transfers.len());

assert_eq!(
"stop5",
gtfs.get_stop("stop3").unwrap().transfers[0].to_stop_id
);
assert_eq!(
"stop2",
gtfs.get_stop("stop1").unwrap().transfers[0].to_stop_id
);
assert_eq!(
TransferType::Recommended,
gtfs.get_stop("stop3").unwrap().transfers[0].transfer_type
);
assert_eq!(
TransferType::Impossible,
gtfs.get_stop("stop1").unwrap().transfers[0].transfer_type
);
assert_eq!(
Some(60),
gtfs.get_stop("stop3").unwrap().transfers[0].min_transfer_time
);
assert_eq!(
None,
gtfs.get_stop("stop1").unwrap().transfers[0].min_transfer_time
);
}

#[test]
fn read_feed_info() {
let gtfs = Gtfs::from_path("fixtures/basic").expect("impossible to read gtfs");
Expand Down Expand Up @@ -241,7 +273,7 @@ fn display() {
#[test]
fn path_files() {
let gtfs = RawGtfs::from_path("fixtures/basic").expect("impossible to read gtfs");
assert_eq!(gtfs.files.len(), 11);
assert_eq!(gtfs.files.len(), 12);
}

#[test]
Expand Down