rustsim-mobility 0.0.1

Multi-modal mobility glue for rustsim: leg-based trips, mode transitions, shared obstacle interfaces between crowds, vehicles, and transit
Documentation
use rustsim_crowd::Pedestrian;
use rustsim_mobility::prelude::*;
use rustsim_modes::{AllowedModes, TravelMode};
use rustsim_traffic::{
    LinkClass, LinkProperties, SignalTiming, TrafficControlType, TransportLinkMetadata,
    TransportLinkOps, TransportLinkSpace,
};
use rustsim_transit::{Boarding, Route, Schedule, Stop, TransitVehicle, Waiter};
use rustsim_vehicle::{idm, Vehicle, VehicleModel};

const TRAVELLER: u64 = 42;

fn build_network() -> (
    TransportLinkSpace,
    Vec<TransportLinkMetadata>,
    [usize; 4],
    usize,
) {
    let mut space = TransportLinkSpace::new();
    let home = space.add_node();
    let stop_a = space.add_node();
    let stop_b = space.add_node();
    let work = space.add_node();

    let (walk_geom, walk_props) = LinkProperties::pedestrian(80.0, 3.0).unwrap();
    let walk_in = space.add_link(home, stop_a, walk_geom, walk_props).unwrap();
    let (bus_geom, bus_props) = LinkProperties::urban(600.0, 40.0, 1).unwrap();
    let bus_link = space.add_link(stop_a, stop_b, bus_geom, bus_props).unwrap();
    let (walk_out_geom, walk_out_props) = LinkProperties::pedestrian(80.0, 3.0).unwrap();
    let walk_out = space
        .add_link(stop_b, work, walk_out_geom, walk_out_props)
        .unwrap();
    let (direct_geom, direct_props) = LinkProperties::pedestrian(1_200.0, 3.0).unwrap();
    let direct_walk = space
        .add_link(home, work, direct_geom, direct_props)
        .unwrap();

    let metadata = vec![
        TransportLinkMetadata::new(
            walk_in,
            home,
            stop_a,
            LinkClass::Walkway,
            AllowedModes::pedestrian_only(),
        ),
        TransportLinkMetadata::new(
            bus_link,
            stop_a,
            stop_b,
            LinkClass::Transitway,
            AllowedModes::none().with_mode(TravelMode::Transit),
        ),
        TransportLinkMetadata::new(
            walk_out,
            stop_b,
            work,
            LinkClass::Walkway,
            AllowedModes::pedestrian_only(),
        ),
        TransportLinkMetadata::new(
            direct_walk,
            home,
            work,
            LinkClass::Walkway,
            AllowedModes::pedestrian_only(),
        ),
    ];

    (space, metadata, [home, stop_a, stop_b, work], bus_link)
}

#[test]
fn multimodal_trip_exercises_routing_transit_vehicle_traffic_and_obstacles() {
    let (mut space, metadata, [home, stop_a, stop_b, work], bus_link) = build_network();
    let graph = ModalGraph::from_transport_links(&space, metadata).unwrap();

    let walk_only = shortest_path(
        &graph,
        home as u64,
        work as u64,
        AllowedModes::pedestrian_only(),
    )
    .expect("direct walk should be reachable");
    assert_eq!(walk_only.nodes, vec![home as u64, work as u64]);

    let multimodal = AllowedModes::pedestrian_only().with_mode(TravelMode::Transit);
    let transit_route = shortest_path(&graph, home as u64, work as u64, multimodal)
        .expect("walk + transit + walk should be reachable");
    assert_eq!(
        transit_route.nodes,
        vec![home as u64, stop_a as u64, stop_b as u64, work as u64]
    );
    assert!(transit_route.total_cost < walk_only.total_cost);

    let stop_a_def = Stop::at_ground(10, "A", 80.0, 0.0, 120);
    let stop_b_def = Stop::at_ground(20, "B", 680.0, 0.0, 120);
    let route = Route::new(
        5,
        "Blue",
        vec![stop_a_def.id, stop_b_def.id],
        vec![space.link_free_flow_time(bus_link)],
        Schedule::fixed_headway(300.0),
    );
    assert_eq!(route.index_of(stop_a_def.id), Some(0));
    assert_eq!(route.index_of(stop_b_def.id), Some(1));

    let plan = TripPlan {
        id: 1,
        legs: vec![
            Leg::Walk {
                from: Waypoint::ground(0.0, 0.0),
                to: Waypoint::ground(stop_a_def.pos[0], stop_a_def.pos[1]),
            },
            Leg::Transit {
                route: route.id,
                board_at: stop_a_def.id,
                alight_at: stop_b_def.id,
            },
            Leg::Walk {
                from: Waypoint::ground(stop_b_def.pos[0], stop_b_def.pos[1]),
                to: Waypoint::ground(760.0, 0.0),
            },
        ],
    };

    let controller = ModeController;
    let mut traveller = TravellerContext::new(TRAVELLER);
    controller.enter_current_leg(&mut traveller, &plan);
    assert_eq!(traveller.state, ModeState::Walking);
    controller.complete_leg(&mut traveller, &plan);
    assert_eq!(
        traveller.state,
        ModeState::WaitingAt(stop_a_def.id, route.id)
    );

    let mut queue = Boarding::new();
    let waiter = Waiter {
        passenger: TRAVELLER,
        destination: stop_b_def.id,
        arrived_at: 0.0,
    };
    assert!(CapacityStopQueuePolicy.can_enqueue(&stop_a_def, &queue, &waiter));
    queue.enqueue(waiter);

    assert_eq!(
        ScheduledDispatchPolicy.decide(DispatchContext::new(&route, 0.0)),
        DispatchDecision::Dispatch { departure_s: 0.0 }
    );

    let mut service = TransitVehicle::idle(99, route.id, 30);
    let mut passenger_destinations = Vec::new();
    let boarding = queue.board_vehicle_with_policy(
        &mut service,
        &route.stops,
        &mut passenger_destinations,
        &FifoBoardingPolicy::capped(4),
    );
    assert_eq!(boarding.boarded, 1);
    assert!(queue.is_empty());
    let dwell = LinearDwellPolicy::default().dwell_time(0, boarding.boarded);
    assert!(dwell.total > 0.0);
    controller.board_transit(&mut traveller, service.id, stop_b_def.id);
    assert_eq!(
        traveller.state,
        ModeState::RidingTransit {
            vehicle: service.id,
            alight_at: stop_b_def.id,
        }
    );

    space.add_agent_to_link(service.id, bus_link, 0.0).unwrap();
    let speed = space
        .agent_speed_with_policy(service.id, &FifoGapPolicy::default())
        .unwrap();
    assert!(speed > 0.0);
    let signal = SignalTiming::new(60.0, 0.0, vec![30.0]);
    let control = FixedControlPolicy::default()
        .decide(ControlContext::new(45.0, TrafficControlType::Signal).with_signal_timing(&signal));
    assert_eq!(control, ControlDecision::Hold { wait_s: 15.0 });
    let advanced = space.advance_agent(service.id, speed * 10.0).unwrap();
    assert!(advanced > 0.0);

    let mut bus = Vehicle {
        pos: [stop_a_def.pos[0], stop_a_def.pos[1]],
        speed,
        ..Vehicle::default_bus()
    };
    rustsim_vehicle::IntelligentDriverModel.step(&mut [bus], &idm::Params::default(), 1.0);
    let mut bus_after = [bus];
    rustsim_vehicle::IntelligentDriverModel.step(&mut bus_after, &idm::Params::default(), 1.0);
    bus = bus_after[0];
    assert!(bus.pos[0] > stop_a_def.pos[0]);

    let ped = Pedestrian::new(
        [stop_a_def.pos[0] - 2.0, 0.0],
        [1.0, 0.0],
        0.25,
        1.3,
        [80.0, 0.0],
    );
    let ped_obstacle = PedestrianObstacle::ground(TRAVELLER, &ped).snapshot();
    let bus_obstacle = VehicleObstacle::ground(service.id, &bus).snapshot();
    assert_eq!(ped_obstacle.kind, ObstacleKind::Pedestrian);
    assert_eq!(bus_obstacle.kind, ObstacleKind::Transit);
    assert!(bus_obstacle.radius > ped_obstacle.radius);

    let alighted = service.alight_at(stop_b_def.id, &passenger_destinations);
    assert_eq!(alighted, vec![TRAVELLER]);
    controller.alight_transit(&mut traveller, &plan);
    assert_eq!(traveller.state, ModeState::Walking);
    controller.complete_leg(&mut traveller, &plan);
    assert_eq!(traveller.state, ModeState::Finished);
}