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.47.0"
version = "0.48.0"
authors = [
"Tristram Gräbener <tristramg@gmail.com>",
"Antoine Desbordes <antoine.desbordes@gmail.com>",
Expand Down
2 changes: 2 additions & 0 deletions fixtures/ticketing-extension/agency.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
agency_id,agency_name,agency_url,agency_timezone,ticketing_deep_link_id
agency,Agency,https://example.com,Etc/UTC,ticket-shop
3 changes: 3 additions & 0 deletions fixtures/ticketing-extension/routes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
route_id,route_short_name,route_long_name,route_desc,route_type,route_url,agency_id,ticketing_deep_link_id
route1,667,,,2,,agency,ticket-shop
route2,689,,,2,,agency,
7 changes: 7 additions & 0 deletions fixtures/ticketing-extension/stop_times.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
trip_id,arrival_time,departure_time,stop_id,stop_sequence,ticketing_type
trip1,13:57:00,13:57:00,stop1,0,0
trip1,14:09:00,14:10:00,stop2,1,0
trip1,14:17:00,14:18:00,stop3,2,0
trip2,14:24:00,14:25:00,stop1,0,0
trip2,14:34:00,14:35:00,stop2,1,1
trip2,14:41:00,14:42:00,stop3,2,0
4 changes: 4 additions & 0 deletions fixtures/ticketing-extension/stops.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
stop_id,stop_name,stop_lon,stop_lat
stop1,Stop 1,1.0,2.0
stop2,Stop 2,2.0,1.0
stop3,Stop 2,4.0,2.0
2 changes: 2 additions & 0 deletions fixtures/ticketing-extension/ticketing_deep_links.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ticketing_deep_link_id,web_url,android_intent_url,ios_universal_link_url
ticket-shop,https://example.com/tickets,,
4 changes: 4 additions & 0 deletions fixtures/ticketing-extension/ticketing_identifiers.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ticketing_stop_id,stop_id,agency_id
ticketing-stop1,stop1,agency
ticketing-stop2,stop2,agency
ticketing-stop3,stop3,agency
3 changes: 3 additions & 0 deletions fixtures/ticketing-extension/trips.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
trip_id,service_id,route_id,shape_id,trip_headsign,trip_short_name,direction_id,block_id,wheelchair_accessible,bikes_allowed,ticketing_trip_id,ticketing_type
trip1,service1,route1,,,,,,0,1,test,0
trip2,service1,route2,,,,,,0,1,,0
13 changes: 13 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,3 +657,16 @@ impl<'de> Deserialize<'de> for DefaultFareCategory {
})
}
}

/// Specifies whether tickets can be bought for this item
#[derive(Debug, Derivative, Deserialize, Serialize, Copy, Clone, PartialEq, Eq)]
#[derivative(Default)]
pub enum TicketingType {
/// If a ticketing_deep_link_id is set, tickets are available
#[derivative(Default)]
#[serde(rename = "0")]
Available,
/// Tickets are unavailable
#[serde(rename = "1")]
Unavailable,
}
3 changes: 1 addition & 2 deletions src/gtfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,7 @@ fn create_trips(
}

for trip in &mut trips.values_mut() {
trip.stop_times
.sort_by(|a, b| a.stop_sequence.cmp(&b.stop_sequence));
trip.stop_times.sort_by_key(|st| st.stop_sequence);
}

for f in raw_frequencies {
Expand Down
13 changes: 13 additions & 0 deletions src/gtfs_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ impl RawGtfsReader {
feed_info: self.read_objs_from_optional_path(p, "feed_info.txt"),
read_duration: start_of_read_instant.elapsed(),
translations: self.read_objs_from_optional_path(p, "translations.txt"),
ticketing_deep_links: self.read_objs_from_optional_path(p, "ticketing_deep_links.txt"),
ticketing_identifiers: self
.read_objs_from_optional_path(p, "ticketing_identifiers.txt"),
files,
source_format: crate::SourceFormat::Directory,
sha256: None,
Expand Down Expand Up @@ -330,6 +333,16 @@ impl RawGtfsReader {
Some(Ok(Vec::new()))
},
translations: self.read_optional_file(&file_mapping, &mut archive, "translations.txt"),
ticketing_deep_links: self.read_optional_file(
&file_mapping,
&mut archive,
"ticketing_deep_links.txt",
),
ticketing_identifiers: self.read_optional_file(
&file_mapping,
&mut archive,
"ticketing_identifiers.txt",
),
read_duration: start_of_read_instant.elapsed(),
files,
source_format: crate::SourceFormat::Zip,
Expand Down
44 changes: 44 additions & 0 deletions src/objects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ pub struct RawStopTime {
/// Indicates if arrival and departure times for a stop are strictly adhered to by the vehicle or if they are instead approximate and/or interpolated times
#[serde(default)]
pub timepoint: TimepointType,
/// This field is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// Enable or disable buying tickets via a deep link
#[serde(default)]
pub ticketing_type: TicketingType,
}

/// The moment where a vehicle, running on [Trip] stops at a [Stop]. See <https://gtfs.org/reference/static/#stopstxt>
Expand Down Expand Up @@ -360,6 +364,9 @@ pub struct Route {
/// Indicates whether a rider can alight from the transit vehicle at any point along the vehicle’s travel path
#[serde(default)]
pub continuous_drop_off: ContinuousPickupDropOff,
/// This field is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// Enable or disable buying tickets via a deep link
pub ticketing_deep_link_id: Option<String>,
}

impl Route {
Expand Down Expand Up @@ -445,6 +452,14 @@ pub struct RawTrip {
/// Indicates whether bikes are allowed
#[serde(default)]
pub bikes_allowed: BikesAllowedType,
/// This field is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// Trip ID to pass to a ticket shop
#[serde(default)]
pub ticketing_trip_id: Option<String>,
/// This field is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// Enable or disable buying tickets via a deep link
#[serde(default)]
pub ticketing_type: TicketingType,
}

impl Type for RawTrip {
Expand Down Expand Up @@ -547,6 +562,9 @@ pub struct Agency {
/// Email address actively monitored by the agency’s customer service department
#[serde(rename = "agency_email")]
pub email: Option<String>,
/// This field is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// Trip ID to pass to a ticket shop
pub ticketing_deep_link_id: Option<String>,
}

impl Type for Agency {
Expand Down Expand Up @@ -907,6 +925,32 @@ impl Id for RawPathway {
}
}

/// This object is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// A mapping of the GTFS-identifiers to the ticket shop identifiers
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TicketingIdentifier {
/// Stop ID used by the ticket shop
pub ticketing_stop_id: String,
/// GTFS-side stop id
pub stop_id: String,
/// GTFS-side agency id
pub agency_id: String,
}

/// This object is not part of the main GTFS specification, it is part of the Google Transit Ticketing extension
/// The base url to a ticket shop without the trip specific parameters
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct TicketingDeepLink {
/// Unique identifier for this base link
pub ticketing_deep_link_id: String,
/// URL to be used for buying a ticket on the web
pub web_url: Option<String>,
/// URI to be used for buying a ticket in an android app
pub android_intent_url: Option<String>,
/// URL used to use on iOS
pub ios_universal_link_url: Option<String>,
}

impl Type for RawPathway {
fn object_type(&self) -> ObjectType {
ObjectType::Pathway
Expand Down
12 changes: 12 additions & 0 deletions src/raw_gtfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ pub struct RawGtfs {
pub sha256: Option<String>,
/// All translations, None if the file was absent as it is not mandatory
pub translations: Option<Result<Vec<RawTranslation>, Error>>,
/// Base urls to ticket shops
pub ticketing_deep_links: Option<Result<Vec<TicketingDeepLink>, Error>>,
/// Identifiers to pass to ticket shops
pub ticketing_identifiers: Option<Result<Vec<TicketingIdentifier>, Error>>,
}

impl RawGtfs {
Expand All @@ -79,6 +83,14 @@ impl RawGtfs {
" Translations: {}",
optional_file_summary(&self.translations)
);
println!(
" Ticketing deep links: {}",
optional_file_summary(&self.ticketing_deep_links)
);
println!(
" Ticketing identifiers: {}",
optional_file_summary(&self.ticketing_identifiers)
);
}

/// Reads from an url (if starts with http), or a local path (either a directory or zipped file)
Expand Down
36 changes: 36 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,3 +561,39 @@ fn fares_v2() {
assert_eq!(gtfs.rider_categories.len(), 2);
assert_eq!(gtfs.rider_categories["concession"], expected);
}

#[test]
fn ticketing_extension() {
let gtfs = RawGtfs::from_path("fixtures/ticketing-extension").expect("impossible to read gtfs");

assert_eq!(gtfs.ticketing_deep_links.unwrap().unwrap().len(), 1);
assert_eq!(gtfs.ticketing_identifiers.unwrap().unwrap().len(), 3);
assert_eq!(gtfs.routes.as_ref().unwrap().len(), 2);
assert_eq!(gtfs.trips.unwrap().len(), 2);
assert_eq!(gtfs.agencies.as_ref().unwrap().len(), 1);
assert!(
gtfs.agencies
.unwrap()
.first()
.unwrap()
.ticketing_deep_link_id
.is_some()
);
assert!(
gtfs.routes
.as_ref()
.unwrap()
.last()
.unwrap()
.ticketing_deep_link_id
.is_none()
);
assert!(
gtfs.routes
.unwrap()
.first()
.unwrap()
.ticketing_deep_link_id
.is_some()
);
}
Loading