1#[macro_use]
14extern crate anyhow;
15#[macro_use]
16extern crate log;
17
18use std::collections::{BTreeMap, BTreeSet, HashMap};
19use std::net::IpAddr;
20use std::sync::RwLock;
21
22use anyhow::Result;
23use hyper::{Body, Request, Response, Server, StatusCode};
24use rand::SeedableRng;
25use rand_xorshift::XorShiftRng;
26use serde::{Deserialize, Serialize};
27use structopt::StructOpt;
28
29use abstio::MapName;
30use abstutil::{serialize_btreemap, Timer};
31use geom::{Distance, Duration, FindClosest, LonLat, Time};
32use map_model::{
33 CompressedMovementID, ControlTrafficSignal, EditIntersectionControl, IntersectionID, Map,
34 MovementID, PermanentMapEdits, RoadID, TurnID,
35};
36use sim::{
37 AgentID, AgentType, DelayCause, PersonID, Sim, SimFlags, SimOptions, TripID, VehicleType,
38};
39use synthpop::{ExternalPerson, Scenario, ScenarioModifier, TripMode};
40
41lazy_static::lazy_static! {
42 static ref MAP: RwLock<Map> = RwLock::new(Map::blank());
43 static ref SIM: RwLock<Sim> = RwLock::new(Sim::new(&Map::blank(), SimOptions::new("tmp")));
44 static ref LOAD: RwLock<LoadSim> = RwLock::new({
45 LoadSim {
46 scenario: abstio::path_scenario(&MapName::seattle("montlake"), "weekday"),
47 modifiers: Vec::new(),
48 edits: None,
49 rng_seed: SimFlags::RNG_SEED,
50 opts: SimOptions::default(),
51 }
52 });
53}
54
55#[derive(StructOpt)]
56#[structopt(
57 name = "headless",
58 about = "Simulate traffic with a JSON API, not a GUI"
59)]
60struct Args {
61 #[structopt(long, default_value = "127.0.0.1")]
63 ip: IpAddr,
64 #[structopt(long)]
66 port: u16,
67 #[structopt(long)]
70 scenario: Option<String>,
71 #[structopt(long, default_value = "42")]
75 rng_seed: u64,
76 #[structopt(flatten)]
77 opts: SimOptions,
78}
79
80#[tokio::main]
81async fn main() {
82 abstutil::logger::setup();
83 let args = Args::from_args();
84
85 {
86 let mut load = LOAD.write().unwrap();
87 load.rng_seed = args.rng_seed;
88 load.opts = args.opts;
89 if let Some(path) = args.scenario {
90 load.scenario = path;
91 }
92
93 let (map, sim) = load.setup(&mut Timer::new("setup headless"));
94 *MAP.write().unwrap() = map;
95 *SIM.write().unwrap() = sim;
96 }
97
98 let addr = std::net::SocketAddr::from((args.ip, args.port));
99 info!("Listening on http://{}", addr);
100 let serve_future = Server::bind(&addr).serve(hyper::service::make_service_fn(|_| async {
101 Ok::<_, hyper::Error>(hyper::service::service_fn(serve_req))
102 }));
103 if let Err(err) = serve_future.await {
104 panic!("Server error: {}", err);
105 }
106}
107
108async fn serve_req(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
109 let path = req.uri().path().to_string();
110 let params: HashMap<String, String> =
112 url::Url::parse(&format!("http://localhost{}", req.uri()))
113 .unwrap()
114 .query_pairs()
115 .map(|(k, v)| (k.to_string(), v.to_string()))
116 .collect();
117 let body = hyper::body::to_bytes(req).await?.to_vec();
118 info!("Handling {}", path);
119 Ok(
120 match handle_command(
121 &path,
122 ¶ms,
123 &body,
124 &mut SIM.write().unwrap(),
125 &mut MAP.write().unwrap(),
126 &mut LOAD.write().unwrap(),
127 ) {
128 Ok(resp) => Response::new(Body::from(resp)),
129 Err(err) => {
130 error!("{}: {}", path, err);
131 Response::builder()
132 .status(StatusCode::BAD_REQUEST)
133 .body(Body::from(format!("Bad command {}: {}", path, err)))
134 .unwrap()
135 }
136 },
137 )
138}
139
140fn handle_command(
141 path: &str,
142 params: &HashMap<String, String>,
143 body: &[u8],
144 sim: &mut Sim,
145 map: &mut Map,
146 load: &mut LoadSim,
147) -> Result<String> {
148 let get = |key: &str| {
149 params
150 .get(key)
151 .ok_or_else(|| anyhow!("missing GET parameter {}", key))
152 };
153
154 match path {
155 "/sim/reset" => {
157 let (new_map, new_sim) = load.setup(&mut Timer::new("reset sim"));
158 *map = new_map;
159 *sim = new_sim;
160 Ok("sim reloaded".to_string())
161 }
162 "/sim/load" => {
163 let args: LoadSim = abstutil::from_json(body)?;
164
165 load.scenario = args.scenario;
166 load.modifiers = args.modifiers;
167 load.edits = args.edits;
168
169 let (new_map, new_sim) = load.setup(&mut Timer::new("reset sim"));
171 *map = new_map;
172 *sim = new_sim;
173
174 Ok("flags changed and sim reloaded".to_string())
175 }
176 "/sim/load-blank" => {
177 *map =
178 Map::load_synchronously(get("map")?.to_string(), &mut Timer::new("load new map"));
179 *sim = Sim::new(&map, SimOptions::default());
180 Ok("map changed, blank simulation".to_string())
181 }
182 "/sim/get-time" => Ok(sim.time().to_string()),
183 "/sim/goto-time" => {
184 let t = Time::parse(get("t")?)?;
185 if t <= sim.time() {
186 bail!("{} is in the past. call /sim/reset first?", t)
187 } else {
188 let dt = t - sim.time();
189 sim.timed_step(map, dt, &mut None, &mut Timer::new("goto-time"));
190 Ok(format!("it's now {}", t))
191 }
192 }
193 "/sim/new-person" => {
194 let input: ExternalPerson = abstutil::from_json(body)?;
195 for trip in &input.trips {
196 if trip.departure < sim.time() {
197 bail!(
198 "It's {} now, so you can't start a trip at {}",
199 sim.time(),
200 trip.departure
201 )
202 }
203 }
204
205 let mut scenario = Scenario::empty(map, "one-shot");
206 scenario.people = ExternalPerson::import(map, vec![input], false)?;
207 let mut rng = XorShiftRng::seed_from_u64(load.rng_seed);
208 sim.instantiate(&scenario, map, &mut rng, &mut Timer::throwaway());
209 Ok(format!(
210 "{} created",
211 sim.get_all_people().last().unwrap().id
212 ))
213 }
214 "/traffic-signals/get" => {
216 let i = IntersectionID(get("id")?.parse::<usize>()?);
217 if let Some(ts) = map.maybe_get_traffic_signal(i) {
218 Ok(abstutil::to_json(ts))
219 } else {
220 bail!("{} isn't a traffic signal", i)
221 }
222 }
223 "/traffic-signals/set" => {
224 let ts: ControlTrafficSignal = abstutil::from_json(body)?;
225 let id = ts.id;
226
227 let mut edits = map.get_edits().clone();
230 edits.commands.push(map.edit_intersection_cmd(id, |new| {
231 new.control = EditIntersectionControl::TrafficSignal(ts.export(map));
232 }));
233 map.must_apply_edits(edits, &mut Timer::throwaway());
234 map.recalculate_pathfinding_after_edits(&mut Timer::throwaway());
235
236 Ok(format!("{} has been updated", id))
237 }
238 "/traffic-signals/get-delays" => {
239 let i = map.get_i(IntersectionID(get("id")?.parse::<usize>()?));
240 let t1 = Time::parse(get("t1")?)?;
241 let t2 = Time::parse(get("t2")?)?;
242 if !i.is_traffic_signal() {
243 bail!("{} isn't a traffic signal", i.id);
244 }
245 let movements: Vec<&MovementID> = i.movements.keys().collect();
246
247 let mut delays = Delays {
248 per_direction: BTreeMap::new(),
249 };
250 for m in i.movements.keys() {
251 delays.per_direction.insert(*m, Vec::new());
252 }
253 if let Some(list) = sim.get_analytics().intersection_delays.get(&i.id) {
254 for (idx, t, dt, _) in list {
255 if *t >= t1 && *t <= t2 {
256 delays
257 .per_direction
258 .get_mut(movements[*idx as usize])
259 .unwrap()
260 .push(*dt);
261 }
262 }
263 }
264 Ok(abstutil::to_json(&delays))
265 }
266 "/traffic-signals/get-cumulative-thruput" => {
267 let i = map.get_i(IntersectionID(get("id")?.parse::<usize>()?));
268 if !i.is_traffic_signal() {
269 bail!("{} isn't a traffic signal", i.id);
270 }
271
272 let mut thruput = Throughput {
273 per_direction: BTreeMap::new(),
274 };
275 for (idx, m) in i.movements.keys().enumerate() {
276 thruput.per_direction.insert(
277 *m,
278 sim.get_analytics()
279 .traffic_signal_thruput
280 .total_for(CompressedMovementID {
281 i: i.id,
282 idx: u8::try_from(idx).unwrap(),
283 }),
284 );
285 }
286 Ok(abstutil::to_json(&thruput))
287 }
288 "/traffic-signals/get-all-current-state" => {
289 let mut all_state = BTreeMap::new();
290 for i in map.all_intersections() {
291 if !i.is_traffic_signal() {
292 continue;
293 }
294 let (current_stage_idx, remaining_time) =
295 sim.current_stage_and_remaining_time(i.id);
296 all_state.insert(
297 i.id,
298 TrafficSignalState {
299 current_stage_idx,
300 remaining_time,
301 accepted: sim
302 .get_accepted_agents(i.id)
303 .into_iter()
304 .map(|(a, _)| a)
305 .collect(),
306 waiting: sim.get_waiting_agents(i.id),
307 },
308 );
309 }
310 Ok(abstutil::to_json(&all_state))
311 }
312 "/data/get-finished-trips" => {
314 let mut trips = Vec::new();
315 for (_, id, mode, maybe_duration) in &sim.get_analytics().finished_trips {
316 let distance_crossed = if maybe_duration.is_some() {
317 sim.finished_trip_details(*id).unwrap().2
318 } else {
319 Distance::ZERO
320 };
321 trips.push(FinishedTrip {
322 id: *id,
323 person: sim.trip_to_person(*id).unwrap(),
324 duration: *maybe_duration,
325 distance_crossed,
326 mode: *mode,
327 });
328 }
329 Ok(abstutil::to_json(&trips))
330 }
331 "/data/get-agent-positions" => Ok(abstutil::to_json(&AgentPositions {
332 agents: sim
333 .get_unzoomed_agents(map)
334 .into_iter()
335 .chain(sim.get_unzoomed_transit_riders(map))
336 .map(|a| AgentPosition {
337 id: a.id,
338 trip: sim.agent_to_trip(a.id),
339 person: a.person,
340 vehicle_type: a.id.to_vehicle_type(),
341 pos: a.pos.to_gps(map.get_gps_bounds()),
342 distance_crossed: sim.agent_properties(map, a.id).dist_crossed,
343 })
344 .collect(),
345 })),
346 "/data/get-road-thruput" => Ok(abstutil::to_json(&RoadThroughput {
347 counts: sim
348 .get_analytics()
349 .road_thruput
350 .counts
351 .iter()
352 .map(|((r, a, hr), cnt)| (*r, *a, *hr, *cnt))
353 .collect(),
354 })),
355 "/data/get-blocked-by-graph" => Ok(abstutil::to_json(&BlockedByGraph {
356 blocked_by: sim
357 .get_blocked_by_graph(map)
358 .into_iter()
359 .map(|(id, (delay, cause))| {
360 (
361 id,
362 (delay, cause, sim.agent_to_trip(id), sim.agent_to_person(id)),
363 )
364 })
365 .collect(),
366 })),
367 "/data/trip-time-lower-bound" => {
368 let id = TripID(get("id")?.parse::<usize>()?);
369 let duration = sim.get_trip_time_lower_bound(map, id)?;
370 Ok(duration.inner_seconds().to_string())
371 }
372 "/data/all-trip-time-lower-bounds" => {
373 let results: BTreeMap<TripID, Duration> = Timer::throwaway()
374 .parallelize(
375 "calculate all trip time lower bounds",
376 sim.all_trip_info(),
377 |(id, _)| {
378 sim.get_trip_time_lower_bound(map, id)
379 .ok()
380 .map(|dt| (id, dt))
381 },
382 )
383 .into_iter()
384 .flatten()
385 .collect();
386 Ok(abstutil::to_json(&results))
387 }
388 "/map/get-edits" => {
390 let mut edits = map.get_edits().clone();
391 edits.commands.clear();
392 edits.compress(map);
393 Ok(abstutil::to_json(&edits.to_permanent(map)))
394 }
395 "/map/get-edit-road-command" => {
396 let r = RoadID(get("id")?.parse::<usize>()?);
397 Ok(abstutil::to_json(
398 &map.edit_road_cmd(r, |_| {}).to_perma(map),
399 ))
400 }
401 "/map/get-intersection-geometry" => {
402 let i = IntersectionID(get("id")?.parse::<usize>()?);
403 Ok(abstutil::to_json(&export_geometry(map, i)))
404 }
405 "/map/get-all-geometry" => Ok(abstutil::to_json(&map.export_geometry())),
406 "/map/get-nearest-road" => {
407 let pt = LonLat::new(get("lon")?.parse::<f64>()?, get("lat")?.parse::<f64>()?);
408 let mut closest = FindClosest::new();
409 for r in map.all_roads() {
410 closest.add(r.id, r.center_pts.points());
411 }
412 let threshold = Distance::meters(get("threshold_meters")?.parse::<f64>()?);
413 match closest.closest_pt(pt.to_pt(map.get_gps_bounds()), threshold) {
414 Some((r, _)) => Ok(r.0.to_string()),
415 None => bail!("No road within {} of {}", threshold, pt),
416 }
417 }
418 _ => Err(anyhow!("Unknown command")),
419 }
420}
421
422#[derive(Serialize)]
425struct FinishedTrip {
426 id: TripID,
427 person: PersonID,
428 duration: Option<Duration>,
429 distance_crossed: Distance,
430 mode: TripMode,
431}
432
433#[derive(Serialize)]
434struct Delays {
435 #[serde(serialize_with = "serialize_btreemap")]
436 per_direction: BTreeMap<MovementID, Vec<Duration>>,
437}
438
439#[derive(Serialize)]
440struct Throughput {
441 #[serde(serialize_with = "serialize_btreemap")]
442 per_direction: BTreeMap<MovementID, usize>,
443}
444
445#[derive(Serialize)]
446struct AgentPositions {
447 agents: Vec<AgentPosition>,
448}
449
450#[derive(Serialize)]
451struct AgentPosition {
452 id: AgentID,
454 trip: Option<TripID>,
456 person: Option<PersonID>,
458 vehicle_type: Option<VehicleType>,
460 pos: LonLat,
463 distance_crossed: Distance,
474}
475
476#[derive(Serialize)]
477struct RoadThroughput {
478 counts: Vec<(RoadID, AgentType, usize, usize)>,
480}
481
482#[derive(Serialize)]
483struct TrafficSignalState {
484 current_stage_idx: usize,
485 remaining_time: Duration,
486 accepted: BTreeSet<AgentID>,
487 waiting: Vec<(AgentID, TurnID, Time)>,
489}
490
491#[derive(Serialize)]
492struct BlockedByGraph {
493 #[serde(serialize_with = "serialize_btreemap")]
497 blocked_by: BTreeMap<AgentID, (Duration, DelayCause, Option<TripID>, Option<PersonID>)>,
498}
499
500#[derive(Deserialize)]
501struct LoadSim {
502 scenario: String,
503 modifiers: Vec<ScenarioModifier>,
504 edits: Option<PermanentMapEdits>,
505 #[serde(skip_deserializing)]
507 rng_seed: u64,
508 #[serde(skip_deserializing)]
509 opts: SimOptions,
510}
511
512impl LoadSim {
513 fn setup(&self, timer: &mut Timer) -> (Map, Sim) {
514 let mut scenario: Scenario = abstio::must_read_object(self.scenario.clone(), timer);
515
516 let mut map = Map::load_synchronously(scenario.map_name.path(), timer);
517 if let Some(perma) = self.edits.clone() {
518 let edits = perma.into_edits(&map).unwrap();
519 map.must_apply_edits(edits, timer);
520 map.recalculate_pathfinding_after_edits(timer);
521 }
522
523 let mut rng = XorShiftRng::seed_from_u64(self.rng_seed);
524 for m in &self.modifiers {
525 scenario = m.apply(&map, scenario, &mut rng);
526 }
527
528 let mut sim = Sim::new(&map, self.opts.clone());
529 sim.instantiate(&scenario, &map, &mut rng, timer);
530
531 (map, sim)
532 }
533}
534
535fn export_geometry(map: &Map, i: IntersectionID) -> geojson::GeoJson {
536 let mut pairs = Vec::new();
537
538 let i = map.get_i(i);
539 let center = i.polygon.center();
541
542 let mut props = serde_json::Map::new();
544 props.insert("type".to_string(), "intersection".into());
545 props.insert("id".to_string(), i.orig_id.to_string().into());
546 pairs.push((
547 i.polygon
548 .translate(-center.x(), -center.y())
549 .get_outer_ring()
550 .to_geojson(None),
551 props,
552 ));
553
554 for r in &i.roads {
556 let r = map.get_r(*r);
557 let mut props = serde_json::Map::new();
558 props.insert("type".to_string(), "road".into());
559 props.insert("id".to_string(), r.orig_id.osm_way_id.to_string().into());
560 pairs.push((
561 r.center_pts
562 .to_thick_ring(r.get_width())
563 .translate(-center.x(), -center.y())
564 .to_geojson(None),
565 props,
566 ));
567 }
568
569 geom::geometries_with_properties_to_geojson(pairs)
570}