1use std::collections::{BTreeMap, HashMap, HashSet};
2
3use serde::{Deserialize, Serialize};
4
5use abstio::{CityName, FileWithProgress};
6use abstutil::{prettyprint_usize, Counter, Timer};
7use geom::{Distance, Duration, LonLat, Time};
8use kml::{ExtraShape, ExtraShapes};
9use map_model::{osm, Map};
10use synthpop::{OrigPersonID, TripMode, TripPurpose};
11
12#[derive(Serialize, Deserialize)]
13pub struct PopDat {
14 pub trips: Vec<OrigTrip>,
15}
16
17pub fn import_data(huge_map: &Map, timer: &mut Timer) -> PopDat {
19 let trips = import_trips(huge_map, timer);
20 let popdat = PopDat { trips };
21 abstio::write_binary(abstio::path_popdat(), &popdat);
22 popdat
23}
24
25fn import_trips(huge_map: &Map, timer: &mut Timer) -> Vec<OrigTrip> {
26 let (parcels, mut keyed_shapes) = import_parcels(huge_map, timer);
27
28 let mut trips = Vec::new();
29 let (reader, done) =
30 FileWithProgress::new(&CityName::seattle().input_path("trips_2014.csv")).unwrap();
31 let mut total_records = 0;
32 let mut people: HashSet<OrigPersonID> = HashSet::new();
33 let mut trips_from_parcel: Counter<usize> = Counter::new();
34 let mut trips_to_parcel: Counter<usize> = Counter::new();
35
36 for rec in csv::Reader::from_reader(reader).deserialize() {
37 total_records += 1;
38 let rec: RawTrip = rec.unwrap();
39
40 let from = parcels[&(rec.opcl as usize)].clone();
41 let to = parcels[&(rec.dpcl as usize)].clone();
42 trips_from_parcel.inc(from.parcel_id);
43 trips_to_parcel.inc(to.parcel_id);
44
45 if from.osm_building == to.osm_building {
48 if from.osm_building.is_some() {
49 }
54 continue;
55 }
56
57 let depart_at = Time::START_OF_DAY + Duration::minutes(rec.deptm as usize);
58
59 let mode = get_mode(&rec.mode);
60 let purpose = get_purpose(&rec.dpurp);
61
62 let trip_time = Duration::f64_minutes(rec.travtime);
63 let trip_dist = Distance::miles(rec.travdist);
64
65 let person = OrigPersonID(rec.hhno as usize, rec.pno as usize);
66 people.insert(person);
67 #[allow(clippy::float_cmp)]
68 let seq = (rec.tour as usize, rec.half == 2.0, rec.tseg as usize);
69
70 trips.push(OrigTrip {
71 from,
72 to,
73 depart_at,
74 mode,
75 person,
76 seq,
77 purpose,
78 trip_time,
79 trip_dist,
80 });
81 }
82 done(timer);
83
84 info!(
85 "{} trips total, over {} people. {} records filtered out",
86 prettyprint_usize(trips.len()),
87 prettyprint_usize(people.len()),
88 prettyprint_usize(total_records - trips.len())
89 );
90
91 trips.sort_by_key(|t| t.depart_at);
92
93 for (id, cnt) in trips_from_parcel.consume() {
95 if let Some(ref mut es) = keyed_shapes.get_mut(&id) {
96 es.attributes
97 .insert("trips_from".to_string(), cnt.to_string());
98 }
99 }
100 for (id, cnt) in trips_to_parcel.consume() {
101 if let Some(ref mut es) = keyed_shapes.get_mut(&id) {
102 es.attributes
103 .insert("trips_to".to_string(), cnt.to_string());
104 }
105 }
106 let shapes: Vec<ExtraShape> = keyed_shapes.into_iter().map(|(_, v)| v).collect();
107 abstio::write_binary(
108 CityName::seattle().input_path("parcels.bin"),
109 &ExtraShapes { shapes },
110 );
111
112 trips
113}
114
115#[cfg(feature = "scenarios")]
118fn import_parcels(
119 huge_map: &Map,
120 timer: &mut Timer,
121) -> (HashMap<usize, Endpoint>, BTreeMap<usize, ExtraShape>) {
122 use geom::FindClosest;
123
124 let mut closest_bldg: FindClosest<osm::OsmID> = FindClosest::new();
127 for b in huge_map.all_buildings() {
128 closest_bldg.add_polygon(b.orig_id, &b.polygon);
129 }
130
131 let mut x_coords: Vec<f64> = Vec::new();
132 let mut y_coords: Vec<f64> = Vec::new();
133 let mut z_coords: Vec<f64> = Vec::new();
135 let mut parcel_metadata = Vec::new();
137
138 let (reader, done) =
139 FileWithProgress::new(&CityName::seattle().input_path("parcels_urbansim.txt")).unwrap();
140 for rec in csv::ReaderBuilder::new()
141 .delimiter(b' ')
142 .from_reader(reader)
143 .deserialize()
144 {
145 let rec: RawParcel = rec.unwrap();
146 parcel_metadata.push((rec.parcelid, rec.hh_p, rec.parkdy_p + rec.parkhr_p));
148 x_coords.push(rec.xcoord_p);
149 y_coords.push(rec.ycoord_p);
150 z_coords.push(0.0);
151 }
152 done(timer);
153
154 timer.start(format!("transform {} points", parcel_metadata.len()));
155
156 let transform = gdal::spatial_ref::CoordTransform::new(
158 &gdal::spatial_ref::SpatialRef::from_proj4(
159 "+proj=lcc +lat_1=47.5 +lat_2=48.73333333333333 +lat_0=47 +lon_0=-120.8333333333333 \
160 +x_0=500000.0000000002 +y_0=0 +datum=NAD83 +units=us-ft +no_defs",
161 )
162 .expect("washington state plane"),
163 &gdal::spatial_ref::SpatialRef::from_epsg(4326).unwrap(),
164 )
165 .expect("regular GPS");
166 transform
167 .transform_coords(&mut x_coords, &mut y_coords, &mut z_coords)
168 .expect("transform coords");
169
170 timer.stop(format!("transform {} points", parcel_metadata.len()));
171
172 let bounds = huge_map.get_gps_bounds();
173 let boundary = huge_map.get_boundary_polygon();
174 let mut result = HashMap::new();
175 let mut shapes = BTreeMap::new();
176 timer.start_iter("finalize parcel output", parcel_metadata.len());
177 for ((x, y), (id, num_households, offstreet_parking_spaces)) in x_coords
178 .into_iter()
179 .zip(y_coords.into_iter())
180 .zip(parcel_metadata.into_iter())
181 {
182 timer.next();
183 let gps = LonLat::new(y, x);
185 let pt = gps.to_pt(bounds);
186 let osm_building = if bounds.contains(gps) {
187 closest_bldg
188 .closest_pt(pt, Distance::meters(30.0))
189 .map(|(b, _)| b)
190 } else {
191 None
192 };
193 result.insert(
194 id,
195 Endpoint {
196 pos: gps,
197 osm_building,
198 parcel_id: id,
199 },
200 );
201
202 if boundary.contains_pt(pt) {
203 let mut attributes = BTreeMap::new();
204 attributes.insert("id".to_string(), id.to_string());
205 if num_households > 0 {
206 attributes.insert("households".to_string(), num_households.to_string());
207 }
208 if offstreet_parking_spaces > 0 {
209 attributes.insert("parking".to_string(), offstreet_parking_spaces.to_string());
210 }
211 if let Some(b) = osm_building {
212 attributes.insert("osm_bldg".to_string(), b.inner_id().to_string());
213 }
214 shapes.insert(
215 id,
216 ExtraShape {
217 points: vec![gps],
218 attributes,
219 },
220 );
221 }
222 }
223 info!("{} parcels", prettyprint_usize(result.len()));
224
225 (result, shapes)
226}
227
228#[cfg(not(feature = "scenarios"))]
229fn import_parcels(
230 _: &Map,
231 _: &mut Timer,
232) -> (HashMap<usize, Endpoint>, BTreeMap<usize, ExtraShape>) {
233 panic!("Can't import_parcels for popdat.bin without the scenarios feature (GDAL dependency)");
234}
235
236fn get_purpose(code: &str) -> TripPurpose {
238 match code {
239 "0.0" => TripPurpose::Home,
240 "1.0" => TripPurpose::Work,
241 "2.0" => TripPurpose::School,
242 "3.0" => TripPurpose::Escort,
243 "4.0" => TripPurpose::PersonalBusiness,
244 "5.0" => TripPurpose::Shopping,
245 "6.0" => TripPurpose::Meal,
246 "7.0" => TripPurpose::Social,
247 "8.0" => TripPurpose::Recreation,
248 "9.0" => TripPurpose::Medical,
249 "10.0" => TripPurpose::ParkAndRideTransfer,
250 _ => panic!("Unknown dpurp {}", code),
251 }
252}
253
254fn get_mode(code: &str) -> TripMode {
256 match code {
257 "1.0" => TripMode::Walk,
258 "2.0" => TripMode::Bike,
259 "3.0" | "4.0" | "5.0" => TripMode::Drive,
260 "6.0" | "7.0" | "8.0" => TripMode::Transit,
262 "0.0" => TripMode::Walk,
265 _ => panic!("Unknown mode {}", code),
266 }
267}
268
269#[derive(Debug, Deserialize)]
285struct RawTrip {
286 opcl: f64,
287 dpcl: f64,
288 deptm: f64,
289 mode: String,
290 dpurp: String,
291 travtime: f64,
292 travdist: f64,
293 hhno: f64,
294 pno: f64,
295 tour: f64,
296 half: f64,
297 tseg: f64,
298}
299
300#[derive(Debug, Deserialize)]
302#[allow(unused)]
304struct RawParcel {
305 parcelid: usize,
306 hh_p: usize,
307 parkdy_p: usize,
308 parkhr_p: usize,
309 xcoord_p: f64,
310 ycoord_p: f64,
311}
312
313#[derive(Clone, Debug, Serialize, Deserialize)]
314pub struct OrigTrip {
315 pub from: Endpoint,
316 pub to: Endpoint,
317 pub depart_at: Time,
318 pub mode: TripMode,
319
320 pub person: OrigPersonID,
322 pub seq: (usize, bool, usize),
324 pub purpose: TripPurpose,
326 pub trip_time: Duration,
327 pub trip_dist: Distance,
328}
329
330#[derive(Clone, Debug, Serialize, Deserialize)]
331pub struct Endpoint {
332 pub pos: LonLat,
333 pub osm_building: Option<osm::OsmID>,
334 pub parcel_id: usize,
335}