1use std::collections::BTreeSet;
2use std::fmt;
3
4use anyhow::Result;
5use serde::{Deserialize, Serialize};
6
7use abstio::{CityName, MapName};
8use abstutil::prettyprint_usize;
9use geom::Time;
10use map_model::Map;
11
12use crate::{OrigPersonID, TripEndpoint, TripMode};
13
14#[derive(Clone, Serialize, Deserialize, Debug)]
16pub struct Scenario {
17 pub scenario_name: String,
18 pub map_name: MapName,
19
20 pub people: Vec<PersonSpec>,
21 pub only_seed_buses: Option<BTreeSet<String>>,
23}
24
25#[derive(Clone, Serialize, Deserialize, Debug)]
26pub struct PersonSpec {
27 pub orig_id: Option<OrigPersonID>,
29 pub trips: Vec<IndividTrip>,
33}
34
35#[derive(Clone, Serialize, Deserialize, Debug)]
36pub struct IndividTrip {
37 pub depart: Time,
38 pub origin: TripEndpoint,
39 pub destination: TripEndpoint,
40 pub mode: TripMode,
41 pub purpose: TripPurpose,
42 pub cancelled: bool,
43 pub modified: bool,
45}
46
47impl IndividTrip {
48 pub fn new(
49 depart: Time,
50 purpose: TripPurpose,
51 origin: TripEndpoint,
52 destination: TripEndpoint,
53 mode: TripMode,
54 ) -> IndividTrip {
55 IndividTrip {
56 depart,
57 origin,
58 destination,
59 mode,
60 purpose,
61 cancelled: false,
62 modified: false,
63 }
64 }
65}
66
67#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
69pub enum TripPurpose {
70 Home,
71 Work,
72 School,
73 Escort,
74 PersonalBusiness,
75 Shopping,
76 Meal,
77 Social,
78 Recreation,
79 Medical,
80 ParkAndRideTransfer,
81}
82
83impl fmt::Display for TripPurpose {
84 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85 write!(
86 f,
87 "{}",
88 match self {
89 TripPurpose::Home => "home",
90 TripPurpose::Work => "work",
91 TripPurpose::School => "school",
92 TripPurpose::Escort => "escort",
94 TripPurpose::PersonalBusiness => "personal business",
95 TripPurpose::Shopping => "shopping",
96 TripPurpose::Meal => "eating",
97 TripPurpose::Social => "social",
98 TripPurpose::Recreation => "recreation",
99 TripPurpose::Medical => "medical",
100 TripPurpose::ParkAndRideTransfer => "park-and-ride transfer",
101 }
102 )
103 }
104}
105
106impl Scenario {
107 pub fn save(&self) {
108 abstio::write_binary(
109 abstio::path_scenario(&self.map_name, &self.scenario_name),
110 self,
111 );
112 }
113
114 pub fn empty(map: &Map, name: &str) -> Scenario {
115 Scenario {
116 scenario_name: name.to_string(),
117 map_name: map.get_name().clone(),
118 people: Vec::new(),
119 only_seed_buses: Some(BTreeSet::new()),
120 }
121 }
122
123 pub fn remove_weird_schedules(mut self, verbose: bool) -> Scenario {
124 let orig = self.people.len();
125 self.people.retain(|person| match person.check_schedule() {
126 Ok(()) => true,
127 Err(err) => {
128 if verbose {
129 warn!("{}", err);
130 }
131 false
132 }
133 });
134 warn!(
135 "{} of {} people have nonsense schedules",
136 prettyprint_usize(orig - self.people.len()),
137 prettyprint_usize(orig)
138 );
139 self
140 }
141
142 pub fn all_trips(&self) -> impl Iterator<Item = &IndividTrip> {
143 self.people.iter().flat_map(|p| p.trips.iter())
144 }
145
146 pub fn default_scenario_for_map(name: &MapName) -> String {
147 if name.city == CityName::seattle()
148 && abstio::file_exists(abstio::path_scenario(name, "weekday"))
149 {
150 return "weekday".to_string();
151 }
152 if name.city.country == "gb" {
153 for x in ["background", "base_with_bg"] {
154 if abstio::file_exists(abstio::path_scenario(name, x)) {
155 return x.to_string();
156 }
157 }
158 }
159 "home_to_work".to_string()
161 }
162}
163
164impl PersonSpec {
165 pub fn check_schedule(&self) -> Result<()> {
167 if self.trips.is_empty() {
168 bail!("Person ({:?}) has no trips at all", self.orig_id);
169 }
170
171 for pair in self.trips.windows(2) {
172 if pair[0].depart >= pair[1].depart {
173 bail!(
174 "Person ({:?}) starts two trips in the wrong order: {} then {}",
175 self.orig_id,
176 pair[0].depart,
177 pair[1].depart
178 );
179 }
180
181 if pair[0].destination != pair[1].origin {
182 if matches!(pair[0].destination, TripEndpoint::Border(_))
184 && matches!(pair[1].origin, TripEndpoint::Border(_))
185 {
186 continue;
187 }
188 bail!(
189 "Person ({:?}) warps from {:?} to {:?} during adjacent trips",
190 self.orig_id,
191 pair[0].destination,
192 pair[1].origin
193 );
194 }
195 }
196
197 for trip in &self.trips {
198 if trip.origin == trip.destination {
199 bail!(
200 "Person ({:?}) has a trip from/to the same place: {:?}",
201 self.orig_id,
202 trip.origin
203 );
204 }
205 }
206
207 Ok(())
208 }
209}