synthpop/
external.rs
1use anyhow::Result;
5use serde::Deserialize;
6
7use geom::{Distance, FindClosest, LonLat, Time};
8use map_model::Map;
9
10use crate::{IndividTrip, MapBorders, PersonSpec, TripEndpoint, TripMode, TripPurpose};
11
12#[derive(Deserialize)]
13pub struct ExternalPerson {
14 pub trips: Vec<ExternalTrip>,
15}
16
17#[derive(Deserialize)]
18pub struct ExternalTrip {
19 pub departure: Time,
20 pub origin: ExternalTripEndpoint,
21 pub destination: ExternalTripEndpoint,
22 pub mode: TripMode,
23 pub purpose: TripPurpose,
24}
25
26#[derive(Deserialize)]
27pub enum ExternalTripEndpoint {
28 TripEndpoint(TripEndpoint),
29 Position(LonLat),
30}
31
32impl ExternalPerson {
33 pub fn import(
40 map: &Map,
41 input: Vec<ExternalPerson>,
42 skip_problems: bool,
43 ) -> Result<Vec<PersonSpec>> {
44 let mut closest: FindClosest<TripEndpoint> = FindClosest::new();
45 for b in map.all_buildings() {
46 closest.add_polygon(TripEndpoint::Building(b.id), &b.polygon);
47 }
48 let borders = MapBorders::new(map);
49
50 let lookup_pt = |endpt, is_origin, mode| match endpt {
51 ExternalTripEndpoint::TripEndpoint(endpt) => Ok(endpt),
52 ExternalTripEndpoint::Position(gps) => {
53 let pt = gps.to_pt(map.get_gps_bounds());
54 if map.get_boundary_polygon().contains_pt(pt) {
55 match closest.closest_pt(pt, Distance::meters(100.0)) {
56 Some((x, _)) => Ok(x),
57 None => Err(anyhow!("No building within 100m of {}", gps)),
58 }
59 } else {
60 let (incoming, outgoing) = borders.for_mode(mode);
61 let candidates = if is_origin { incoming } else { outgoing };
62 Ok(TripEndpoint::Border(
63 candidates
64 .iter()
65 .min_by_key(|border| border.gps_pos.fast_dist(gps))
66 .ok_or_else(|| anyhow!("No border for {}", mode.ongoing_verb()))?
67 .i,
68 ))
69 }
70 }
71 };
72
73 let mut results = Vec::new();
74 for person in input {
75 let mut spec = PersonSpec {
76 orig_id: None,
77 trips: Vec::new(),
78 };
79 for trip in person.trips {
80 if trip.departure < Time::START_OF_DAY {
81 if skip_problems {
82 warn!(
83 "Skipping trip with negative departure time {:?}",
84 trip.departure
85 );
86 continue;
87 } else {
88 bail!("Some trip has negative departure time {:?}", trip.departure);
89 }
90 }
91
92 spec.trips.push(IndividTrip::new(
93 trip.departure,
94 trip.purpose,
95 match lookup_pt(trip.origin, true, trip.mode) {
96 Ok(endpt) => endpt,
97 Err(err) => {
98 if skip_problems {
99 warn!("Skipping person: {}", err);
100 continue;
101 } else {
102 return Err(err);
103 }
104 }
105 },
106 match lookup_pt(trip.destination, false, trip.mode) {
107 Ok(endpt) => endpt,
108 Err(err) => {
109 if skip_problems {
110 warn!("Skipping person: {}", err);
111 continue;
112 } else {
113 return Err(err);
114 }
115 }
116 },
117 trip.mode,
118 ));
119 }
120 results.push(spec);
121 }
122 Ok(results)
123 }
124}