use crate::{
model::Collections,
objects::{StopTime, VehicleJourney},
};
use std::collections::{HashMap, HashSet};
use typed_index_collection::Idx;
pub fn enhance_pickup_dropoff(collections: &mut Collections) {
let mut allowed_last_pick_up_vj = HashSet::new();
let mut allowed_first_drop_off_vj = HashSet::new();
let can_chain_without_overlap = |prev_vj: &VehicleJourney, next_vj: &VehicleJourney| {
let last_stop = &prev_vj.stop_times.last();
let first_stop = &next_vj.stop_times.first();
if let (Some(last_stop), Some(first_stop)) = (last_stop, first_stop) {
if last_stop.pickup_type == 3
|| first_stop.pickup_type == 3
|| last_stop.drop_off_type == 3
|| first_stop.drop_off_type == 3
{
return false;
}
if last_stop.stop_point_idx != first_stop.stop_point_idx {
match (
collections.calendars.get(&prev_vj.service_id),
collections.calendars.get(&next_vj.service_id),
) {
(Some(prev), Some(next)) => {
return last_stop.departure_time <= first_stop.arrival_time
&& prev.overlaps(next);
}
_ => return false,
}
}
}
false
};
type BlockId = String;
let mut vj_by_blocks = HashMap::<BlockId, Vec<(Idx<VehicleJourney>, &VehicleJourney)>>::new();
for (b, (vj_idx, vj)) in collections
.vehicle_journeys
.iter()
.filter_map(|(vj_idx, vj)| vj.block_id.clone().map(|b| (b, (vj_idx, vj))))
{
let other_block_id_vj = vj_by_blocks.entry(b).or_insert_with(Vec::new);
for (other_vj_idx, other_vj) in other_block_id_vj.iter_mut() {
if can_chain_without_overlap(vj, other_vj) {
allowed_first_drop_off_vj.insert(*other_vj_idx);
allowed_last_pick_up_vj.insert(vj_idx);
} else if can_chain_without_overlap(other_vj, vj) {
allowed_first_drop_off_vj.insert(vj_idx);
allowed_last_pick_up_vj.insert(*other_vj_idx);
}
}
other_block_id_vj.push((vj_idx, vj));
}
let is_route_point =
|stop_time: &StopTime| stop_time.pickup_type == 3 || stop_time.drop_off_type == 3;
for vj_idx in collections.vehicle_journeys.indexes() {
let mut vj = collections.vehicle_journeys.index_mut(vj_idx);
if !allowed_first_drop_off_vj.contains(&vj_idx) {
if let Some(st) = vj.stop_times.iter_mut().find(|st| !is_route_point(st)) {
st.drop_off_type = 1;
}
}
if !allowed_last_pick_up_vj.contains(&vj_idx) {
if let Some(st) = vj
.stop_times
.iter_mut()
.rev()
.find(|st| !is_route_point(st))
{
st.pickup_type = 1;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::objects::{Calendar, Date, StopPoint, Time};
use pretty_assertions::assert_eq;
use std::collections::BTreeSet;
use typed_index_collection::CollectionWithId;
type VjConfig = (String, usize, Time, Time);
fn build_vehicle_journeys(
prev_vj_config: VjConfig,
next_vj_config: VjConfig,
) -> CollectionWithId<VehicleJourney> {
let mut stop_points = CollectionWithId::default();
let mut sp_idxs = Vec::new();
for i in 0..4 {
let idx = stop_points
.push(StopPoint {
id: format!("sp{}", i),
..Default::default()
})
.unwrap();
sp_idxs.push(idx);
}
let stop_time_1 = StopTime {
stop_point_idx: sp_idxs[0],
sequence: 0,
arrival_time: prev_vj_config.2 - Time::new(1, 0, 0),
departure_time: prev_vj_config.3 - Time::new(1, 0, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
local_zone_id: None,
precision: None,
};
let stop_time_2 = StopTime {
stop_point_idx: sp_idxs[prev_vj_config.1],
sequence: 0,
arrival_time: prev_vj_config.2,
departure_time: prev_vj_config.3,
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
local_zone_id: None,
precision: None,
};
let next_vj_config_time_1 = StopTime {
stop_point_idx: sp_idxs[next_vj_config.1],
sequence: 1,
arrival_time: next_vj_config.2,
departure_time: next_vj_config.3,
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
local_zone_id: None,
precision: None,
};
let next_vj_config_time_2 = StopTime {
stop_point_idx: sp_idxs[3],
sequence: 1,
arrival_time: next_vj_config.2 + Time::new(1, 0, 0),
departure_time: next_vj_config.3 + Time::new(1, 0, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 0,
drop_off_type: 0,
local_zone_id: None,
precision: None,
};
let vj1 = VehicleJourney {
id: "vj1".to_string(),
block_id: Some(prev_vj_config.0),
stop_times: vec![stop_time_1, stop_time_2],
..Default::default()
};
let vj2 = VehicleJourney {
id: "vj2".to_string(),
block_id: Some(next_vj_config.0),
stop_times: vec![next_vj_config_time_1, next_vj_config_time_2],
..Default::default()
};
CollectionWithId::new(vec![vj1, vj2]).unwrap()
}
#[test]
fn no_stay_in() {
let mut collections = Collections::default();
let prev_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
let next_vj_config = (
"block_id_2".to_string(),
2,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
collections.vehicle_journeys = build_vehicle_journeys(prev_vj_config, next_vj_config);
enhance_pickup_dropoff(&mut collections);
let vj1 = collections.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj2 = collections.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn stay_in_same_stop() {
let mut collections = Collections::default();
let prev_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
let next_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
collections.vehicle_journeys = build_vehicle_journeys(prev_vj_config, next_vj_config);
let mut dates = BTreeSet::new();
dates.insert(Date::from_ymd_opt(2020, 1, 1).unwrap());
collections.calendars = CollectionWithId::new(vec![Calendar {
id: "default_service".to_owned(),
dates,
}])
.unwrap();
enhance_pickup_dropoff(&mut collections);
let vj1 = collections.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj2 = collections.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn stay_in_different_stop_overlapping_time() {
let mut collections = Collections::default();
let prev_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(12, 0, 0),
);
let next_vj_config = (
"block_id_1".to_string(),
2,
Time::new(11, 0, 0),
Time::new(13, 0, 0),
);
collections.vehicle_journeys = build_vehicle_journeys(prev_vj_config, next_vj_config);
let mut dates = BTreeSet::new();
dates.insert(Date::from_ymd_opt(2020, 1, 1).unwrap());
collections.calendars = CollectionWithId::new(vec![Calendar {
id: "default_service".to_owned(),
dates,
}])
.unwrap();
enhance_pickup_dropoff(&mut collections);
let vj1 = collections.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj2 = collections.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn stay_in_different_stop() {
let mut collections = Collections::default();
let prev_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
let next_vj_config = (
"block_id_1".to_string(),
2,
Time::new(12, 0, 0),
Time::new(13, 0, 0),
);
collections.vehicle_journeys = build_vehicle_journeys(prev_vj_config, next_vj_config);
let mut dates = BTreeSet::new();
dates.insert(Date::from_ymd_opt(2020, 1, 1).unwrap());
collections.calendars = CollectionWithId::new(vec![Calendar {
id: "default_service".to_owned(),
dates,
}])
.unwrap();
enhance_pickup_dropoff(&mut collections);
let vj1 = collections.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj2 = collections.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn stay_in_different_stop_but_with_route_point() {
let mut collections = Collections::default();
let prev_vj_config = (
"block_id_1".to_string(),
1,
Time::new(10, 0, 0),
Time::new(11, 0, 0),
);
let next_vj_config = (
"block_id_1".to_string(),
2,
Time::new(12, 0, 0),
Time::new(13, 0, 0),
);
collections.vehicle_journeys = build_vehicle_journeys(prev_vj_config, next_vj_config);
let sp4_idx = collections
.stop_points
.push(StopPoint {
id: String::from("sp4"),
..Default::default()
})
.unwrap();
let vj_idx = collections.vehicle_journeys.get_idx("vj1").unwrap();
let mut vj_mut = collections.vehicle_journeys.index_mut(vj_idx);
vj_mut.stop_times.push(StopTime {
stop_point_idx: sp4_idx,
sequence: 2,
arrival_time: Time::new(11, 30, 0),
departure_time: Time::new(11, 30, 0),
boarding_duration: 0,
alighting_duration: 0,
pickup_type: 3,
drop_off_type: 3,
local_zone_id: None,
precision: None,
});
drop(vj_mut);
let mut dates = BTreeSet::new();
dates.insert(Date::from_ymd_opt(2020, 1, 1).unwrap());
collections.calendars = CollectionWithId::new(vec![Calendar {
id: "default_service".to_owned(),
dates,
}])
.unwrap();
enhance_pickup_dropoff(&mut collections);
let vj1 = collections.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times[1];
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let stop_time = &vj1.stop_times[2];
assert_eq!(3, stop_time.pickup_type);
assert_eq!(3, stop_time.drop_off_type);
let vj2 = collections.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj2.stop_times[1];
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn forbidden_drop_off_should_be_kept() {
let model = transit_model_builder::ModelBuilder::default()
.vj("vj1", |vj| {
vj.block_id("block_1")
.st("SP1", "10:00:00", "10:01:00")
.st_mut("SP2", "11:00:00", "11:01:00", |st| {
st.pickup_type = 1;
st.drop_off_type = 1;
});
})
.vj("vj2", |vj| {
vj.block_id("block_1")
.st_mut("SP3", "12:00:00", "12:01:00", |st| {
st.drop_off_type = 2; })
.st("SP4", "13:00:00", "13:01:00");
})
.build();
let vj1 = model.vehicle_journeys.get("vj1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type); let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let vj2 = model.vehicle_journeys.get("vj2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(2, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn block_id_on_overlapping_calendar_ok() {
let model = transit_model_builder::ModelBuilder::default()
.calendar("c1", &["2020-01-01", "2020-01-02", "2020-01-03"])
.calendar("c2", &["2020-01-01", "2020-01-02"])
.calendar("c3", &["2020-01-03", "2020-01-04"])
.vj("VJ:1", |vj| {
vj.block_id("block_1")
.calendar("c1")
.st("SP1", "10:00:00", "10:01:00")
.st("SP2", "11:00:00", "11:01:00");
})
.vj("VJ:2", |vj| {
vj.block_id("block_1")
.calendar("c2")
.st("SP3", "12:00:00", "12:01:00")
.st("SP4", "13:00:00", "13:01:00");
})
.vj("VJ:3", |vj| {
vj.block_id("block_1")
.calendar("c3")
.st("SP3", "12:30:00", "12:31:00")
.st("SP4", "13:30:00", "13:31:00");
})
.build();
let vj1 = model.vehicle_journeys.get("VJ:1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(0, stop_time.pickup_type); assert_eq!(0, stop_time.drop_off_type);
let vj2 = model.vehicle_journeys.get("VJ:2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type); let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type); assert_eq!(0, stop_time.drop_off_type);
let vj3 = model.vehicle_journeys.get("VJ:3").unwrap();
let stop_time = &vj3.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type); let stop_time = &vj3.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn block_id_on_overlapping_calendar_forbidden_pickup() {
let model = transit_model_builder::ModelBuilder::default()
.calendar(
"c1",
&["2020-01-01", "2020-01-02", "2020-01-03", "2020-01-04"],
)
.calendar("c2", &["2020-01-01", "2020-01-02", "2020-01-03"])
.calendar("c3", &["2020-01-04"])
.vj("VJ:1", |vj| {
vj.block_id("block_1")
.calendar("c1")
.st("SP1", "10:00:00", "10:01:00")
.st_mut("SP2", "11:00:00", "11:01:00", |st| {
st.pickup_type = 1;
}); })
.vj("VJ:2", |vj| {
vj.block_id("block_1")
.calendar("c2")
.st("SP3", "12:00:00", "12:01:00")
.st("SP4", "13:00:00", "13:01:00");
})
.vj("VJ:3", |vj| {
vj.block_id("block_1")
.calendar("c3")
.st("SP3", "12:30:00", "12:31:00")
.st("SP4", "13:30:00", "13:31:00");
})
.build();
let vj1 = model.vehicle_journeys.get("VJ:1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type); assert_eq!(0, stop_time.drop_off_type);
let vj2 = model.vehicle_journeys.get("VJ:2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type); let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type); assert_eq!(0, stop_time.drop_off_type);
let vj3 = model.vehicle_journeys.get("VJ:3").unwrap();
let stop_time = &vj3.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type); let stop_time = &vj3.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn block_id_on_non_overlaping_calendar_ko() {
let model = transit_model_builder::ModelBuilder::default()
.calendar("c1", &["2020-01-01", "2020-01-02"])
.calendar("c2", &["2020-01-03"])
.vj("VJ:1", |vj| {
vj.block_id("block_1")
.calendar("c1")
.st("SP1", "10:00:00", "10:01:00")
.st("SP2", "11:00:00", "11:01:00");
})
.vj("VJ:2", |vj| {
vj.block_id("block_1")
.calendar("c2")
.st("SP3", "12:00:00", "12:01:00")
.st("SP4", "13:00:00", "13:01:00");
})
.build();
let vj1 = model.vehicle_journeys.get("VJ:1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj2 = model.vehicle_journeys.get("VJ:2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn block_id_on_non_overlaping_calendar_with_overlaping_stops() {
let model = transit_model_builder::ModelBuilder::default()
.calendar("c1", &["2020-01-01", "2020-01-02"])
.calendar("c2", &["2020-01-01"])
.calendar("c3", &["2020-01-02"])
.vj("VJ:1", |vj| {
vj.block_id("block_1")
.calendar("c1")
.st("SP1", "10:00:00", "10:01:00")
.st("SP2", "11:00:00", "11:01:00");
})
.vj("VJ:2", |vj| {
vj.block_id("block_1")
.calendar("c2")
.st("SP3", "12:00:00", "12:01:00")
.st("SP4", "13:00:00", "13:01:00");
})
.vj("VJ:3", |vj| {
vj.block_id("block_1")
.calendar("c3")
.st("SP2", "12:00:00", "12:01:00")
.st("SP3", "13:00:00", "13:01:00");
})
.build();
let vj1 = model.vehicle_journeys.get("VJ:1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times.last().unwrap();
assert_eq!(0, stop_time.pickup_type); assert_eq!(0, stop_time.drop_off_type);
let vj2 = model.vehicle_journeys.get("VJ:2").unwrap();
let stop_time = &vj2.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type); let stop_time = &vj2.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let vj3 = model.vehicle_journeys.get("VJ:3").unwrap();
let stop_time = &vj3.stop_times[0];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type); let stop_time = &vj3.stop_times.last().unwrap();
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
}
#[test]
fn ignore_route_points() {
let model = transit_model_builder::ModelBuilder::default()
.vj("VJ1:1", |vj| {
vj.st_mut("SP1", "10:00:00", "10:01:00", |st| {
st.pickup_type = 3;
st.drop_off_type = 3;
})
.st("SP2", "10:30:00", "10:31:00")
.st("SP3", "11:00:00", "11:01:00")
.st_mut("SP4", "11:30:00", "11:31:00", |st| {
st.pickup_type = 3;
st.drop_off_type = 3;
});
})
.build();
let vj1 = model.vehicle_journeys.get("VJ1:1").unwrap();
let stop_time = &vj1.stop_times[0];
assert_eq!(3, stop_time.pickup_type);
assert_eq!(3, stop_time.drop_off_type);
let stop_time = &vj1.stop_times[1];
assert_eq!(0, stop_time.pickup_type);
assert_eq!(1, stop_time.drop_off_type);
let stop_time = &vj1.stop_times[2];
assert_eq!(1, stop_time.pickup_type);
assert_eq!(0, stop_time.drop_off_type);
let stop_time = &vj1.stop_times[3];
assert_eq!(3, stop_time.pickup_type);
assert_eq!(3, stop_time.drop_off_type);
}
}