mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-23 22:42:32 +03:00
Record throughput through a traffic signal by direction, expose through
the API (#245), and beef up the Python example. Impact to prebaked file size is tiny -- for lakeslice, the original intersection_thruput is 2MB and the new traffic_signal_thruput is 435KB. [rebuild]
This commit is contained in:
parent
5b2a548c93
commit
11832aa606
@ -34,6 +34,9 @@ are missing, etc. A summary of the commands available so far:
|
||||
- **GET /traffic-signals/get-delays?id=42&t1=03:00:00&t2=03:30:00**: Returns
|
||||
the delay experienced by every agent passing through intersection #42 from
|
||||
3am to 3:30, grouped by direction of travel.
|
||||
- **GET /traffic-signals/get-cumulative-thruput?id=42**: Returns the number of
|
||||
agents passing through intersection #42 since midnight, grouped by direction
|
||||
of travel.
|
||||
- **/data**
|
||||
- **GET /data/get-finished-trips**: Returns a JSON list of all finished trips.
|
||||
Each tuple is (time the trip finished in seconds after midnight, trip ID,
|
||||
|
@ -73,9 +73,9 @@ data/system/maps/south_seattle.bin,87815aaddcc294c0cd30f0b7db97d0d4,https://www.
|
||||
data/system/maps/udistrict.bin,ae9163526dcd1b4f15d4cee2b3a2f91f,https://www.dropbox.com/s/hb6yxpvkums7mtt/udistrict.bin.zip?dl=0
|
||||
data/system/maps/west_seattle.bin,d91214290adac1f8d8db63f6d680f2d1,https://www.dropbox.com/s/qk0kxreqjpoghwh/west_seattle.bin.zip?dl=0
|
||||
data/system/maps/xian.bin,dcb681858c2f4723a4c09b0ebafcc38f,https://www.dropbox.com/s/qxtwswze0o93w3p/xian.bin.zip?dl=0
|
||||
data/system/prebaked_results/lakeslice/weekday.bin,364abba838c51f1ecc94738878c6ec9c,https://www.dropbox.com/s/jwy9gnak0g81qoz/weekday.bin.zip?dl=0
|
||||
data/system/prebaked_results/montlake/car vs bike contention.bin,faa041af70a41bb8fcacd3dfd7a7d467,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0
|
||||
data/system/prebaked_results/montlake/weekday.bin,3d2e502d13b995ebd3de4682ae41207a,https://www.dropbox.com/s/wxi268ft3iuhco4/weekday.bin.zip?dl=0
|
||||
data/system/prebaked_results/lakeslice/weekday.bin,6f5a5b9076fb4f47c3e2d9870c020167,https://www.dropbox.com/s/tzzxxu4575wnapl/weekday.bin.zip?dl=0
|
||||
data/system/prebaked_results/montlake/car vs bike contention.bin,8002ad9207bda6a2fc20594f413a8c2e,https://www.dropbox.com/s/jefg0ikjy9dsrdd/car%20vs%20bike%20contention.bin.zip?dl=0
|
||||
data/system/prebaked_results/montlake/weekday.bin,c483c6734087c76630cdf652882dc440,https://www.dropbox.com/s/rvhek1pbwnf7t2e/weekday.bin.zip?dl=0
|
||||
data/system/scenarios/ballard/weekday.bin,8fba0bf4e623552d75c27e9c210ce469,https://www.dropbox.com/s/62pwtz3pn098v84/weekday.bin.zip?dl=0
|
||||
data/system/scenarios/downtown/weekday.bin,d789b5b7fe2a536f65ae330abb575823,https://www.dropbox.com/s/2ia9ssy9gza86sw/weekday.bin.zip?dl=0
|
||||
data/system/scenarios/huge_seattle/weekday.bin,9892f03c2998b3661384f086dbc6c4bb,https://www.dropbox.com/s/hk3sr90owfr3hzc/weekday.bin.zip?dl=0
|
||||
|
@ -105,6 +105,10 @@ impl App {
|
||||
"- intersection_thruput: {} bytes",
|
||||
prettyprint_usize(serialized_size_bytes(&a.intersection_thruput))
|
||||
);
|
||||
println!(
|
||||
"- traffic_signal_thruput: {} bytes",
|
||||
prettyprint_usize(serialized_size_bytes(&a.traffic_signal_thruput))
|
||||
);
|
||||
println!(
|
||||
"- demand : {} bytes",
|
||||
prettyprint_usize(serialized_size_bytes(&a.demand))
|
||||
|
@ -9,10 +9,11 @@ import json
|
||||
import requests
|
||||
|
||||
|
||||
def main():
|
||||
api = 'http://localhost:1234'
|
||||
hours_to_sim = '12:00:00'
|
||||
api = 'http://localhost:1234'
|
||||
hours_to_sim = '12:00:00'
|
||||
|
||||
|
||||
def main():
|
||||
# Make sure to start the simulation from the beginning
|
||||
print('Did you just start the simulation? Time is currently', requests.get(api + '/sim/get-time').text)
|
||||
print('Reset the simulation:', requests.get(api + '/sim/reset').text)
|
||||
@ -20,13 +21,12 @@ def main():
|
||||
|
||||
# Run a few hours to get a baseline
|
||||
print('Simulating before any edits')
|
||||
print(requests.get(api + '/sim/goto-time', params={'t': hours_to_sim}).text)
|
||||
baseline_trips = process_trips(requests.get(api + '/data/get-finished-trips').json()['trips'])
|
||||
baseline_delays = requests.get(api + '/traffic-signals/get-delays', params={'id': 67, 't1': '00:00:00', 't2': hours_to_sim}).json()
|
||||
print('Baseline: {} finished trips, total of {} seconds'.format(len(baseline_trips), sum(baseline_trips.values())))
|
||||
trips1, delays1, thruput1 = run_experiment()
|
||||
print('Baseline: {} finished trips, total of {} seconds'.format(len(trips1), sum(trips1.values())))
|
||||
print()
|
||||
|
||||
# Modify one traffic signal, doubling the duration of its second phase
|
||||
print('Modify a traffic signal')
|
||||
ts = requests.get(api + '/traffic-signals/get', params={'id': 67}).json()
|
||||
ts['phases'][1]['phase_type']['Fixed'] *= 2
|
||||
# Reset the simulation before applying the edit, since reset also clears edits.
|
||||
@ -36,42 +36,61 @@ def main():
|
||||
|
||||
# Repeat the experiment
|
||||
print('Simulating after the edits')
|
||||
print(requests.get(api + '/sim/goto-time', params={'t': hours_to_sim}).text)
|
||||
experimental_trips = process_trips(requests.get(api + '/data/get-finished-trips').json()['trips'])
|
||||
experimental_delays = requests.get(api + '/traffic-signals/get-delays', params={'id': 67, 't1': '00:00:00', 't2': hours_to_sim}).json()
|
||||
print('Experiment: {} finished trips, total of {} seconds'.format(len(experimental_trips), sum(experimental_trips.values())))
|
||||
trips2, delays2, thruput2 = run_experiment()
|
||||
print('Experiment: {} finished trips, total of {} seconds'.format(len(trips2), sum(trips2.values())))
|
||||
print()
|
||||
|
||||
# Compare -- did this help or not?
|
||||
print('{} more trips finished after the edits (higher is better)'.format(len(experimental_trips) - len(baseline_trips)))
|
||||
print('Experiment was {} seconds faster, over all trips'.format(sum(baseline_trips.values()) - sum(experimental_trips.values())))
|
||||
print('{} more trips finished after the edits (higher is better)'.format(len(trips2) - len(trips1)))
|
||||
print('Experiment was {} seconds faster, over all trips'.format(sum(trips1.values()) - sum(trips2.values())))
|
||||
print()
|
||||
|
||||
# How much delay difference was there for each direction of travel?
|
||||
print('Average delay per direction of travel at this intersection BEFORE edits:')
|
||||
for direction, delays1 in baseline_delays['per_direction']:
|
||||
# Skip empty cases and crosswalks
|
||||
if delays1 and not direction['crosswalk']:
|
||||
print('- {} -> {}: {:.1f} seconds'.format(print_road(direction['from']), print_road(direction['to']), sum(delays1) / len(delays1)))
|
||||
# TODO Hard to group these together to compare, because Python can't handle dicts as keys. Stringify it first.
|
||||
print('Average delay per direction of travel at this intersection AFTER edits:')
|
||||
for direction, delays2 in experimental_delays['per_direction']:
|
||||
# Skip empty cases and crosswalks
|
||||
if delays2 and not direction['crosswalk']:
|
||||
print('- {} -> {}: {:.1f} seconds'.format(print_road(direction['from']), print_road(direction['to']), sum(delays2) / len(delays2)))
|
||||
# Now we'll print some before/after stats per direction of travel through
|
||||
# the intersection
|
||||
col = '{:<40} {:>20} {:>20} {:>17} {:>17}'
|
||||
print(col.format('Direction', 'avg delay before', 'avg delay after', 'thruput before', 'thruput after'))
|
||||
for k in delays1.keys():
|
||||
print(col.format(k, delays1[k], delays2[k], thruput1[k], thruput2[k]))
|
||||
|
||||
|
||||
# Return a map from trip ID to the duration (in seconds) of the trip. Filter
|
||||
# out aborted (failed) trips.
|
||||
def process_trips(trips):
|
||||
results = {}
|
||||
for (_, trip, mode, duration) in trips:
|
||||
# Returns (trips, delay, throughput)
|
||||
def run_experiment():
|
||||
print(requests.get(api + '/sim/goto-time', params={'t': hours_to_sim}).text)
|
||||
raw_trips = requests.get(api + '/data/get-finished-trips').json()['trips']
|
||||
raw_delays = requests.get(api + '/traffic-signals/get-delays', params={'id': 67, 't1': '00:00:00', 't2': hours_to_sim}).json()
|
||||
raw_thruput = requests.get(api + '/traffic-signals/get-cumulative-thruput', params={'id': 67}).json()
|
||||
|
||||
# Map trip ID to the duration (in seconds) of the trip. Filter out aborted
|
||||
# (failed) trips.
|
||||
trips = {}
|
||||
for (_, trip, mode, duration) in raw_trips:
|
||||
if mode is not None:
|
||||
results[trip] = duration
|
||||
return results
|
||||
trips[trip] = duration
|
||||
|
||||
# The direction is a dict, but Python can't handle dicts as keys. Stringify
|
||||
# the keys, also filtering out crosswalks and empty directions.
|
||||
delays = {}
|
||||
for k, v in raw_delays['per_direction']:
|
||||
k = stringify_direction(k)
|
||||
if k and v:
|
||||
delays[k] = '{:.1f}'.format(sum(v) / len(v))
|
||||
|
||||
thruput = {}
|
||||
for k, v in raw_thruput['per_direction']:
|
||||
k = stringify_direction(k)
|
||||
if k:
|
||||
thruput[k] = v
|
||||
|
||||
return (trips, delays, thruput)
|
||||
|
||||
|
||||
def print_road(directed_road):
|
||||
def stringify_direction(direxn):
|
||||
if direxn['crosswalk']:
|
||||
return None
|
||||
return '{} -> {}'.format(stringify_road(direxn['from']), stringify_road(direxn['to']))
|
||||
|
||||
|
||||
def stringify_road(directed_road):
|
||||
if directed_road['forwards']:
|
||||
direxn = 'fwd'
|
||||
else:
|
||||
|
@ -12,10 +12,11 @@
|
||||
use abstutil::{serialize_btreemap, CmdArgs, Timer};
|
||||
use geom::{Duration, Time};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use map_model::{ControlTrafficSignal, IntersectionID, Map, TurnGroupID};
|
||||
use map_model::{CompressedTurnGroupID, ControlTrafficSignal, IntersectionID, Map, TurnGroupID};
|
||||
use serde::Serialize;
|
||||
use sim::{AlertHandler, Sim, SimFlags, SimOptions, TripID, TripMode};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::sync::RwLock;
|
||||
|
||||
@ -147,6 +148,30 @@ fn handle_command(
|
||||
}
|
||||
Ok(abstutil::to_json(&delays))
|
||||
}
|
||||
"/traffic-signals/get-cumulative-thruput" => {
|
||||
let i = IntersectionID(params["id"].parse::<usize>()?);
|
||||
let ts = if let Some(ts) = map.maybe_get_traffic_signal(i) {
|
||||
ts
|
||||
} else {
|
||||
return Err(format!("{} isn't a traffic signal", i).into());
|
||||
};
|
||||
|
||||
let mut thruput = Throughput {
|
||||
per_direction: BTreeMap::new(),
|
||||
};
|
||||
for (idx, tg) in ts.turn_groups.keys().enumerate() {
|
||||
thruput.per_direction.insert(
|
||||
tg.clone(),
|
||||
sim.get_analytics()
|
||||
.traffic_signal_thruput
|
||||
.total_for(CompressedTurnGroupID {
|
||||
i,
|
||||
idx: u8::try_from(idx).unwrap(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
Ok(abstutil::to_json(&thruput))
|
||||
}
|
||||
// Querying data
|
||||
"/data/get-finished-trips" => Ok(abstutil::to_json(&FinishedTrips {
|
||||
trips: sim.get_analytics().finished_trips.clone(),
|
||||
@ -169,3 +194,9 @@ struct Delays {
|
||||
#[serde(serialize_with = "serialize_btreemap")]
|
||||
per_direction: BTreeMap<TurnGroupID, Vec<Duration>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Throughput {
|
||||
#[serde(serialize_with = "serialize_btreemap")]
|
||||
per_direction: BTreeMap<TurnGroupID, usize>,
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ use crate::{AgentType, AlertLocation, CarID, Event, ParkingSpot, TripID, TripMod
|
||||
use abstutil::Counter;
|
||||
use geom::{Distance, Duration, Time};
|
||||
use map_model::{
|
||||
BusRouteID, BusStopID, IntersectionID, LaneID, Map, ParkingLotID, Path, PathRequest, RoadID,
|
||||
Traversable, TurnGroupID,
|
||||
BusRouteID, BusStopID, CompressedTurnGroupID, IntersectionID, LaneID, Map, ParkingLotID, Path,
|
||||
PathRequest, RoadID, Traversable, TurnGroupID,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, VecDeque};
|
||||
@ -12,6 +12,10 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
pub struct Analytics {
|
||||
pub road_thruput: TimeSeriesCount<RoadID>,
|
||||
pub intersection_thruput: TimeSeriesCount<IntersectionID>,
|
||||
// TODO For traffic signals, intersection_thruput could theoretically use this. But that
|
||||
// requires occasionally expensive or complicated summing or merging over all directions of an
|
||||
// intersection. So for now, eat the file size cost.
|
||||
pub traffic_signal_thruput: TimeSeriesCount<CompressedTurnGroupID>,
|
||||
|
||||
// Unlike everything else in Analytics, this is just for a moment in time.
|
||||
pub demand: BTreeMap<TurnGroupID, usize>,
|
||||
@ -50,6 +54,7 @@ impl Analytics {
|
||||
Analytics {
|
||||
road_thruput: TimeSeriesCount::new(),
|
||||
intersection_thruput: TimeSeriesCount::new(),
|
||||
traffic_signal_thruput: TimeSeriesCount::new(),
|
||||
demand: BTreeMap::new(),
|
||||
bus_arrivals: Vec::new(),
|
||||
passengers_boarding: BTreeMap::new(),
|
||||
@ -99,6 +104,17 @@ impl Analytics {
|
||||
|
||||
if let Some(id) = map.get_turn_group(t) {
|
||||
*self.demand.entry(id).or_insert(0) -= 1;
|
||||
|
||||
let tg = map.get_traffic_signal(t.parent).compressed_id(t);
|
||||
self.traffic_signal_thruput.record(time, tg, a.to_type(), 1);
|
||||
if let Some(n) = passengers {
|
||||
self.traffic_signal_thruput.record(
|
||||
time,
|
||||
tg,
|
||||
AgentType::TransitRider,
|
||||
n,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user