use std::fs::File;
use std::io::Write;
use anyhow::Result;
use rand::seq::SliceRandom;
use abstio::{CityName, MapName};
use abstutil::Timer;
use geom::{Distance, Duration, Time};
use map_model::{IntersectionID, Map};
use sim::{IndividTrip, PersonSpec, Scenario, TripEndpoint, TripMode, TripPurpose};
fn main() -> Result<()> {
test_lane_changing(&import_map(abstio::path(
"../tests/input/lane_selection.osm",
)))?;
test_map_importer()?;
check_proposals()?;
smoke_test()?;
Ok(())
}
fn test_map_importer() -> Result<()> {
for &name in &[
"divided_highway_split",
"left_turn_and_bike_lane",
"multiple_left_turn_lanes",
] {
let map = import_map(abstio::path(format!("../tests/input/{}.osm", name)));
if false {
map.save();
}
println!("Producing goldenfiles for {}", map.get_name().describe());
dump_turn_goldenfile(&map)?;
}
Ok(())
}
fn import_map(path: String) -> Map {
let mut timer = Timer::new("convert synthetic map");
let raw = convert_osm::convert(
convert_osm::Options {
name: MapName::new("zz", "oneshot", &abstutil::basename(&path)),
osm_input: path,
clip: None,
map_config: map_model::MapConfig {
driving_side: map_model::DrivingSide::Right,
bikes_can_use_bus_lanes: true,
inferred_sidewalks: true,
street_parking_spot_length: Distance::meters(8.0),
},
onstreet_parking: convert_osm::OnstreetParking::JustOSM,
public_offstreet_parking: convert_osm::PublicOffstreetParking::None,
private_offstreet_parking: convert_osm::PrivateOffstreetParking::FixedPerBldg(0),
include_railroads: true,
extra_buildings: None,
},
&mut timer,
);
Map::create_from_raw(raw, map_model::RawToMapOptions::default(), &mut timer)
}
fn dump_turn_goldenfile(map: &Map) -> Result<()> {
let path = abstio::path(format!("../tests/goldenfiles/{}.txt", map.get_name().map));
let mut f = File::create(path)?;
for t in map.all_turns() {
writeln!(f, "{} is a {:?}", t.id, t.turn_type)?;
}
Ok(())
}
fn smoke_test() -> Result<()> {
let mut timer = Timer::new("run a smoke-test for all maps");
for name in MapName::list_all_maps_locally() {
let map = map_model::Map::load_synchronously(name.path(), &mut timer);
let scenario = if map.get_city_name() == &CityName::seattle() {
abstio::read_binary(abstio::path_scenario(&name, "weekday"), &mut timer)
} else {
let mut rng = sim::SimFlags::for_test("smoke_test").make_rng();
sim::ScenarioGenerator::proletariat_robot(&map, &mut rng, &mut timer)
};
let mut opts = sim::SimOptions::new("smoke_test");
opts.alerts = sim::AlertHandler::Silence;
let mut sim = sim::Sim::new(&map, opts);
let mut rng = sim::SimFlags::for_test("smoke_test").make_rng();
scenario.instantiate(&mut sim, &map, &mut rng, &mut timer);
sim.timed_step(&map, Duration::hours(1), &mut None, &mut timer);
#[allow(clippy::collapsible_if)]
if (name.city == CityName::seattle()
&& vec!["downtown", "lakeslice", "montlake", "udistrict"].contains(&name.map.as_str()))
|| name == MapName::new("pl", "krakow", "center")
{
if false {
dump_route_goldenfile(&map)?;
}
}
}
Ok(())
}
fn dump_route_goldenfile(map: &map_model::Map) -> Result<()> {
let path = abstio::path(format!(
"route_goldenfiles/{}.txt",
map.get_name().as_filename()
));
let mut f = File::create(path)?;
for br in map.all_bus_routes() {
writeln!(
f,
"{} from {} to {:?}",
br.osm_rel_id, br.start, br.end_border
)?;
for bs in &br.stops {
let bs = map.get_bs(*bs);
writeln!(
f,
" {}: {} driving, {} sidewalk",
bs.name, bs.driving_pos, bs.sidewalk_pos
)?;
}
}
Ok(())
}
fn check_proposals() -> Result<()> {
let mut timer = Timer::new("check all proposals");
for name in abstio::list_all_objects(abstio::path("system/proposals")) {
match abstio::maybe_read_json::<map_model::PermanentMapEdits>(
abstio::path(format!("system/proposals/{}.json", name)),
&mut timer,
) {
Ok(perma) => {
let map = map_model::Map::load_synchronously(perma.map_name.path(), &mut timer);
if let Err(err) = perma.clone().into_edits(&map) {
abstio::write_json(
"repair_attempt.json".to_string(),
&perma.into_edits_permissive(&map).to_permanent(&map),
);
anyhow::bail!("{} is out-of-date: {}", name, err);
}
}
Err(err) => {
anyhow::bail!("{} JSON is broken: {}", name, err);
}
}
}
Ok(())
}
fn test_lane_changing(map: &Map) -> Result<()> {
let mut rng = sim::SimFlags::for_test("smoke_test").make_rng();
let north = IntersectionID(8);
let south = IntersectionID(0);
let east = IntersectionID(2);
let west = IntersectionID(4);
let mut od = Vec::new();
for _ in 0..100 {
od.push((north, south));
od.push((east, south));
}
for _ in 0..100 {
od.push((north, west));
od.push((east, west));
}
od.shuffle(&mut rng);
let mut scenario = Scenario::empty(map, "lane_changing");
for (idx, (from, to)) in od.into_iter().enumerate() {
scenario.people.push(PersonSpec {
orig_id: None,
trips: vec![IndividTrip::new(
Time::START_OF_DAY + Duration::seconds(idx as f64 - 0.5).max(Duration::ZERO),
TripPurpose::Shopping,
TripEndpoint::Border(from),
TripEndpoint::Border(to),
if idx % 2 == 0 {
TripMode::Drive
} else {
TripMode::Bike
},
)],
});
}
if false {
map.save();
scenario.save();
}
let mut opts = sim::SimOptions::new("test_lane_changing");
opts.alerts = sim::AlertHandler::Silence;
let mut sim = sim::Sim::new(&map, opts);
let mut rng = sim::SimFlags::for_test("test_lane_changing").make_rng();
scenario.instantiate(&mut sim, &map, &mut rng, &mut Timer::throwaway());
while !sim.is_done() {
sim.tiny_step(&map, &mut None);
}
let limit = Duration::minutes(8) + Duration::seconds(40.0);
if sim.time() > Time::START_OF_DAY + limit {
panic!(
"Lane-changing scenario took {} to complete; it should be under {}",
sim.time(),
limit
);
}
Ok(())
}