Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 From<RawTransfer> for StopTransfer {
/// Converts from a [RawTransfer] to a [StopTransfer]
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