use super::{
Agency, DirectionType, Route, RouteType, Shape, Stop, StopLocationType, StopTime, Transfer,
TransferType, Trip,
};
use crate::{
model::Collections,
objects::{
self, Availability, CommentLinksT, Coord, KeysValues, Pathway, StopLocation,
StopTime as NtfsStopTime, StopTimePrecision, StopType, Time, TransportType, VehicleJourney,
},
read_utils::{read_collection, read_objects, FileHandler},
utils::*,
Result,
};
use derivative::Derivative;
use failure::{bail, format_err, Error, ResultExt};
use geo::{LineString, Point};
use log::{info, warn, Level as LogLevel};
use serde::Deserialize;
use skip_error::skip_error_and_log;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::convert::TryFrom;
use std::result::Result as StdResult;
use typed_index_collection::{impl_id, Collection, CollectionWithId, Idx};
fn default_agency_id() -> String {
1.to_string()
}
fn get_agency_id(route: &Route, networks: &CollectionWithId<objects::Network>) -> Result<String> {
route
.agency_id
.clone()
.ok_or(())
.or_else(|()| match networks.values().next() {
Some(n) if networks.len() == 1 => Ok(n.id.clone()),
Some(_) => bail!("Impossible to get agency id, several networks found"),
None => bail!("Impossible to get agency id, no network found"),
})
}
impl From<Agency> for objects::Network {
fn from(agency: Agency) -> objects::Network {
let id = agency.id.unwrap_or_else(default_agency_id);
let mut codes = KeysValues::default();
codes.insert(("source".to_string(), id.clone()));
objects::Network {
id,
name: agency.name,
codes,
timezone: Some(agency.timezone),
url: Some(agency.url),
lang: agency.lang,
phone: agency.phone,
address: None,
sort_order: None,
}
}
}
impl From<Agency> for objects::Company {
fn from(agency: Agency) -> objects::Company {
objects::Company {
id: agency.id.unwrap_or_else(default_agency_id),
name: agency.name,
address: None,
url: Some(agency.url),
mail: agency.email,
phone: agency.phone,
}
}
}
impl TryFrom<Stop> for objects::StopArea {
type Error = Error;
fn try_from(stop: Stop) -> Result<Self> {
let mut codes: KeysValues = BTreeSet::new();
codes.insert(("source".to_string(), stop.id.clone()));
if let Some(c) = stop.code.as_ref() {
codes.insert(("gtfs_stop_code".to_string(), c.clone()));
}
if stop.name.is_empty() {
warn!("stop_id: {}: for station stop_name is required", stop.id);
}
let coord = Coord::from((stop.lon, stop.lat));
if coord == Coord::default() {
warn!("stop_id: {}: for station coordinates are required", stop.id);
}
let stop_area = objects::StopArea {
id: stop.id,
name: stop.name,
codes,
object_properties: KeysValues::default(),
comment_links: objects::CommentLinksT::default(),
coord,
timezone: stop.timezone,
visible: true,
geometry_id: None,
level_id: stop.level_id,
equipment_id: None,
};
Ok(stop_area)
}
}
impl TryFrom<Stop> for objects::StopPoint {
type Error = Error;
fn try_from(stop: Stop) -> Result<Self> {
let mut codes: KeysValues = BTreeSet::new();
codes.insert(("source".to_string(), stop.id.clone()));
if let Some(c) = stop.code.as_ref() {
codes.insert(("gtfs_stop_code".to_string(), c.clone()));
}
if stop.name.is_empty() {
warn!("stop_id: {}: for platform name is required", stop.id);
};
let coord = Coord::from((stop.lon, stop.lat));
if coord == Coord::default() {
warn!(
"stop_id: {}: for platform coordinates are required",
stop.id
);
}
let stop_point = objects::StopPoint {
id: stop.id,
name: stop.name,
code: stop.code,
codes,
coord,
stop_area_id: stop
.parent_station
.unwrap_or_else(|| String::from("default_id")),
timezone: stop.timezone,
visible: true,
stop_type: StopType::Point,
platform_code: stop.platform_code,
level_id: stop.level_id,
fare_zone_id: stop.fare_zone_id,
..Default::default()
};
Ok(stop_point)
}
}
impl TryFrom<Stop> for objects::StopLocation {
type Error = Error;
fn try_from(stop: Stop) -> Result<Self> {
let coord = Coord::from((stop.lon, stop.lat));
if stop.location_type == StopLocationType::StopEntrance {
if coord == Coord::default() {
bail!(
"stop_id: {}: for entrances/exits coordinates is required",
stop.id
);
}
if stop.parent_station.is_none() {
bail!(
"stop_id: {}: for entrances/exits parent_station is required",
stop.id
);
}
if stop.name.is_empty() {
bail!(
"stop_id: {}: for entrances/exits stop_name is required",
stop.id
);
}
}
if stop.location_type == StopLocationType::GenericNode && stop.parent_station.is_none() {
bail!(
"stop_id: {}: for generic node parent_station is required",
stop.id
);
}
if stop.location_type == StopLocationType::BoardingArea && stop.parent_station.is_none() {
bail!(
"stop_id: {}: for boarding area parent_station is required",
stop.id
);
}
let stop_location = StopLocation {
id: stop.id,
name: stop.name,
code: stop.code,
comment_links: CommentLinksT::default(),
visible: false,
coord,
parent_id: stop.parent_station,
timezone: stop.timezone,
geometry_id: None,
equipment_id: None,
level_id: stop.level_id,
stop_type: stop.location_type.into(),
};
Ok(stop_location)
}
}
impl RouteType {
fn to_gtfs_value(&self) -> String {
match *self {
RouteType::Tramway => "0".to_string(),
RouteType::Metro => "1".to_string(),
RouteType::Train => "2".to_string(),
RouteType::Bus
| RouteType::UnknownMode
| RouteType::Coach
| RouteType::Air
| RouteType::Taxi => "3".to_string(),
RouteType::Ferry => "4".to_string(),
RouteType::CableCar => "5".to_string(),
RouteType::SuspendedCableCar => "6".to_string(),
RouteType::Funicular => "7".to_string(),
}
}
}
impl ::serde::Serialize for RouteType {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: ::serde::Serializer,
{
serializer.serialize_str(&self.to_gtfs_value())
}
}
impl<'de> ::serde::Deserialize<'de> for RouteType {
fn deserialize<D>(deserializer: D) -> StdResult<RouteType, D::Error>
where
D: ::serde::Deserializer<'de>,
{
let i = u16::deserialize(deserializer)?;
let hundreds = i / 100;
Ok(match (i, hundreds) {
(0, _) | (_, 9) => RouteType::Tramway,
(1, _) | (_, 4) | (_, 5) | (_, 6) => RouteType::Metro,
(2, _) | (_, 1) | (_, 3) => RouteType::Train,
(3, _) | (_, 7) | (_, 8) => RouteType::Bus,
(4, _) | (_, 10) | (_, 12) => RouteType::Ferry,
(5, _) => RouteType::CableCar,
(6, _) | (_, 13) => RouteType::SuspendedCableCar,
(7, _) | (_, 14) => RouteType::Funicular,
(_, 2) => RouteType::Coach,
(_, 11) => RouteType::Air,
(_, 15) => RouteType::Taxi,
_ => RouteType::UnknownMode,
})
}
}
impl_id!(Route);
impl Route {
fn get_line_key(&self) -> (Option<String>, String) {
let name = if self.short_name != "" {
self.short_name.clone()
} else {
self.long_name.clone()
};
(self.agency_id.clone(), name)
}
fn get_id_by_direction(&self, d: DirectionType) -> String {
let id = self.id.clone();
match d {
DirectionType::Forward => id,
DirectionType::Backward => id + "_R",
}
}
}
impl Trip {
fn to_ntfs_vehicle_journey(
&self,
routes: &CollectionWithId<Route>,
dataset: &objects::Dataset,
trip_property_id: &Option<String>,
networks: &CollectionWithId<objects::Network>,
) -> Result<objects::VehicleJourney> {
let route = match routes.get(&self.route_id) {
Some(route) => route,
None => bail!("Coudn't find route {} for trip {}", self.route_id, self.id),
};
let physical_mode = get_physical_mode(&route.route_type);
let mut codes = KeysValues::default();
codes.insert(("source".to_string(), self.id.clone()));
Ok(objects::VehicleJourney {
id: self.id.clone(),
codes,
object_properties: KeysValues::default(),
comment_links: CommentLinksT::default(),
route_id: route.get_id_by_direction(self.direction),
physical_mode_id: physical_mode.id,
dataset_id: dataset.id.clone(),
service_id: self.service_id.clone(),
headsign: self.short_name.clone().or_else(|| self.headsign.clone()),
short_name: None,
block_id: self.block_id.clone(),
company_id: get_agency_id(route, networks)?,
trip_property_id: trip_property_id.clone(),
geometry_id: self.shape_id.clone(),
stop_times: vec![],
journey_pattern_id: None,
})
}
}
pub(in crate::gtfs) fn manage_shapes<H>(
collections: &mut Collections,
file_handler: &mut H,
) -> Result<()>
where
for<'a> &'a mut H: FileHandler,
{
let file = "shapes.txt";
let (reader, path) = file_handler.get_file_if_exists(file)?;
match reader {
None => {
info!("Skipping {}", file);
Ok(())
}
Some(reader) => {
info!("Reading {}", file);
let mut rdr = csv::Reader::from_reader(reader);
let mut shapes = vec![];
for shape in rdr.deserialize() {
let shape: Shape = skip_error_and_log!(
shape.with_context(|_| format!("Error reading {:?}", path)),
LogLevel::Warn
);
shapes.push(shape);
}
shapes.sort_unstable_by_key(|s| s.sequence);
let mut map: HashMap<String, Vec<Point<f64>>> = HashMap::new();
for s in &shapes {
map.entry(s.id.clone())
.or_insert_with(Vec::new)
.push((s.lon, s.lat).into())
}
collections.geometries = CollectionWithId::new(
map.iter()
.filter(|(_, points)| !points.is_empty())
.map(|(id, points)| {
let linestring: LineString<f64> = points.to_vec().into();
objects::Geometry {
id: id.to_string(),
geometry: linestring.into(),
}
})
.collect(),
)?;
Ok(())
}
}
}
pub(in crate::gtfs) fn manage_stop_times<H>(
collections: &mut Collections,
file_handler: &mut H,
on_demand_transport: bool,
on_demand_transport_comment: Option<String>,
) -> Result<()>
where
for<'a> &'a mut H: FileHandler,
{
let file_name = "stop_times.txt";
let (reader, path) = file_handler.get_file(file_name)?;
info!("Reading stop_times.txt");
let mut rdr = csv::ReaderBuilder::new()
.trim(csv::Trim::All)
.from_reader(reader);
let mut headsigns = HashMap::new();
let mut tmp_vjs = BTreeMap::new();
for stop_time in rdr.deserialize() {
let mut stop_time: StopTime =
stop_time.with_context(|_| format!("Error reading {:?}", path))?;
let vj_idx = collections
.vehicle_journeys
.get_idx(&stop_time.trip_id)
.ok_or_else(|| {
format_err!(
"Problem reading {:?}: trip_id={:?} not found",
file_name,
stop_time.trip_id
)
})?;
let headsign = std::mem::replace(&mut stop_time.stop_headsign, None);
if let Some(headsign) = headsign {
headsigns.insert(
(stop_time.trip_id.clone(), stop_time.stop_sequence),
headsign,
);
}
tmp_vjs
.entry(vj_idx)
.or_insert_with(Vec::new)
.push(stop_time);
}
collections.stop_time_headsigns = headsigns;
for (vj_idx, mut stop_times) in tmp_vjs {
stop_times.sort_unstable_by_key(|st| st.stop_sequence);
let st_values = interpolate_undefined_stop_times(
&collections.vehicle_journeys[vj_idx].id,
&stop_times,
)?;
let company_idx = collections
.companies
.get_idx(&collections.vehicle_journeys[vj_idx].company_id);
for (stop_time, st_values) in stop_times.iter().zip(st_values) {
let stop_point_idx = collections
.stop_points
.get_idx(&stop_time.stop_id)
.ok_or_else(|| {
format_err!(
"Problem reading {:?}: stop_id={:?} not found",
file_name,
stop_time.stop_id
)
})?;
let precision = match (on_demand_transport, st_values.datetime_estimated) {
(_, false) => Some(StopTimePrecision::Exact),
(false, true) => Some(StopTimePrecision::Approximate),
(true, true) => Some(StopTimePrecision::Estimated),
};
if let Some(message) = on_demand_transport_comment.as_ref() {
if stop_time.pickup_type == 2 || stop_time.drop_off_type == 2 {
if let Some(company_idx) = company_idx {
manage_odt_comment_from_stop_time(
collections,
message,
company_idx,
vj_idx,
stop_time,
);
}
}
}
collections
.vehicle_journeys
.index_mut(vj_idx)
.stop_times
.push(objects::StopTime {
stop_point_idx,
sequence: stop_time.stop_sequence,
arrival_time: st_values.arrival_time,
departure_time: st_values.departure_time,
boarding_duration: 0,
alighting_duration: 0,
pickup_type: stop_time.pickup_type,
drop_off_type: stop_time.drop_off_type,
datetime_estimated: st_values.datetime_estimated,
local_zone_id: stop_time.local_zone_id,
precision,
});
}
}
Ok(())
}
fn ventilate_stop_times(
undefined_stop_times: &[&StopTime],
before: &StopTimesValues,
after: &StopTimesValues,
) -> Vec<StopTimesValues> {
let duration = after.arrival_time - before.departure_time;
let step = duration / (undefined_stop_times.len() + 1) as u32;
let mut res = vec![];
for idx in 0..undefined_stop_times.len() {
let num = idx as u32 + 1u32;
let time = before.departure_time + objects::Time::new(0, 0, num * step.total_seconds());
res.push(StopTimesValues {
departure_time: time,
arrival_time: time,
datetime_estimated: true,
});
}
res
}
struct StopTimesValues {
arrival_time: Time,
departure_time: Time,
datetime_estimated: bool,
}
fn interpolate_undefined_stop_times(
vj_id: &str,
stop_times: &[StopTime],
) -> Result<Vec<StopTimesValues>> {
let mut undefined_stops_bulk = Vec::with_capacity(0);
let mut res = vec![];
for st in stop_times {
let (departure_time, arrival_time) = match (st.departure_time, st.arrival_time) {
(Some(departure_time), None) => {
log::debug!("for vj '{}', stop time n° {} has no arrival defined, we set it to its departure value", vj_id, st.stop_sequence);
(departure_time, departure_time)
}
(None, Some(arrival_time)) => {
log::debug!("for vj '{}', stop time n° {} has no departure defined, we set it to its arrival value", vj_id, st.stop_sequence);
(arrival_time, arrival_time)
}
(Some(departure_time), Some(arrival_time)) => (departure_time, arrival_time),
(None, None) => {
undefined_stops_bulk.push(st);
continue;
}
};
let st_value = StopTimesValues {
departure_time,
arrival_time,
datetime_estimated: !st.timepoint,
};
if !undefined_stops_bulk.is_empty() {
let values = ventilate_stop_times(
&undefined_stops_bulk,
res.last().ok_or_else(|| format_err!("the first stop time of the vj '{}' has no departure/arrival, the stop_times.txt file is not valid", vj_id))?,
&st_value,
);
res.extend(values);
undefined_stops_bulk.clear();
}
res.push(st_value);
}
if !undefined_stops_bulk.is_empty() {
Err(format_err!("the last stop time of the vj '{}' has no departure/arrival, the stop_times.txt file is not valid", vj_id))
} else {
Ok(res)
}
}
pub(in crate::gtfs) fn read_agency<H>(
file_handler: &mut H,
) -> Result<(
CollectionWithId<objects::Network>,
CollectionWithId<objects::Company>,
)>
where
for<'a> &'a mut H: FileHandler,
{
let filename = "agency.txt";
let gtfs_agencies = read_objects::<_, Agency>(file_handler, filename)?;
let networks = gtfs_agencies
.iter()
.cloned()
.map(objects::Network::from)
.collect();
let networks = CollectionWithId::new(networks)?;
let companies = gtfs_agencies
.into_iter()
.map(objects::Company::from)
.collect();
let companies = CollectionWithId::new(companies)?;
Ok((networks, companies))
}
fn manage_comment_from_stop(
comments: &mut CollectionWithId<objects::Comment>,
stop: &Stop,
) -> CommentLinksT {
let mut comment_links: CommentLinksT = CommentLinksT::default();
if let Some(desc) = &stop.desc {
let comment_id = "stop:".to_string() + &stop.id;
let comment = objects::Comment {
id: comment_id.clone(),
comment_type: objects::CommentType::Information,
label: None,
name: desc.to_string(),
url: None,
};
comments.push(comment).unwrap();
comment_links.insert(comment_id);
}
comment_links
}
fn manage_odt_comment_from_stop_time(
collections: &mut Collections,
on_demand_transport_comment: &str,
company_idx: Idx<objects::Company>,
vj_idx: Idx<objects::VehicleJourney>,
stop_time: &StopTime,
) {
let comment_id = format!("ODT:{}", collections.companies[company_idx].id);
if !collections.comments.contains_id(&comment_id) {
let comment = objects::Comment {
id: comment_id.clone(),
comment_type: objects::CommentType::OnDemandTransport,
label: None,
name: on_demand_transport_comment
.replace("{agency_name}", &collections.companies[company_idx].name)
.replace(
"{agency_phone}",
&collections.companies[company_idx]
.phone
.clone()
.unwrap_or_default(),
),
url: None,
};
collections.comments.push(comment).unwrap();
}
collections.stop_time_comments.insert(
(
collections.vehicle_journeys[vj_idx].id.to_string(),
stop_time.stop_sequence,
),
comment_id,
);
let stop_time_id = format!("{}-{}", stop_time.trip_id, stop_time.stop_sequence);
collections.stop_time_ids.insert(
(
collections.vehicle_journeys[vj_idx].id.to_string(),
stop_time.stop_sequence,
),
stop_time_id,
);
}
#[derive(Default)]
pub struct EquipmentList {
equipments: HashMap<objects::Equipment, String>,
}
impl EquipmentList {
pub fn into_equipments(self) -> Vec<objects::Equipment> {
let mut eqs: Vec<_> = self
.equipments
.into_iter()
.map(|(mut eq, id)| {
eq.id = id;
eq
})
.collect();
eqs.sort_by(|l, r| l.id.cmp(&r.id));
eqs
}
pub fn push(&mut self, equipment: objects::Equipment) -> String {
let equipment_id = self.equipments.len().to_string();
let id = self.equipments.entry(equipment).or_insert(equipment_id);
id.clone()
}
}
fn get_equipment_id_and_populate_equipments(
equipments: &mut EquipmentList,
stop: &Stop,
) -> Option<String> {
match stop.wheelchair_boarding {
Availability::Available | Availability::NotAvailable => {
Some(equipments.push(objects::Equipment {
id: "".to_string(),
wheelchair_boarding: stop.wheelchair_boarding,
sheltered: Availability::InformationNotAvailable,
elevator: Availability::InformationNotAvailable,
escalator: Availability::InformationNotAvailable,
bike_accepted: Availability::InformationNotAvailable,
bike_depot: Availability::InformationNotAvailable,
visual_announcement: Availability::InformationNotAvailable,
audible_announcement: Availability::InformationNotAvailable,
appropriate_escort: Availability::InformationNotAvailable,
appropriate_signage: Availability::InformationNotAvailable,
}))
}
_ => None,
}
}
pub(in crate::gtfs) fn read_stops<H>(
file_handler: &mut H,
comments: &mut CollectionWithId<objects::Comment>,
equipments: &mut EquipmentList,
) -> Result<(
CollectionWithId<objects::StopArea>,
CollectionWithId<objects::StopPoint>,
CollectionWithId<objects::StopLocation>,
)>
where
for<'a> &'a mut H: FileHandler,
{
info!("Reading stops.txt");
let file = "stops.txt";
let (reader, path) = file_handler.get_file(file)?;
let mut rdr = csv::ReaderBuilder::new()
.trim(csv::Trim::All)
.from_reader(reader);
let gtfs_stops: Vec<Stop> = rdr
.deserialize()
.collect::<StdResult<_, _>>()
.with_context(|_| format!("Error reading {:?}", path))?;
let mut stop_areas = vec![];
let mut stop_points = vec![];
let mut stop_locations = vec![];
for stop in gtfs_stops {
let comment_links = manage_comment_from_stop(comments, &stop);
let equipment_id = get_equipment_id_and_populate_equipments(equipments, &stop);
match stop.location_type {
StopLocationType::StopPoint => {
let mut stop_point =
skip_error_and_log!(objects::StopPoint::try_from(stop.clone()), LogLevel::Warn);
if stop.parent_station.is_none() {
let stop_area = objects::StopArea::from(stop_point.clone());
stop_point.stop_area_id = stop_area.id.clone();
stop_areas.push(stop_area);
};
stop_point.comment_links = comment_links;
stop_point.equipment_id = equipment_id;
stop_points.push(stop_point);
}
StopLocationType::StopArea => {
let mut stop_area =
skip_error_and_log!(objects::StopArea::try_from(stop), LogLevel::Warn);
stop_area.comment_links = comment_links;
stop_area.equipment_id = equipment_id;
stop_areas.push(stop_area);
}
_ => {
let mut stop_location =
skip_error_and_log!(objects::StopLocation::try_from(stop), LogLevel::Warn);
stop_location.comment_links = comment_links;
stop_location.equipment_id = equipment_id;
stop_locations.push(stop_location);
}
}
}
let stoppoints = CollectionWithId::new(stop_points)?;
let stopareas = CollectionWithId::new(stop_areas)?;
let stoplocations = CollectionWithId::new(stop_locations)?;
Ok((stopareas, stoppoints, stoplocations))
}
pub(in crate::gtfs) fn manage_pathways<H>(
collections: &mut Collections,
file_handler: &mut H,
) -> Result<()>
where
for<'a> &'a mut H: FileHandler,
{
let file = "pathways.txt";
let (reader, _path) = file_handler.get_file_if_exists(file)?;
match reader {
None => {
info!("Skipping {}", file);
}
Some(reader) => {
info!("Reading {}", file);
let mut rdr = csv::Reader::from_reader(reader);
let mut pathways = vec![];
for pathway in rdr.deserialize() {
let mut pathway: Pathway =
skip_error_and_log!(pathway.map_err(|e| format_err!("{}", e)), LogLevel::Warn);
pathway.from_stop_type = skip_error_and_log!(
collections
.stop_points
.get(&pathway.from_stop_id)
.map(|st| st.stop_type.clone())
.or_else(|| collections
.stop_locations
.get(&pathway.from_stop_id)
.map(|sl| sl.stop_type.clone()))
.ok_or_else(|| {
format_err!(
"Problem reading {:?}: from_stop_id={:?} not found",
file,
pathway.from_stop_id
)
}),
LogLevel::Warn
);
pathway.to_stop_type = skip_error_and_log!(
collections
.stop_points
.get(&pathway.to_stop_id)
.map(|st| st.stop_type.clone())
.or_else(|| collections
.stop_locations
.get(&pathway.to_stop_id)
.map(|sl| sl.stop_type.clone()))
.ok_or_else(|| {
format_err!(
"Problem reading {:?}: to_stop_id={:?} not found",
file,
pathway.to_stop_id
)
}),
LogLevel::Warn
);
pathways.push(pathway);
}
collections.pathways = CollectionWithId::new(pathways)?;
}
}
Ok(())
}
pub(in crate::gtfs) fn read_transfers<H>(
file_handler: &mut H,
stop_points: &CollectionWithId<objects::StopPoint>,
) -> Result<Collection<objects::Transfer>>
where
for<'a> &'a mut H: FileHandler,
{
let file = "transfers.txt";
let (reader, _path) = file_handler.get_file_if_exists(file)?;
match reader {
None => {
info!("Skipping {}", file);
Ok(Collection::new(vec![]))
}
Some(reader) => {
info!("Reading {}", file);
let mut rdr = csv::Reader::from_reader(reader);
let mut transfers = vec![];
for transfer in rdr.deserialize() {
let transfer: Transfer = skip_error_and_log!(
transfer.map_err(|e| format_err!("Problem reading {:?}: {}", file, e)),
LogLevel::Warn
);
let from_stop_point = skip_error_and_log!(
stop_points
.get(&transfer.from_stop_id)
.ok_or_else(|| format_err!(
"Problem reading {:?}: from_stop_id={:?} not found",
file,
transfer.from_stop_id
)),
LogLevel::Warn
);
let to_stop_point = skip_error_and_log!(
stop_points
.get(&transfer.to_stop_id)
.ok_or_else(|| format_err!(
"Problem reading {:?}: to_stop_id={:?} not found",
file,
transfer.to_stop_id
)),
LogLevel::Warn
);
let (min_transfer_time, real_min_transfer_time) = match transfer.transfer_type {
TransferType::Recommended => {
let distance = from_stop_point.coord.distance_to(&to_stop_point.coord);
let transfer_time = (distance / 0.785) as u32;
(Some(transfer_time), Some(transfer_time + 2 * 60))
}
TransferType::Timed => (Some(0), Some(0)),
TransferType::WithTransferTime => {
if transfer.min_transfer_time.is_none() {
warn!(
"The min_transfer_time between from_stop_id {} and to_stop_id {} is empty",
from_stop_point.id, to_stop_point.id
);
}
(transfer.min_transfer_time, transfer.min_transfer_time)
}
TransferType::NotPossible => (Some(86400), Some(86400)),
};
transfers.push(objects::Transfer {
from_stop_id: from_stop_point.id.clone(),
to_stop_id: to_stop_point.id.clone(),
min_transfer_time,
real_min_transfer_time,
equipment_id: None,
});
}
Ok(Collection::new(transfers))
}
}
}
fn get_commercial_mode(route_type: &RouteType) -> objects::CommercialMode {
objects::CommercialMode {
id: route_type.to_string(),
name: match route_type {
RouteType::CableCar => "Cable car".to_string(),
RouteType::SuspendedCableCar => "Suspended cable car".to_string(),
RouteType::UnknownMode => "Unknown mode".to_string(),
RouteType::Air => "Airplane".to_string(),
_ => route_type.to_string(),
},
}
}
fn get_physical_mode(route_type: &RouteType) -> objects::PhysicalMode {
let repres = match route_type {
RouteType::UnknownMode => "Bus".into(),
RouteType::CableCar => "Funicular".into(),
_ => route_type.to_string(),
};
objects::PhysicalMode {
id: repres.clone(),
name: repres,
co2_emission: None,
}
}
fn get_modes_from_gtfs(
gtfs_routes: &CollectionWithId<Route>,
) -> (Vec<objects::CommercialMode>, Vec<objects::PhysicalMode>) {
let gtfs_mode_types: BTreeSet<RouteType> =
gtfs_routes.values().map(|r| r.route_type.clone()).collect();
let commercial_modes = gtfs_mode_types
.iter()
.map(|mt| get_commercial_mode(mt))
.collect();
let physical_modes = gtfs_mode_types
.iter()
.map(|mt| get_physical_mode(mt))
.collect::<BTreeSet<_>>()
.into_iter()
.collect();
(commercial_modes, physical_modes)
}
fn get_route_with_smallest_name<'a>(routes: &'a [&Route]) -> &'a Route {
routes.iter().min_by_key(|r| &r.id).unwrap()
}
type MapLineRoutes<'a> = BTreeMap<(Option<String>, String), Vec<&'a Route>>;
fn map_line_routes<'a>(
gtfs_routes: &'a CollectionWithId<Route>,
gtfs_trips: &[Trip],
) -> MapLineRoutes<'a> {
let mut map = BTreeMap::new();
for r in gtfs_routes.values().filter(|r| {
if !gtfs_trips.iter().any(|t| t.route_id == r.id) {
warn!("Couldn't find trips for route_id {}", r.id);
return false;
}
true
}) {
map.entry(r.get_line_key()).or_insert_with(Vec::new).push(r);
}
map
}
fn make_lines(
map_line_routes: &MapLineRoutes<'_>,
networks: &CollectionWithId<objects::Network>,
) -> Result<Vec<objects::Line>> {
let mut lines = vec![];
let line_code = |r: &Route| {
if r.short_name.is_empty() {
None
} else {
Some(r.short_name.to_string())
}
};
for routes in map_line_routes.values() {
let r = get_route_with_smallest_name(routes);
lines.push(objects::Line {
id: r.id.clone(),
code: line_code(r),
codes: KeysValues::default(),
object_properties: KeysValues::default(),
comment_links: CommentLinksT::default(),
name: r.long_name.to_string(),
forward_name: None,
forward_direction: None,
backward_name: None,
backward_direction: None,
color: r.color.clone(),
text_color: r.text_color.clone(),
sort_order: r.sort_order,
network_id: get_agency_id(r, networks)?,
commercial_mode_id: r.route_type.to_string(),
geometry_id: None,
opening_time: None,
closing_time: None,
});
}
Ok(lines)
}
fn make_routes(gtfs_trips: &[Trip], map_line_routes: &MapLineRoutes<'_>) -> Vec<objects::Route> {
let mut routes = vec![];
let get_direction_name = |d: DirectionType| match d {
DirectionType::Forward => "forward".to_string(),
DirectionType::Backward => "backward".to_string(),
};
for rs in map_line_routes.values() {
let sr = get_route_with_smallest_name(rs);
for r in rs {
let mut route_directions: BTreeSet<DirectionType> = BTreeSet::new();
for t in gtfs_trips.iter().filter(|t| t.route_id == r.id) {
route_directions.insert(t.direction);
}
let has_one_direction = route_directions.len() <= 1;
for d in route_directions {
routes.push(objects::Route {
id: r.get_id_by_direction(d),
name: if has_one_direction {
if !r.long_name.is_empty() {
r.long_name.clone()
} else {
r.short_name.clone()
}
} else {
String::new()
},
direction_type: Some(get_direction_name(d)),
codes: KeysValues::default(),
object_properties: KeysValues::default(),
comment_links: CommentLinksT::default(),
line_id: sr.id.clone(),
geometry_id: None,
destination_id: None,
});
}
}
}
routes
}
fn make_ntfs_vehicle_journeys(
gtfs_trips: &[Trip],
routes: &CollectionWithId<Route>,
datasets: &CollectionWithId<objects::Dataset>,
networks: &CollectionWithId<objects::Network>,
) -> Result<(Vec<objects::VehicleJourney>, Vec<objects::TripProperty>)> {
let (_, dataset) = datasets.iter().next().unwrap();
let mut vehicle_journeys: Vec<objects::VehicleJourney> = vec![];
let mut trip_properties: Vec<objects::TripProperty> = vec![];
let mut map_tps_trips: BTreeMap<(Availability, Availability), Vec<&Trip>> = BTreeMap::new();
let mut id_incr: u8 = 1;
let mut property_id: Option<String>;
for t in gtfs_trips {
map_tps_trips
.entry((t.wheelchair_accessible, t.bikes_allowed))
.or_insert_with(Vec::new)
.push(t);
}
for ((wheelchair, bike), trips) in &map_tps_trips {
if *wheelchair == Availability::InformationNotAvailable
&& *bike == Availability::InformationNotAvailable
{
property_id = None;
} else {
property_id = Some(id_incr.to_string());
trip_properties.push(objects::TripProperty {
id: id_incr.to_string(),
wheelchair_accessible: *wheelchair,
bike_accepted: *bike,
air_conditioned: Availability::InformationNotAvailable,
visual_announcement: Availability::InformationNotAvailable,
audible_announcement: Availability::InformationNotAvailable,
appropriate_escort: Availability::InformationNotAvailable,
appropriate_signage: Availability::InformationNotAvailable,
school_vehicle_type: TransportType::Regular,
});
id_incr += 1;
}
for t in trips {
vehicle_journeys.push(skip_error_and_log!(
t.to_ntfs_vehicle_journey(routes, dataset, &property_id, networks,),
LogLevel::Warn
));
}
}
Ok((vehicle_journeys, trip_properties))
}
pub(in crate::gtfs) fn read_routes<H>(
file_handler: &mut H,
collections: &mut Collections,
) -> Result<()>
where
for<'a> &'a mut H: FileHandler,
{
info!("Reading routes.txt");
let gtfs_routes_collection = read_collection(file_handler, "routes.txt")?;
let (commercial_modes, physical_modes) = get_modes_from_gtfs(>fs_routes_collection);
collections.commercial_modes = CollectionWithId::new(commercial_modes)?;
collections.physical_modes = CollectionWithId::new(physical_modes)?;
let gtfs_trips = read_objects(file_handler, "trips.txt")?;
let map_line_routes = map_line_routes(>fs_routes_collection, >fs_trips);
let lines = make_lines(&map_line_routes, &collections.networks)?;
collections.lines = CollectionWithId::new(lines)?;
let routes = make_routes(>fs_trips, &map_line_routes);
collections.routes = CollectionWithId::new(routes)?;
let (vehicle_journeys, trip_properties) = make_ntfs_vehicle_journeys(
>fs_trips,
>fs_routes_collection,
&collections.datasets,
&collections.networks,
)
.with_context(|_| format!("Error reading {:?}", "trips.txt"))?;
collections.vehicle_journeys = CollectionWithId::new(vehicle_journeys)?;
collections.trip_properties = CollectionWithId::new(trip_properties)?;
Ok(())
}
#[derivative(Default)]
#[derive(Derivative, Deserialize, Debug, Clone, PartialEq)]
enum FrequencyPrecision {
#[derivative(Default)]
#[serde(rename = "0")]
Inexact,
#[serde(rename = "1")]
Exact,
}
#[derive(Deserialize, Debug, Clone, PartialEq)]
struct Frequency {
trip_id: String,
start_time: Time,
end_time: Time,
headway_secs: u32,
#[serde(default, deserialize_with = "de_with_empty_default")]
exact_times: FrequencyPrecision,
}
pub(in crate::gtfs) fn manage_frequencies<H>(
collections: &mut Collections,
file_handler: &mut H,
) -> Result<()>
where
for<'a> &'a mut H: FileHandler,
{
let file = "frequencies.txt";
let (reader, path) = file_handler.get_file_if_exists(file)?;
info!("Reading {}", file);
match reader {
None => {
info!("Skipping {}", file);
Ok(())
}
Some(reader) => {
let mut rdr = csv::Reader::from_reader(reader);
let gtfs_frequencies: Vec<Frequency> =
rdr.deserialize()
.collect::<StdResult<_, _>>()
.with_context(|_| format!("Error reading {:?}", path))?;
let mut trip_id_sequence: HashMap<String, u32> = HashMap::new();
let mut new_vehicle_journeys: Vec<VehicleJourney> = vec![];
for frequency in >fs_frequencies {
if frequency.start_time == frequency.end_time {
warn!(
"frequency for trip {:?} has same start and end time",
frequency.trip_id
);
continue;
}
let datetime_estimated = match frequency.exact_times {
FrequencyPrecision::Exact => false,
FrequencyPrecision::Inexact => true,
};
let corresponding_vj = skip_error_and_log!(
collections
.vehicle_journeys
.get(&frequency.trip_id)
.cloned()
.ok_or_else(|| format_err!(
"frequency mapped to an unexisting trip {:?}",
frequency.trip_id
)),
LogLevel::Warn
);
let mut start_time = frequency.start_time;
let mut arrival_time_delta = match corresponding_vj.stop_times.iter().min() {
None => {
warn!(
"frequency mapped to trip {:?} with no stop_times",
frequency.trip_id
);
continue;
}
Some(st) => st.arrival_time,
};
while start_time < frequency.end_time {
trip_id_sequence
.entry(frequency.trip_id.clone())
.and_modify(|counter| *counter += 1)
.or_insert(0);
let generated_trip_id = format!(
"{}-{}",
frequency.trip_id, trip_id_sequence[&frequency.trip_id]
);
let service_id = if start_time.hours() >= 24 {
let nb_days = start_time.hours() / 24;
let service = collections
.calendars
.get(&corresponding_vj.service_id)
.cloned()
.unwrap();
let new_service_id = format!("{}:+{}days", service.id, nb_days);
if !collections.calendars.contains_id(&new_service_id) {
arrival_time_delta = arrival_time_delta + Time::new(24, 0, 0);
let new_dates: BTreeSet<_> = service
.dates
.iter()
.map(|d| *d + chrono::Duration::days(i64::from(nb_days)))
.collect();
let new_service = objects::Calendar {
id: new_service_id.clone(),
dates: new_dates,
};
collections.calendars.push(new_service)?;
}
new_service_id
} else {
corresponding_vj.service_id.clone()
};
let stop_times: Vec<NtfsStopTime> = corresponding_vj
.stop_times
.iter()
.map(|stop_time| NtfsStopTime {
stop_point_idx: stop_time.stop_point_idx,
sequence: stop_time.sequence,
arrival_time: stop_time.arrival_time + start_time - arrival_time_delta,
departure_time: stop_time.departure_time + start_time
- arrival_time_delta,
boarding_duration: stop_time.boarding_duration,
alighting_duration: stop_time.alighting_duration,
pickup_type: stop_time.pickup_type,
drop_off_type: stop_time.drop_off_type,
datetime_estimated,
local_zone_id: stop_time.local_zone_id,
precision: stop_time.precision.clone(),
})
.collect();
start_time = start_time + Time::new(0, 0, frequency.headway_secs);
let generated_vj = VehicleJourney {
id: generated_trip_id.clone(),
service_id,
stop_times,
..corresponding_vj.clone()
};
new_vehicle_journeys.push(generated_vj);
let stop_time_comments: HashMap<(String, u32), String> = corresponding_vj
.stop_times
.iter()
.filter(|stop_time| {
stop_time.pickup_type == 2 || stop_time.drop_off_type == 2
})
.filter_map(|stop_time| {
collections
.stop_time_comments
.get(&(frequency.trip_id.clone(), stop_time.sequence))
.map(|comment_id| {
(
(generated_trip_id.clone(), stop_time.sequence),
comment_id.to_string(),
)
})
})
.collect();
let stop_time_ids: HashMap<(String, u32), String> = stop_time_comments
.keys()
.map(|(trip_id, sequence)| {
(
(trip_id.to_string(), *sequence),
format!("{}-{}", trip_id, sequence),
)
})
.collect();
collections.stop_time_comments.extend(stop_time_comments);
collections.stop_time_ids.extend(stop_time_ids);
}
}
let mut vehicle_journeys = collections.vehicle_journeys.take();
let trip_ids_to_remove: Vec<_> = gtfs_frequencies.iter().map(|f| &f.trip_id).collect();
vehicle_journeys.retain(|vj| !trip_ids_to_remove.contains(&&vj.id));
collections
.stop_time_ids
.retain(|(vj_id, _), _| !trip_ids_to_remove.contains(&&vj_id));
collections
.stop_time_comments
.retain(|(vj_id, _), _| !trip_ids_to_remove.contains(&&vj_id));
vehicle_journeys.append(&mut new_vehicle_journeys);
collections.vehicle_journeys = CollectionWithId::new(vehicle_journeys)?;
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
calendars,
gtfs::read::EquipmentList,
model::Collections,
objects::*,
objects::{Calendar, Comment, CommentType, Equipment, Geometry, Rgb, StopTime, Transfer},
read_utils::{self, read_opt_collection, PathFileHandler},
test_utils::*,
AddPrefix, PrefixConfiguration,
};
use geo::line_string;
use pretty_assertions::assert_eq;
use typed_index_collection::Id;
fn extract<'a, T, S: ::std::cmp::Ord>(f: fn(&'a T) -> S, c: &'a Collection<T>) -> Vec<S> {
let mut extracted_props: Vec<S> = c.values().map(|l| f(l)).collect();
extracted_props.sort();
extracted_props
}
fn extract_ids<T: Id<T>>(c: &Collection<T>) -> Vec<&str> {
extract(T::id, c)
}
#[test]
fn load_minimal_agency() {
let agency_content = "agency_name,agency_url,agency_timezone\n\
My agency,http://my-agency_url.com,Europe/London";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
let (networks, companies) = super::read_agency(&mut handler).unwrap();
assert_eq!(1, networks.len());
let agency = networks.iter().next().unwrap().1;
assert_eq!("1", agency.id);
assert_eq!(1, companies.len());
});
}
#[test]
fn load_standard_agency() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_1,My agency,http://my-agency_url.com,Europe/London";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
let (networks, companies) = super::read_agency(&mut handler).unwrap();
assert_eq!(1, networks.len());
assert_eq!(1, companies.len());
});
}
#[test]
fn load_complete_agency() {
let agency_content =
"agency_id,agency_name,agency_url,agency_timezone,agency_lang,agency_phone,\
agency_fare_url,agency_email\n\
id_1,My agency,http://my-agency_url.com,Europe/London,EN,0123456789,\
http://my-agency_fare_url.com,my-mail@example.com";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
let (networks, companies) = super::read_agency(&mut handler).unwrap();
assert_eq!(1, networks.len());
let network = networks.iter().next().unwrap().1;
assert_eq!("id_1", network.id);
assert_eq!(1, companies.len());
});
}
#[test]
#[should_panic]
fn load_2_agencies_with_no_id() {
let agency_content = "agency_name,agency_url,agency_timezone\n\
My agency 1,http://my-agency_url.com,Europe/London\
My agency 2,http://my-agency_url.com,Europe/London";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
super::read_agency(&mut handler).unwrap();
});
}
#[test]
fn load_one_stop_point() {
let stops_content = "stop_id,stop_name,stop_code,stop_lat,stop_lon\n\
id1,my stop name,stopcode,0.1,1.2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (stop_areas, stop_points, stop_locations) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
assert_eq!(1, stop_areas.len());
assert_eq!(1, stop_points.len());
assert_eq!(0, stop_locations.len());
let stop_area = stop_areas.iter().next().unwrap().1;
assert_eq!("Navitia:id1", stop_area.id);
assert_eq!(1, stop_points.len());
let stop_point = stop_points.iter().next().unwrap().1;
assert_eq!("Navitia:id1", stop_point.stop_area_id);
assert_eq!("stopcode", stop_point.code.as_ref().unwrap());
});
}
#[test]
fn load_without_slashes() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n\
stoparea/01,my stop name 1,0.1,1.2,1,\n\
stoppoint/01,my stop name 2,0.1,1.2,0,stoparea/01\n\
stoppoint/02,my stop name 3,0.1,1.2,0,stoparea/01";
let shapes_content =
"shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled\n\
relation/1,12.1280176,-86.214164,1,\n\
relation/1,12.1279272,-86.2132786,2,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
create_file_with_content(path, "shapes.txt", shapes_content);
let mut collections = Collections::default();
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (stop_areas, stop_points, stop_locations) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_areas = stop_areas;
collections.stop_points = stop_points;
collections.stop_locations = stop_locations;
super::manage_shapes(&mut collections, &mut handler).unwrap();
let stop_area = collections.stop_areas.iter().next().unwrap().1;
assert_eq!("stoparea01", stop_area.id);
assert_eq!(
vec![("stoppoint01", "stoparea01"), ("stoppoint02", "stoparea01"),],
extract(
|sp| (sp.id.as_str(), sp.stop_area_id.as_str()),
&collections.stop_points
)
);
assert_eq!(
vec!["relation1"],
extract(|geo| geo.id.as_str(), &collections.geometries)
);
});
}
#[test]
fn stop_code_on_stops() {
let stops_content =
"stop_id,stop_code,stop_name,stop_lat,stop_lon,location_type,parent_station\n\
stoppoint_id,1234,my stop name,0.1,1.2,0,stop_area_id\n\
stoparea_id,5678,stop area name,0.1,1.2,1,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (stop_areas, stop_points, stop_locations) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
assert_eq!(1, stop_points.len());
let stop_point = stop_points.iter().next().unwrap().1;
assert_eq!("1234", stop_point.code.as_ref().unwrap());
assert_eq!(2, stop_point.codes.len());
let mut codes_iterator = stop_point.codes.iter();
let code = codes_iterator.next().unwrap();
assert_eq!("gtfs_stop_code", code.0);
assert_eq!("1234", code.1);
let code = codes_iterator.next().unwrap();
assert_eq!("source", code.0);
assert_eq!("stoppoint_id", code.1);
assert_eq!(1, stop_areas.len());
let stop_area = stop_areas.iter().next().unwrap().1;
assert_eq!(2, stop_area.codes.len());
let mut codes_iterator = stop_area.codes.iter();
let code = codes_iterator.next().unwrap();
assert_eq!("gtfs_stop_code", code.0);
assert_eq!("5678", code.1);
let code = codes_iterator.next().unwrap();
assert_eq!("source", code.0);
assert_eq!("stoparea_id", code.1);
assert_eq!(0, stop_locations.len());
});
}
#[test]
fn no_stop_code_on_autogenerated_stoparea() {
let stops_content =
"stop_id,stop_code,stop_name,stop_lat,stop_lon,location_type,parent_station\n\
stoppoint_id,1234,my stop name,0.1,1.2,0,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (stop_areas, _, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
assert_eq!(1, stop_areas.len());
let stop_area = stop_areas.iter().next().unwrap().1;
assert_eq!(0, stop_area.codes.len());
});
}
#[test]
fn gtfs_routes_as_line() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,agency_2,,My line 2,2,7BC142,000000\n\
route_3,agency_3,3,My line 3,8,,\n\
route_4,agency_4,3,My line 3 for agency 3,8,,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n\
4,route_3,0,service_3,,\n\
5,route_4,0,service_4,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(4, collections.lines.len());
assert_eq!(
vec!["agency_1", "agency_2", "agency_3", "agency_4"],
extract(|l| &l.network_id, &collections.lines)
);
assert_eq!(3, collections.commercial_modes.len());
assert_eq!(
vec!["Bus", "Train", "Unknown mode"],
extract(|cm| &cm.name, &collections.commercial_modes)
);
let lines_commercial_modes_id: Vec<String> = collections
.lines
.values()
.map(|l| l.commercial_mode_id.clone())
.collect();
assert!(lines_commercial_modes_id.contains(&"Train".to_string()));
assert!(lines_commercial_modes_id.contains(&"Bus".to_string()));
assert!(lines_commercial_modes_id.contains(&"UnknownMode".to_string()));
assert_eq!(2, collections.physical_modes.len());
assert_eq!(
vec!["Bus", "Train"],
extract(|pm| &pm.name, &collections.physical_modes)
);
assert_eq!(5, collections.routes.len());
assert_eq!(
vec!["route_1", "route_1_R", "route_2", "route_3", "route_4"],
extract_ids(&collections.routes)
);
});
}
#[test]
fn gtfs_routes_without_agency_id_as_line() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_agency,My agency,http://my-agency_url.com,Europe/London";
let routes_content =
"route_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,,My line 2,2,7BC142,000000\n\
route_3,3,My line 3,8,,\n\
route_4,3,My line 3 for agency 3,8,,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n\
4,route_3,0,service_3,,\n\
5,route_4,0,service_4,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (networks, _) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(5, collections.routes.len());
assert_eq!(
vec!["id_agency", "id_agency", "id_agency"],
extract(|l| &l.network_id, &collections.lines)
);
});
}
#[test]
fn gtfs_routes_with_wrong_colors() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_agency1,My agency 1,http://my-agency_url1.com,Europe/London";
let routes_content =
"route_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,1,My line 1,3,0,FFFFFF\n\
route_2,,My line 2,2,7BC142,0\n\
route_3,3,My line 3,8,FFFFFF,000000\n\
route_4,3,My line 3 for agency 3,8,FFFFAF,FAFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n\
4,route_3,0,service_3,,\n\
5,route_4,0,service_4,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (networks, _) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(
vec![
(
&None,
&Some(Rgb {
red: 255,
green: 255,
blue: 255
})
),
(
&Some(Rgb {
red: 123,
green: 193,
blue: 66
}),
&None
),
(
&Some(Rgb {
red: 255,
green: 255,
blue: 255
}),
&Some(Rgb {
red: 0,
green: 0,
blue: 0
})
)
],
extract(|l| (&l.color, &l.text_color), &collections.lines)
);
});
}
#[test]
#[should_panic(expected = "Impossible to get agency id, several networks found")]
fn gtfs_routes_without_agency_id_as_line_and_2_agencies() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_agency1,My agency 1,http://my-agency_url1.com,Europe/London\n\
id_agency2,My agency 2,http://my-agency_url2.com,Europe/London";
let routes_content =
"route_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,,My line 2,2,7BC142,000000\n\
route_3,3,My line 3,8,,\n\
route_4,3,My line 3 for agency 3,8,,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n\
4,route_3,0,service_3,,\n\
5,route_4,0,service_4,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (networks, _) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
});
}
#[test]
#[should_panic(expected = "Impossible to get agency id, no network found")]
fn gtfs_routes_without_agency_id_as_line_and_0_agencies() {
let routes_content =
"route_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,,My line 2,2,7BC142,000000\n\
route_3,3,My line 3,8,,\n\
route_4,3,My line 3 for agency 3,8,,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n\
4,route_3,0,service_3,,\n\
5,route_4,0,service_4,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
});
}
#[test]
fn gtfs_routes_as_route() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_agency,My agency,http://my-agency_url.com,Europe/London";
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1A,3,8F7A32,FFFFFF\n\
route_2,agency_1,1,My line 1B,3,8F7A32,FFFFFF\n\
route_4,agency_2,1,My line 1B,3,8F7A32,FFFFFF\n\
route_3,agency_2,1,My line 1B,3,8F7A32,FFFFFF\n\
route_5,,1,My line 1C,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_2,0,service_1,,\n\
3,route_3,0,service_2,,\n\
4,route_4,0,service_2,,\n\
5,route_5,0,service_3,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (networks, _) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(
vec!["agency_1", "agency_2", "id_agency"],
extract(|l| &l.network_id, &collections.lines)
);
assert_eq!(
vec!["route_1", "route_3", "route_5"],
extract_ids(&collections.lines)
);
assert_eq!(5, collections.routes.len());
assert_eq!(
vec!["route_1", "route_1", "route_3", "route_3", "route_5"],
extract(|r| &r.line_id, &collections.routes)
);
});
}
#[test]
fn gtfs_routes_as_route_with_backward_trips() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1A,3,8F7A32,FFFFFF\n\
route_2,agency_1,1,My line 1B,3,8F7A32,FFFFFF\n\
route_3,agency_2,,My line 2,2,7BC142,000000";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_1,1,service_1,,\n\
3,route_2,0,service_2,,\n
4,route_3,0,service_3,,\n\
5,route_3,1,service_3,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(2, collections.lines.len());
assert_eq!(5, collections.routes.len());
assert_eq!(
vec!["route_1", "route_1_R", "route_2", "route_3", "route_3_R"],
extract_ids(&collections.routes)
);
});
}
#[test]
fn gtfs_routes_as_route_same_name_different_agency() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1A,3,8F7A32,FFFFFF\n\
route_2,agency_1,1,My line 1B,3,8F7A32,FFFFFF\n\
route_3,agency_2,1,My line 1 for agency 2,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_2,0,service_2,,\n
3,route_3,0,service_3,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(2, collections.lines.len());
assert_eq!(vec!["route_1", "route_3"], extract_ids(&collections.lines));
assert_eq!(
vec!["route_1", "route_2", "route_3"],
extract_ids(&collections.routes)
);
assert_eq!(
vec!["route_1", "route_1", "route_3"],
extract(|r| &r.line_id, &collections.routes)
);
});
}
#[test]
fn gtfs_routes_with_no_trips() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,agency_2,2,My line 2,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(1, collections.lines.len());
assert_eq!(1, collections.routes.len());
});
}
#[test]
fn prefix_on_all_pt_object_id() {
let stops_content =
"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station,wheelchair_boarding\n\
sp:01,my stop point name,my first desc,0.1,1.2,0,,1\n\
sp:02,my stop point name child,,0.2,1.5,0,sp:01,2\n\
sa:03,my stop area name,my second desc,0.3,2.2,1,,1";
let agency_content = "agency_id,agency_name,agency_url,agency_timezone,agency_lang\n\
584,TAM,http://whatever.canaltp.fr/,Europe/Paris,fr\n\
285,Phébus,http://plop.kisio.com/,Europe/London,en";
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color,destination_id\n\
route_1,agency_1,1,My line 1A,3,8F7A32,FFFFFF,\n\
route_2,agency_1,2,My line 1B,3,8F7A32,FFFFFF,sp:01";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed,shape_id\n\
1,route_1,0,service_1,,,1\n\
2,route_2,1,service_2,1,2,2";
let transfers_content = "from_stop_id,to_stop_id,transfer_type,min_transfer_time\n\
sp:01,sp:01,1,\n\
sp:01,sp:02,0,\n\
sp:02,sp:01,0,\n\
sp:02,sp:02,1,";
let shapes_content = "shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence\n\
1,4.4,3.3,2\n\
2,6.6,5.5,1";
let calendar = "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date\n\
1,0,0,0,0,0,1,1,20180501,20180508\n\
2,1,0,0,0,0,0,0,20180502,20180506";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "transfers.txt", transfers_content);
create_file_with_content(path, "shapes.txt", shapes_content);
create_file_with_content(path, "calendar.txt", calendar);
let mut collections = Collections::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let (stop_areas, stop_points, stop_locations) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.equipments = CollectionWithId::new(equipments.into_equipments()).unwrap();
collections.transfers = super::read_transfers(&mut handler, &stop_points).unwrap();
collections.stop_areas = stop_areas;
collections.stop_points = stop_points;
collections.stop_locations = stop_locations;
let (networks, companies) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
collections.companies = companies;
collections.comments = comments;
super::read_routes(&mut handler, &mut collections).unwrap();
super::manage_shapes(&mut collections, &mut handler).unwrap();
calendars::manage_calendars(&mut handler, &mut collections).unwrap();
let mut prefix_conf = PrefixConfiguration::default();
prefix_conf.set_data_prefix("my_prefix");
collections.prefix(&prefix_conf);
assert_eq!(
vec!["my_prefix:285", "my_prefix:584"],
extract_ids(&collections.companies)
);
assert_eq!(
vec!["my_prefix:285", "my_prefix:584"],
extract_ids(&collections.networks)
);
assert_eq!(
vec![
("my_prefix:Navitia:sp:01", None),
("my_prefix:sa:03", Some("my_prefix:0")),
],
extract(
|obj| (obj.id.as_str(), obj.equipment_id.as_deref()),
&collections.stop_areas,
)
);
assert_eq!(
vec![
(
"my_prefix:sp:01",
"my_prefix:Navitia:sp:01",
Some("my_prefix:0")
),
("my_prefix:sp:02", "my_prefix:sp:01", Some("my_prefix:1")),
],
extract(
|obj| (
obj.id.as_str(),
obj.stop_area_id.as_str(),
obj.equipment_id.as_deref()
),
&collections.stop_points,
)
);
assert_eq!(
vec![
("my_prefix:route_1", "my_prefix:agency_1", "Bus", None, None),
("my_prefix:route_2", "my_prefix:agency_1", "Bus", None, None),
],
extract(
|obj| (
obj.id.as_str(),
obj.network_id.as_str(),
obj.commercial_mode_id.as_str(),
obj.forward_direction.as_deref(),
obj.backward_direction.as_deref(),
),
&collections.lines,
)
);
assert_eq!(
vec![
("my_prefix:route_1", "my_prefix:route_1", None),
("my_prefix:route_2_R", "my_prefix:route_2", None),
],
extract(
|obj| (
obj.id.as_str(),
obj.line_id.as_str(),
obj.destination_id.as_deref()
),
&collections.routes,
)
);
assert_eq!(
vec!["my_prefix:1"],
extract_ids(&collections.trip_properties)
);
assert_eq!(
vec!["my_prefix:stop:sa:03", "my_prefix:stop:sp:01"],
extract_ids(&collections.comments)
);
assert_eq!(vec!["Bus"], extract_ids(&collections.commercial_modes));
assert_eq!(
vec![
("my_prefix:sp:01", "my_prefix:sp:01"),
("my_prefix:sp:01", "my_prefix:sp:02"),
("my_prefix:sp:02", "my_prefix:sp:01"),
("my_prefix:sp:02", "my_prefix:sp:02"),
],
extract(
|sp| (sp.from_stop_id.as_str(), sp.to_stop_id.as_str()),
&collections.transfers,
)
);
assert_eq!(
vec!["my_prefix:default_contributor"],
extract_ids(&collections.contributors)
);
assert_eq!(
vec![("my_prefix:default_dataset", "my_prefix:default_contributor")],
extract(
|obj| (obj.id.as_str(), obj.contributor_id.as_str()),
&collections.datasets,
)
);
assert_eq!(
vec![
(
"my_prefix:1",
"my_prefix:route_1",
"my_prefix:default_dataset",
"my_prefix:service_1",
Some("my_prefix:1"),
),
(
"my_prefix:2",
"my_prefix:route_2_R",
"my_prefix:default_dataset",
"my_prefix:service_2",
Some("my_prefix:2"),
),
],
extract(
|obj| (
obj.id.as_str(),
obj.route_id.as_str(),
obj.dataset_id.as_str(),
obj.service_id.as_str(),
obj.geometry_id.as_deref()
),
&collections.vehicle_journeys,
)
);
assert_eq!(
vec!["my_prefix:0", "my_prefix:1"],
extract_ids(&collections.equipments)
);
assert_eq!(
vec!["my_prefix:1", "my_prefix:2"],
extract_ids(&collections.geometries)
);
assert_eq!(vec!["my_prefix:1"], extract_ids(&collections.calendars));
});
}
#[test]
fn gtfs_trips() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,agency_2,2,My line 2,3,8F7A32,FFFFFF\n\
route_3,agency_3,3,My line 3,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_2,0,service_1,1,2\n\
3,route_3,0,service_1,1,2
4,unknown_route,0,service_1,1,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(3, collections.routes.len());
assert_eq!(3, collections.vehicle_journeys.len());
assert_eq!(
vec!["agency_1", "agency_2", "agency_3"],
extract(|vj| &vj.company_id, &collections.vehicle_journeys)
);
assert_eq!(1, collections.trip_properties.len());
});
}
#[test]
fn gtfs_trips_with_routes_without_agency_id() {
let agency_content = "agency_id,agency_name,agency_url,agency_timezone\n\
id_agency,My agency,http://my-agency_url.com,Europe/London";
let routes_content =
"route_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,2,My line 2,3,8F7A32,FFFFFF\n\
route_3,3,My line 3,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_2,0,service_1,1,2\n\
3,route_3,0,service_1,1,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "agency.txt", agency_content);
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (networks, _) = super::read_agency(&mut handler).unwrap();
collections.networks = networks;
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(3, collections.routes.len());
assert_eq!(3, collections.vehicle_journeys.len());
assert_eq!(
vec!["id_agency", "id_agency", "id_agency"],
extract(|vj| &vj.company_id, &collections.vehicle_journeys)
);
assert_eq!(1, collections.trip_properties.len());
});
}
#[test]
fn gtfs_trips_no_direction_id() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF\n\
route_2,agency_2,2,My line 2,3,8F7A32,FFFFFF\n\
route_3,agency_3,3,My line 3,3,8F7A32,FFFFFF";
let trips_content = "trip_id,route_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,service_1,,\n\
2,route_2,service_1,1,2\n\
3,route_3,service_1,1,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(3, collections.lines.len());
assert_eq!(3, collections.routes.len());
assert_eq!(
vec![
&Some("forward".to_string()),
&Some("forward".to_string()),
&Some("forward".to_string())
],
extract(|r| &r.direction_type, &collections.routes)
);
});
}
#[test]
fn gtfs_trips_with_no_accessibility_information() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,\n\
2,route_1,0,service_2,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(2, collections.vehicle_journeys.len());
assert_eq!(0, collections.trip_properties.len());
for vj in collections.vehicle_journeys.values() {
assert!(vj.trip_property_id.is_none());
}
});
}
#[test]
fn push_on_collection() {
let mut c = CollectionWithId::default();
c.push(Comment {
id: "foo".into(),
name: "toto".into(),
comment_type: CommentType::Information,
url: None,
label: None,
})
.unwrap();
assert!(c
.push(Comment {
id: "foo".into(),
name: "tata".into(),
comment_type: CommentType::Information,
url: None,
label: None,
})
.is_err());
let id = c.get_idx("foo").unwrap();
assert_eq!(c.iter().next().unwrap().0, id);
}
#[test]
fn stops_generates_equipments() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station,wheelchair_boarding\n\
sp:01,my stop point name,0.1,1.2,0,,1\n\
sp:02,my stop point name child,0.2,1.5,0,sp:01,\n\
sa:03,my stop area name,0.3,2.2,1,,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (stop_areas, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
let equipments_collection =
CollectionWithId::new(equipments.into_equipments()).unwrap();
assert_eq!(2, stop_areas.len());
assert_eq!(2, stop_points.len());
assert_eq!(2, equipments_collection.len());
let mut stop_point_equipment_ids: Vec<Option<String>> = stop_points
.iter()
.map(|(_, stop_point)| stop_point.equipment_id.clone())
.collect();
stop_point_equipment_ids.sort();
assert_eq!(vec![None, Some("0".to_string())], stop_point_equipment_ids);
assert_eq!(
vec![&None, &Some("1".to_string())],
extract(|sa| &sa.equipment_id, &stop_areas)
);
use objects::Availability::*;
assert_eq!(
vec![
Equipment {
id: "0".to_string(),
wheelchair_boarding: Available,
sheltered: InformationNotAvailable,
elevator: InformationNotAvailable,
escalator: InformationNotAvailable,
bike_accepted: InformationNotAvailable,
bike_depot: InformationNotAvailable,
visual_announcement: InformationNotAvailable,
audible_announcement: InformationNotAvailable,
appropriate_escort: InformationNotAvailable,
appropriate_signage: InformationNotAvailable,
},
Equipment {
id: "1".to_string(),
wheelchair_boarding: NotAvailable,
sheltered: InformationNotAvailable,
elevator: InformationNotAvailable,
escalator: InformationNotAvailable,
bike_accepted: InformationNotAvailable,
bike_depot: InformationNotAvailable,
visual_announcement: InformationNotAvailable,
audible_announcement: InformationNotAvailable,
appropriate_escort: InformationNotAvailable,
appropriate_signage: InformationNotAvailable,
},
],
equipments_collection.into_vec()
);
});
}
#[test]
fn stops_do_not_generate_duplicate_equipments() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station,wheelchair_boarding\n\
sp:01,my stop point name 1,0.1,1.2,0,,1\n\
sp:02,my stop point name 2,0.2,1.5,0,,1";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
let equipments_collection =
CollectionWithId::new(equipments.into_equipments()).unwrap();
assert_eq!(2, stop_points.len());
assert_eq!(1, equipments_collection.len());
let mut stop_point_equipment_ids: Vec<Option<String>> = stop_points
.iter()
.map(|(_, stop_point)| stop_point.equipment_id.clone())
.collect();
stop_point_equipment_ids.sort();
assert_eq!(
vec![Some("0".to_string()), Some("0".to_string())],
stop_point_equipment_ids
);
use objects::Availability::*;
assert_eq!(
vec![Equipment {
id: "0".to_string(),
wheelchair_boarding: Available,
sheltered: InformationNotAvailable,
elevator: InformationNotAvailable,
escalator: InformationNotAvailable,
bike_accepted: InformationNotAvailable,
bike_depot: InformationNotAvailable,
visual_announcement: InformationNotAvailable,
audible_announcement: InformationNotAvailable,
appropriate_escort: InformationNotAvailable,
appropriate_signage: InformationNotAvailable,
}],
equipments_collection.into_vec()
);
});
}
#[test]
fn gtfs_stop_times_estimated() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let stops_content =
"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station\n\
sp:01,my stop point name 1,my first desc,0.1,1.2,0,\n\
sp:02,my stop point name 2,,0.2,1.5,0,\n\
sp:03,my stop point name 2,,0.2,1.5,0,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
let stop_times_content = "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled,timepoint\n\
1,06:00:00,06:00:00,sp:01,1,over there,,,,0\n\
1,06:06:27,06:06:27,sp:02,2,,2,1,,1\n\
1,06:06:27,06:06:27,sp:03,3,,2,1,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "stop_times.txt", stop_times_content);
create_file_with_content(path, "stops.txt", stops_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_points = stop_points;
super::read_routes(&mut handler, &mut collections).unwrap();
super::manage_stop_times(&mut collections, &mut handler, false, None).unwrap();
assert_eq!(
vec![
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:01").unwrap(),
sequence: 1,
arrival_time: Time::new(6, 0, 0),
departure_time: Time::new(6, 0, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
datetime_estimated: true,
local_zone_id: None,
precision: Some(StopTimePrecision::Approximate),
},
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:02").unwrap(),
sequence: 2,
arrival_time: Time::new(6, 6, 27),
departure_time: Time::new(6, 6, 27),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 2,
drop_off_type: 1,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:03").unwrap(),
sequence: 3,
arrival_time: Time::new(6, 6, 27),
departure_time: Time::new(6, 6, 27),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 2,
drop_off_type: 1,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
],
collections.vehicle_journeys.into_vec()[0].stop_times
);
});
}
#[test]
fn gtfs_stop_times() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let stops_content =
"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station\n\
sp:01,my stop point name 1,my first desc,0.1,1.2,0,\n\
sp:02,my stop point name 2,,0.2,1.5,0,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
let stop_times_content = "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled\n\
1,06:00:00,06:00:00,sp:01,1,over there,,,\n\
1,06:06:27,06:06:27,sp:02,2,,2,1,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "stop_times.txt", stop_times_content);
create_file_with_content(path, "stops.txt", stops_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_points = stop_points;
super::read_routes(&mut handler, &mut collections).unwrap();
super::manage_stop_times(&mut collections, &mut handler, false, None).unwrap();
assert_eq!(
vec![
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:01").unwrap(),
sequence: 1,
arrival_time: Time::new(6, 0, 0),
departure_time: Time::new(6, 0, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:02").unwrap(),
sequence: 2,
arrival_time: Time::new(6, 6, 27),
departure_time: Time::new(6, 6, 27),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 2,
drop_off_type: 1,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
],
collections.vehicle_journeys.into_vec()[0].stop_times
);
let headsigns: Vec<String> =
collections.stop_time_headsigns.values().cloned().collect();
assert_eq!(vec!["over there".to_string()], headsigns);
});
}
#[test]
fn read_tranfers() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station,wheelchair_boarding\n\
sp:01,my stop point name 1,48.857332,2.346331,0,,1\n\
sp:02,my stop point name 2,48.858195,2.347448,0,,1\n\
sp:03,my stop point name 3,48.859031,2.346958,0,,1";
let transfers_content = "from_stop_id,to_stop_id,transfer_type,min_transfer_time\n\
sp:01,sp:01,1,\n\
sp:01,sp:02,0,\n\
sp:01,sp:03,2,60\n\
sp:02,sp:01,0,\n\
sp:02,sp:02,1,\n\
sp:02,sp:03,3,\n\
sp:03,sp:01,0,\n\
sp:03,sp:02,2,\n\
sp:03,sp:03,0,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
create_file_with_content(path, "transfers.txt", transfers_content);
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
let transfers = super::read_transfers(&mut handler, &stop_points).unwrap();
assert_eq!(
vec![
&Transfer {
from_stop_id: "sp:01".to_string(),
to_stop_id: "sp:01".to_string(),
min_transfer_time: Some(0),
real_min_transfer_time: Some(0),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:01".to_string(),
to_stop_id: "sp:02".to_string(),
min_transfer_time: Some(160),
real_min_transfer_time: Some(280),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:01".to_string(),
to_stop_id: "sp:03".to_string(),
min_transfer_time: Some(60),
real_min_transfer_time: Some(60),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:02".to_string(),
to_stop_id: "sp:01".to_string(),
min_transfer_time: Some(160),
real_min_transfer_time: Some(280),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:02".to_string(),
to_stop_id: "sp:02".to_string(),
min_transfer_time: Some(0),
real_min_transfer_time: Some(0),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:02".to_string(),
to_stop_id: "sp:03".to_string(),
min_transfer_time: Some(86400),
real_min_transfer_time: Some(86400),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:03".to_string(),
to_stop_id: "sp:01".to_string(),
min_transfer_time: Some(247),
real_min_transfer_time: Some(367),
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:03".to_string(),
to_stop_id: "sp:02".to_string(),
min_transfer_time: None,
real_min_transfer_time: None,
equipment_id: None,
},
&Transfer {
from_stop_id: "sp:03".to_string(),
to_stop_id: "sp:03".to_string(),
min_transfer_time: Some(0),
real_min_transfer_time: Some(120),
equipment_id: None,
},
],
transfers.values().collect::<Vec<_>>()
);
});
}
#[test]
fn gtfs_with_calendars_and_no_calendar_dates() {
let content = "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date\n\
1,0,0,0,0,0,1,1,20180501,20180508\n\
2,1,0,0,0,0,0,0,20180502,20180506";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "calendar.txt", content);
let mut collections = Collections::default();
calendars::manage_calendars(&mut handler, &mut collections).unwrap();
let mut dates = BTreeSet::new();
dates.insert(chrono::NaiveDate::from_ymd(2018, 5, 5));
dates.insert(chrono::NaiveDate::from_ymd(2018, 5, 6));
assert_eq!(
vec![Calendar {
id: "1".to_string(),
dates,
},],
collections.calendars.into_vec()
);
});
}
#[test]
fn gtfs_with_calendars_dates_and_no_calendar() {
let content = "service_id,date,exception_type\n\
1,20180212,1\n\
1,20180211,2\n\
2,20180211,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "calendar_dates.txt", content);
let mut collections = Collections::default();
calendars::manage_calendars(&mut handler, &mut collections).unwrap();
let mut dates = BTreeSet::new();
dates.insert(chrono::NaiveDate::from_ymd(2018, 2, 12));
assert_eq!(
vec![Calendar {
id: "1".to_string(),
dates,
}],
collections.calendars.into_vec()
);
});
}
#[test]
fn gtfs_with_calendars_and_calendar_dates() {
let calendars_content = "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date\n\
1,0,0,0,0,0,1,1,20180501,20180508\n\
2,0,0,0,0,0,0,1,20180502,20180506";
let calendar_dates_content = "service_id,date,exception_type\n\
1,20180507,1\n\
1,20180505,2\n\
2,20180506,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "calendar.txt", calendars_content);
create_file_with_content(path, "calendar_dates.txt", calendar_dates_content);
let mut collections = Collections::default();
calendars::manage_calendars(&mut handler, &mut collections).unwrap();
let mut dates = BTreeSet::new();
dates.insert(chrono::NaiveDate::from_ymd(2018, 5, 6));
dates.insert(chrono::NaiveDate::from_ymd(2018, 5, 7));
assert_eq!(
vec![
Calendar {
id: "1".to_string(),
dates,
},
Calendar {
id: "2".to_string(),
dates: BTreeSet::new(),
},
],
collections.calendars.into_vec()
);
});
}
#[test]
#[should_panic(expected = "calendar_dates.txt or calendar.txt not found")]
fn gtfs_without_calendar_dates_or_calendar() {
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
let mut collections = Collections::default();
calendars::manage_calendars(&mut handler, &mut collections).unwrap();
});
}
#[test]
fn read_shapes() {
let shapes_content = "shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence\n\
1,4.4,3.3,2\n\
1,2.2,1.1,1\n\
2,6.6,5.5,1\n\
2,,7.7,2\n\
2,8.8,,3\n\
2,,,4\n\
2,,,5\n\
3,,,1\n\
3,,,2";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "shapes.txt", shapes_content);
let mut collections = Collections::default();
super::manage_shapes(&mut collections, &mut handler).unwrap();
let mut geometries = collections.geometries.into_vec();
geometries.sort_unstable_by_key(|s| s.id.clone());
assert_eq!(
vec![
Geometry {
id: "1".to_string(),
geometry: line_string![(x: 1.1, y: 2.2), (x: 3.3, y: 4.4)].into(),
},
Geometry {
id: "2".to_string(),
geometry: line_string![(x: 5.5, y: 6.6)].into(),
},
],
geometries
);
});
}
#[test]
fn read_shapes_with_no_shapes_file() {
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
let mut collections = Collections::default();
super::manage_shapes(&mut collections, &mut handler).unwrap();
let geometries = collections.geometries.into_vec();
assert_eq!(Vec::<Geometry>::new(), geometries);
});
}
#[test]
fn deduplicate_funicular_physical_mode() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color\n\
route:1,agency:1,S1,S 1,,5,,ffea00,000000\n\
route:2,agency:1,L2,L 2,,6,,ffea00,000000\n\
route:3,agency:1,L3,L 3,,2,,ffea00,000000\n\
route:4,agency:2,57,57,,7,,ffea00,000000";
let trips_content = "route_id,service_id,trip_id,trip_headsign,direction_id,shape_id\n\
route:1,service:1,trip:1,pouet,0,\n\
route:2,service:1,trip:2,pouet,0,\n\
route:3,service:1,trip:3,pouet,0,\n\
route:4,service:1,trip:4,pouet,0,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
super::read_routes(&mut handler, &mut collections).unwrap();
assert_eq!(4, collections.lines.len());
assert_eq!(4, collections.commercial_modes.len());
assert_eq!(
vec!["Funicular", "SuspendedCableCar", "Train"],
extract_ids(&collections.physical_modes)
);
});
}
#[test]
fn location_type_default_value() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type\n\
stop:1,Tornio pouet,65.843294,24.145138,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (stop_areas, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
assert_eq!(1, stop_points.len());
assert_eq!(1, stop_areas.len());
let stop_area = stop_areas.iter().next().unwrap().1;
assert_eq!("Navitia:stop:1", stop_area.id);
let stop_point = stop_points.iter().next().unwrap().1;
assert_eq!("stop:1", stop_point.id);
});
}
#[test]
fn location_with_space_proof() {
let stops_content = "stop_id,stop_name,stop_lat,stop_lon,location_type\n\
stop:1,plop, 65.444,24.156 ,0\n\
stop:2,plop 2, 66.666 , 26.123,0\n\
stop:3,invalid loc, ,25.558,0";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
assert_eq!(3, stop_points.len());
let longitudes: Vec<f64> = stop_points
.values()
.map(|sp| &sp.coord.lon)
.cloned()
.collect();
assert_eq!(vec![24.156, 26.123, 25.558], longitudes);
let latitudes: Vec<f64> = stop_points
.values()
.map(|sp| &sp.coord.lat)
.cloned()
.collect();
assert_eq!(vec![65.444, 66.666, 0.00], latitudes);
});
}
#[test]
fn gtfs_undefined_stop_times() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let stops_content = r#"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station
sp:01,my stop point name 1,my first desc,0.1,1.2,0,
sp:02,my stop point name 2,my first desc,0.1,1.2,0,
sp:03,my stop point name 3,my first desc,0.1,1.2,0,
sp:04,my stop point name 4,my first desc,0.1,1.2,0,
sp:05,my stop point name 5,my first desc,0.1,1.2,0,
sp:06,my stop point name 6,my first desc,0.1,1.2,0,
sp:07,my stop point name 7,my first desc,0.1,1.2,0,
sp:08,my stop point name 8,my first desc,0.1,1.2,0,"#;
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
let stop_times_content = "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled\n\
1,06:00:00,06:00:00,sp:01,1,,,,\n\
1,07:00:00,07:00:00,sp:02,2,,,,\n\
1,,,sp:03,3,,,,\n\
1,,,sp:04,4,,,,\n\
1,10:00:00,,sp:05,5,,,,\n\
1,,,sp:06,6,,,,\n\
1,,12:00:00,sp:07,7,,,,\n\
1,13:00:00,13:00:00,sp:08,8,,,,\n\
";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "stop_times.txt", stop_times_content);
create_file_with_content(path, "stops.txt", stops_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_points = stop_points;
super::read_routes(&mut handler, &mut collections).unwrap();
super::manage_stop_times(&mut collections, &mut handler, false, None).unwrap();
assert_eq!(
vec![
(Time::new(6, 0, 0), Time::new(6, 0, 0)),
(Time::new(7, 0, 0), Time::new(7, 0, 0)),
(Time::new(8, 0, 0), Time::new(8, 0, 0)),
(Time::new(9, 0, 0), Time::new(9, 0, 0)),
(Time::new(10, 0, 0), Time::new(10, 0, 0)),
(Time::new(11, 0, 0), Time::new(11, 0, 0)),
(Time::new(12, 0, 0), Time::new(12, 0, 0)),
(Time::new(13, 0, 0), Time::new(13, 0, 0)),
],
collections.vehicle_journeys.into_vec()[0]
.stop_times
.iter()
.map(|st| (st.arrival_time, st.departure_time))
.collect::<Vec<_>>()
);
});
}
#[test]
fn gtfs_invalid_undefined_stop_times() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let stops_content = r#"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station
sp:01,my stop point name 1,my first desc,0.1,1.2,0,
sp:02,my stop point name 2,my first desc,0.1,1.2,0,
sp:03,my stop point name 3,my first desc,0.1,1.2,0,
sp:04,my stop point name 4,my first desc,0.1,1.2,0,
sp:05,my stop point name 5,my first desc,0.1,1.2,0,
sp:06,my stop point name 6,my first desc,0.1,1.2,0,
sp:07,my stop point name 7,my first desc,0.1,1.2,0,
sp:08,my stop point name 8,my first desc,0.1,1.2,0,"#;
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
let stop_times_content = "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled\n\
1,,,sp:01,1,,,,\n\
1,07:00:00,07:00:00,sp:02,2,,,,\n\
1,,,sp:03,3,,,,\n\
1,,,sp:04,4,,,,\n\
1,10:00:00,10:00:00,sp:05,5,,,,\n\
1,,,sp:06,6,,,,\n\
1,12:00:00,12:00:00,sp:07,7,,,,\n\
1,13:00:00,13:00:00,sp:08,8,,,,\n\
";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "stop_times.txt", stop_times_content);
create_file_with_content(path, "stops.txt", stops_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_points = stop_points;
super::read_routes(&mut handler, &mut collections).unwrap();
let val = super::manage_stop_times(&mut collections, &mut handler, false, None);
let err = val.unwrap_err();
assert_eq!( "the first stop time of the vj '1' has no departure/arrival, the stop_times.txt file is not valid",format!("{}", err));
});
}
#[test]
fn stop_location_on_stops() {
let stops_content =
"stop_id,stop_code,stop_name,stop_lat,stop_lon,location_type,parent_station\n\
stoppoint_id,1234,my stop name,0.1,1.2,0,stop_area_id\n\
stoparea_id,5678,stop area name,0.1,1.2,1,\n\
entrance_id,,entrance name,0.1,1.2,2,stop_area_id\n\
node_id,,node name,0.1,1.2,3,stop_area_id\n\
boarding_id,,boarding name,0.1,1.2,4,stoppoint_id";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
let mut equipments = EquipmentList::default();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let (_, _, stop_locations) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
let stop_entrance = stop_locations
.values()
.filter(|sl| sl.stop_type == StopType::StopEntrance)
.collect::<Vec<_>>();
assert_eq!(1, stop_entrance.len());
let stop_node = stop_locations
.values()
.filter(|sl| sl.stop_type == StopType::GenericNode)
.collect::<Vec<_>>();
assert_eq!(1, stop_node.len());
let stop_boarding = stop_locations
.values()
.filter(|sl| sl.stop_type == StopType::GenericNode)
.collect::<Vec<_>>();
assert_eq!(1, stop_boarding.len());
});
}
#[test]
fn filter_pathway() {
let stops_content =
"stop_id,stop_code,stop_name,stop_lat,stop_lon,location_type,parent_station,level_id\n\
stoppoint_id,1234,my stop name,0.1,1.2,0,stop_area_id,2\n\
stoparea_id,5678,stop area name,0.1,1.2,1,,\n\
entrance_id,,entrance name,0.1,1.2,2,stop_area_id,1\n\
node_id,,node name,0.1,1.2,3,stop_area_id,2\n\
boarding_id,,boarding name,0.1,1.2,4,stoppoint_id,";
let pathway_content = "pathway_id,from_stop_id,to_stop_id,pathway_mode,is_bidirectional\n\
1;stoppoint_id,stoparea_id,8,0\n\
2,stoppoint_id,stoparea_id,1,3\n\
3,stoppoint_id,stoparea_id_0,2,0\n\
4,stoppoint_id,stoparea_id,1,0
5,node_id,boarding_id,1,0";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
create_file_with_content(path, "pathways.txt", pathway_content);
let mut collections = Collections::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, stop_locations) =
super::read_stops(&mut handler, &mut collections.comments, &mut equipments)
.unwrap();
collections.stop_points = stop_points;
collections.stop_locations = stop_locations;
super::manage_pathways(&mut collections, &mut handler).unwrap();
assert_eq!(1, collections.pathways.len());
})
}
#[test]
fn read_levels() {
let stops_content =
"stop_id,stop_code,stop_name,stop_lat,stop_lon,location_type,parent_station,level_id\n\
stoppoint_id,1234,my stop name,0.1,1.2,0,stop_area_id,2\n\
stoparea_id,5678,stop area name,0.1,1.2,1,,\n\
entrance_id,,entrance name,0.1,1.2,2,stop_area_id,1\n\
node_id,,node name,0.1,1.2,3,stop_area_id,2\n\
boarding_id,,boarding name,0.1,1.2,4,stoppoint_id,";
let level_content = "level_id,level_index\n\
1,0\n\
2,2\n\
3,1\n\
4,4";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "stops.txt", stops_content);
create_file_with_content(path, "levels.txt", level_content);
let levels: CollectionWithId<Level> =
read_opt_collection(&mut handler, "levels.txt").unwrap();
assert_eq!(4, levels.len());
})
}
#[test]
fn gtfs_stop_times_precision() {
let routes_content = "route_id,agency_id,route_short_name,route_long_name,route_type,route_color,route_text_color\n\
route_1,agency_1,1,My line 1,3,8F7A32,FFFFFF";
let stops_content =
"stop_id,stop_name,stop_desc,stop_lat,stop_lon,location_type,parent_station\n\
sp:01,my stop point name 1,my first desc,0.1,1.2,0,\n\
sp:02,my stop point name 2,,0.2,1.5,0,\n\
sp:03,my stop point name 2,,0.2,1.5,0,";
let trips_content =
"trip_id,route_id,direction_id,service_id,wheelchair_accessible,bikes_allowed\n\
1,route_1,0,service_1,,";
let stop_times_content = "trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled,timepoint\n\
1,06:00:00,06:00:00,sp:01,1,over there,,,,0\n\
1,06:06:27,06:06:27,sp:02,2,,2,1,,1\n\
1,06:06:27,06:06:27,sp:03,3,,2,1,,";
test_in_tmp_dir(|path| {
let mut handler = PathFileHandler::new(path.to_path_buf());
create_file_with_content(path, "routes.txt", routes_content);
create_file_with_content(path, "trips.txt", trips_content);
create_file_with_content(path, "stop_times.txt", stop_times_content);
create_file_with_content(path, "stops.txt", stops_content);
let mut collections = Collections::default();
let (contributor, dataset, _) = read_utils::read_config(None::<&str>).unwrap();
collections.contributors = CollectionWithId::new(vec![contributor]).unwrap();
collections.datasets = CollectionWithId::new(vec![dataset]).unwrap();
let mut comments: CollectionWithId<Comment> = CollectionWithId::default();
let mut equipments = EquipmentList::default();
let (_, stop_points, _) =
super::read_stops(&mut handler, &mut comments, &mut equipments).unwrap();
collections.stop_points = stop_points;
super::read_routes(&mut handler, &mut collections).unwrap();
super::manage_stop_times(&mut collections, &mut handler, true, None).unwrap();
assert_eq!(
vec![
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:01").unwrap(),
sequence: 1,
arrival_time: Time::new(6, 0, 0),
departure_time: Time::new(6, 0, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
datetime_estimated: true,
local_zone_id: None,
precision: Some(StopTimePrecision::Estimated),
},
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:02").unwrap(),
sequence: 2,
arrival_time: Time::new(6, 6, 27),
departure_time: Time::new(6, 6, 27),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 2,
drop_off_type: 1,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
StopTime {
stop_point_idx: collections.stop_points.get_idx("sp:03").unwrap(),
sequence: 3,
arrival_time: Time::new(6, 6, 27),
departure_time: Time::new(6, 6, 27),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 2,
drop_off_type: 1,
datetime_estimated: false,
local_zone_id: None,
precision: Some(StopTimePrecision::Exact),
},
],
collections.vehicle_journeys.into_vec()[0].stop_times
);
});
}
}