Skip to content

Commit 9468e51

Browse files
committedApr 20, 2024·
Support circle routes
1 parent ea8b4fc commit 9468e51

File tree

7 files changed

+310
-96
lines changed

7 files changed

+310
-96
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
/target
2+
.vscode

‎data/points.wkt

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
POINT(37.534465 55.751137),POINT(37.815976 55.712977)
22
POINT(37.743553 55.802620),POINT(37.397570 55.648272)
33
POINT(37.560980 55.549048),POINT(37.619830 55.740432)
4-
POINT(37.353372 55.856412),POINT(37.763277 55.634527)
4+
POINT(37.353372 55.856412),POINT(37.763277 55.634527)
5+
POINT(37.397570 55.648272),POINT(37.743553 55.802620)
6+
POINT(37.631472 55.778005),POINT(37.576309 55.759789)

‎src/map.rs

+85-17
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,13 @@ impl Platform {
3131

3232
#[derive(Debug)]
3333
pub struct Trip {
34-
pub stops: Vec<Time>,
3534
id: i32,
35+
pub stops: Vec<Time>,
3636
}
3737

3838
impl Trip {
39-
pub fn new(stops: Vec<Time>) -> Self {
40-
static mut ID: i32 = 0;
41-
unsafe {
42-
let id = ID;
43-
ID += 1;
44-
Self { stops, id }
45-
}
39+
pub fn new(id: i32, stops: Vec<Time>) -> Self {
40+
Self { id, stops }
4641
}
4742
}
4843

@@ -54,9 +49,9 @@ impl PartialEq for Trip {
5449

5550
#[derive(Debug)]
5651
pub struct Route {
57-
pub circle: bool,
58-
pub platforms: Vec<PlatformIndex>,
59-
pub trips: Vec<Trip>,
52+
circle: bool,
53+
platforms: Vec<PlatformIndex>,
54+
trips: Vec<Trip>,
6055
pub ordinal: HashMap<PlatformIndex, SequenceNumber>,
6156
}
6257

@@ -75,9 +70,66 @@ impl Route {
7570
}
7671
}
7772

78-
pub fn before(&self, lhs: &PlatformIndex, rhs: &PlatformIndex) -> bool {
73+
pub fn is_before(&self, lhs: &PlatformIndex, rhs: &PlatformIndex) -> bool {
7974
self.ordinal[&lhs] < self.ordinal[&rhs]
8075
}
76+
77+
pub fn is_seam(&self, platform: &PlatformIndex) -> bool {
78+
self.circle && platform == self.platforms.last().unwrap()
79+
}
80+
81+
fn next_trip(&self, time: Time, platform: &PlatformIndex) -> Option<&Trip> {
82+
let ordinal = self.ordinal[platform];
83+
let i = self.trips.partition_point(|t| t.stops[ordinal] < time);
84+
self.trips.get(i)
85+
}
86+
87+
fn next_circle_trip(&self, current_trip: &Trip) -> Option<&Trip> {
88+
let arrival = *current_trip.stops.last().unwrap();
89+
let platform = self.platforms.first().unwrap();
90+
self.next_trip(arrival, platform)
91+
}
92+
93+
fn move_on(&self, arrival: Time, platform: &PlatformIndex, trip: &Trip) -> bool {
94+
let ordinal = self.ordinal[platform];
95+
let next_circle = self.is_seam(platform);
96+
let was_here_earlier = arrival < trip.stops[ordinal];
97+
!next_circle && !was_here_earlier
98+
}
99+
100+
pub fn try_catch(
101+
&self,
102+
time: Time,
103+
platform: &PlatformIndex,
104+
trip: Option<&Trip>,
105+
) -> Option<&Trip> {
106+
if let Some(trip) = trip {
107+
if self.move_on(time, platform, trip) {
108+
return None;
109+
}
110+
if self.is_seam(platform) {
111+
return self.next_circle_trip(trip);
112+
}
113+
}
114+
self.next_trip(time, platform)
115+
}
116+
117+
pub fn range(&self, from: PlatformIndex, to: PlatformIndex) -> Vec<PlatformIndex> {
118+
let from = self.ordinal[&from];
119+
let to = self.ordinal[&to];
120+
match self.circle {
121+
true if from > to => [&self.platforms[from..], &self.platforms[..=to]].concat(),
122+
_ => self.platforms[from..=to].to_vec(),
123+
}
124+
}
125+
126+
pub fn tail(&self, from: PlatformIndex) -> Vec<PlatformIndex> {
127+
let ordinal = self.ordinal[&from];
128+
match self.circle {
129+
true => [&self.platforms[ordinal..], &self.platforms[..ordinal]].concat(),
130+
false => self.platforms[ordinal..].to_vec(),
131+
}
132+
}
81133
}
82134

83135
#[derive(Debug, Clone)]
@@ -113,11 +165,27 @@ impl PublicTransport {
113165
mod trip {
114166
use super::*;
115167

168+
fn route() -> Route {
169+
let trips = vec![
170+
Trip::new(1, vec![10, 60, 70]),
171+
Trip::new(2, vec![30, 90, 100]),
172+
Trip::new(3, vec![50, 110, 120]),
173+
];
174+
Route::new(false, vec![0, 1, 2], trips)
175+
}
176+
177+
#[test]
178+
fn no_trip() {
179+
let route = route();
180+
let trip = route.next_trip(60, &0);
181+
assert!(trip.is_none());
182+
}
183+
116184
#[test]
117-
fn new_trip() {
118-
let trip1 = Trip::new(vec![]);
119-
let _ = Trip::new(vec![]);
120-
let trip3 = Trip::new(vec![]);
121-
assert_eq!(trip1.id + 2, trip3.id);
185+
fn yes_trip() {
186+
let route = route();
187+
let trip = route.next_trip(70, &1);
188+
assert!(trip.is_some());
189+
assert_eq!(90, trip.unwrap().stops[1]);
122190
}
123191
}

‎src/path.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl fmt::Display for Path {
5353
routes.push(part.route);
5454
let line = LineString::new(part.points.clone());
5555
collection.0.push(Geometry::LineString(line));
56-
let point = (*part.points.last().unwrap()).into();
56+
let point = part.last().clone().into();
5757
collection.0.push(Geometry::Point(point));
5858
}
5959
write!(f, "{}", collection.to_wkt())

‎src/reader.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,12 @@ impl From<&Value> for Platform {
140140
impl From<&Value> for Trip {
141141
fn from(value: &Value) -> Self {
142142
let stops = make_vec_of_i64(&value);
143-
Trip::new(stops)
143+
static mut ID: i32 = 0;
144+
unsafe {
145+
let id = ID;
146+
ID += 1;
147+
Trip::new(id, stops)
148+
}
144149
}
145150
}
146151

@@ -158,6 +163,10 @@ impl From<&Value> for Route {
158163
.get(&HashableValue::String(String::from("platforms")))
159164
.unwrap_or(&Value::None),
160165
);
166+
let platforms = match circle {
167+
true => platforms[..platforms.len() - 1].to_vec(),
168+
false => platforms.to_vec(),
169+
};
161170
let trips = make_vec(
162171
route
163172
.get(&HashableValue::String(String::from("trips")))

‎src/searcher.rs

+20-63
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,8 @@ impl<'a> Searcher<'a> {
101101
for mp in marked {
102102
for r in &self.map.platforms[mp].routes {
103103
let route = &self.map.routes[*r];
104-
if route.circle {
105-
continue;
106-
}
107104
let op = routes.get(r);
108-
if op.is_none() || route.before(&mp, op.unwrap()) {
105+
if op.is_none() || route.is_before(&mp, op.unwrap()) {
109106
routes.insert(*r, mp);
110107
}
111108
}
@@ -119,26 +116,24 @@ impl<'a> Searcher<'a> {
119116
for (r, p) in routes {
120117
let mut boarding: Option<Boarding> = None;
121118
let route = &self.map.routes[r];
122-
let ordinal = route.ordinal[&p];
123-
// TODO - closed routes?
124-
for pi in &route.platforms[ordinal..] {
125-
let pi_ordinal = route.ordinal[pi];
119+
for pi in route.tail(p) {
120+
let pi_ordinal = route.ordinal[&pi];
126121
if let Some(boarding) = &boarding {
127122
let arrival = boarding.trip.stops[pi_ordinal];
128123
// With local and target pruning
129-
let minimal = cmp::min(self.best[*pi].arrival, self.arrival);
124+
let minimal = cmp::min(self.best[pi].arrival, self.arrival);
130125
if arrival < minimal {
131-
self.best[*pi] = Label::new(arrival, Some(boarding.platform), Some(r));
132-
self.labels[round][*pi] = self.best[*pi].clone();
133-
marked.insert(*pi);
134-
self.update(pi, arrival);
126+
self.best[pi] = Label::new(arrival, Some(boarding.platform), Some(r));
127+
self.labels[round][pi] = self.best[pi].clone();
128+
marked.insert(pi);
129+
self.update(&pi, arrival);
135130
}
136131
}
137-
let trip = self.try_catch(*pi, route, &boarding);
132+
let trip = self.try_catch(&pi, route, &boarding);
138133
if let Some(trip) = trip {
139134
boarding = match &boarding {
140135
Some(b) if trip == b.trip => boarding,
141-
_ => Some(Boarding::new(*pi, trip)),
136+
_ => Some(Boarding::new(pi, trip)),
142137
};
143138
}
144139
}
@@ -148,16 +143,15 @@ impl<'a> Searcher<'a> {
148143

149144
fn try_catch(
150145
&self,
151-
platform: PlatformIndex,
146+
platform: &PlatformIndex,
152147
route: &'a Route,
153148
boarding: &Option<Boarding>,
154149
) -> Option<&'a Trip> {
155-
let arrival = self.best[platform].arrival;
156-
let ordinal = route.ordinal[&platform];
157-
return match boarding {
158-
Some(b) if !(arrival < b.trip.stops[ordinal]) => None,
159-
_ => next_trip(arrival, platform, route),
160-
};
150+
let arrival = self.best[*platform].arrival;
151+
match boarding {
152+
Some(b) => route.try_catch(arrival, platform, Some(b.trip)),
153+
None => route.try_catch(arrival, platform, None),
154+
}
161155
}
162156

163157
fn transfer(&mut self, marked: &Marked) -> Marked {
@@ -219,10 +213,8 @@ impl<'a> Searcher<'a> {
219213
let mut points = vec![];
220214
if route.is_some() {
221215
let route = &self.map.routes[route.unwrap()];
222-
let from = route.ordinal[&from];
223-
let to = route.ordinal[&to];
224-
for p in &route.platforms[from..=to] {
225-
points.push(make_point(&self.map.platforms[*p].point));
216+
for p in route.range(from, to) {
217+
points.push(make_point(&self.map.platforms[p].point));
226218
}
227219
} else {
228220
points.push(make_point(&self.map.platforms[from].point));
@@ -249,12 +241,6 @@ fn make_point(point: &Point) -> Coord<f64> {
249241
}
250242
}
251243

252-
fn next_trip<'a>(time: Time, platform: PlatformIndex, route: &'a Route) -> Option<&'a Trip> {
253-
let ordinal = route.ordinal[&platform];
254-
let i = route.trips.partition_point(|t| t.stops[ordinal] < time);
255-
route.trips.get(i)
256-
}
257-
258244
fn on_foot(labels: &Labels, platform: PlatformIndex) -> bool {
259245
labels[platform].route.is_none()
260246
}
@@ -310,35 +296,6 @@ mod labels {
310296
}
311297
}
312298

313-
#[cfg(test)]
314-
mod utils {
315-
use super::*;
316-
317-
fn route() -> Route {
318-
let trips = vec![
319-
Trip::new(vec![10, 60, 70]),
320-
Trip::new(vec![30, 90, 100]),
321-
Trip::new(vec![50, 110, 120]),
322-
];
323-
Route::new(false, vec![0, 1, 2], trips)
324-
}
325-
326-
#[test]
327-
fn no_trip() {
328-
let route = route();
329-
let trip = next_trip(60, 0, &route);
330-
assert!(trip.is_none());
331-
}
332-
333-
#[test]
334-
fn yes_trip() {
335-
let route = route();
336-
let trip = next_trip(70, 1, &route);
337-
assert!(trip.is_some());
338-
assert_eq!(90, trip.unwrap().stops[1]);
339-
}
340-
}
341-
342299
#[cfg(test)]
343300
mod searcher {
344301
use super::*;
@@ -364,12 +321,12 @@ mod searcher {
364321
Route::new(
365322
false,
366323
vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
367-
vec![Trip::new(vec![20, 25, 30, 35, 40, 45, 50, 55, 60, 65])],
324+
vec![Trip::new(1, vec![20, 25, 30, 35, 40, 45, 50, 55, 60, 65])],
368325
),
369326
Route::new(
370327
false,
371328
vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
372-
vec![Trip::new(vec![25, 30, 35, 40, 45, 50, 55, 60, 65, 70])],
329+
vec![Trip::new(2, vec![25, 30, 35, 40, 45, 50, 55, 60, 65, 70])],
373330
),
374331
]
375332
}

‎tests/searcher_test.rs

+190-13
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ fn one_route() {
2020
vec![Route::new(
2121
false,
2222
vec![0, 1, 2, 3, 4],
23-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
23+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
2424
)],
2525
vec![vec![]; 5],
2626
);
@@ -60,12 +60,12 @@ fn two_cross_route() {
6060
Route::new(
6161
false,
6262
vec![0, 1, 2, 3, 4],
63-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
63+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
6464
),
6565
Route::new(
6666
false,
6767
vec![5, 6, 2, 7, 8],
68-
vec![Trip::new(vec![20, 40, 60, 70, 80])],
68+
vec![Trip::new(2, vec![20, 40, 60, 70, 80])],
6969
),
7070
],
7171
vec![vec![]; 9],
@@ -105,12 +105,12 @@ fn two_parallel_route() {
105105
Route::new(
106106
false,
107107
vec![0, 1, 2, 3, 4],
108-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
108+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
109109
),
110110
Route::new(
111111
false,
112112
vec![0, 5, 6, 7, 4],
113-
vec![Trip::new(vec![7, 20, 40, 60, 73])],
113+
vec![Trip::new(2, vec![7, 20, 40, 60, 73])],
114114
),
115115
],
116116
vec![vec![]; 8],
@@ -152,17 +152,17 @@ fn direct_faster() {
152152
Route::new(
153153
false,
154154
vec![0, 1, 2, 3, 4],
155-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
155+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
156156
),
157157
Route::new(
158158
false,
159159
vec![0, 5, 6, 7],
160-
vec![Trip::new(vec![7, 10, 20, 30])],
160+
vec![Trip::new(2, vec![7, 10, 20, 30])],
161161
),
162162
Route::new(
163163
false,
164164
vec![7, 8, 9, 4],
165-
vec![Trip::new(vec![35, 45, 55, 65])],
165+
vec![Trip::new(3, vec![35, 45, 55, 65])],
166166
),
167167
],
168168
vec![vec![]; 10],
@@ -204,17 +204,17 @@ fn direct_slower() {
204204
Route::new(
205205
false,
206206
vec![0, 1, 2, 3, 4],
207-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
207+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
208208
),
209209
Route::new(
210210
false,
211211
vec![0, 5, 6, 7],
212-
vec![Trip::new(vec![7, 15, 20, 25])],
212+
vec![Trip::new(2, vec![7, 15, 20, 25])],
213213
),
214214
Route::new(
215215
false,
216216
vec![7, 8, 9, 4],
217-
vec![Trip::new(vec![30, 35, 40, 45])],
217+
vec![Trip::new(3, vec![30, 35, 40, 45])],
218218
),
219219
],
220220
vec![vec![]; 10],
@@ -280,12 +280,12 @@ fn do_transfer() {
280280
Route::new(
281281
false,
282282
vec![0, 1, 2, 3, 4],
283-
vec![Trip::new(vec![10, 20, 30, 40, 50])],
283+
vec![Trip::new(1, vec![10, 20, 30, 40, 50])],
284284
),
285285
Route::new(
286286
false,
287287
vec![5, 6, 7, 8],
288-
vec![Trip::new(vec![10, 20, 40, 60])],
288+
vec![Trip::new(2, vec![10, 20, 40, 60])],
289289
),
290290
],
291291
vec![
@@ -322,3 +322,180 @@ fn do_transfer() {
322322
)];
323323
assert_eq!(expected, searcher.run(1));
324324
}
325+
326+
#[test]
327+
fn circle() {
328+
let map = PublicTransport::new(
329+
vec![
330+
Platform::new(Point::new(0., 1.), vec![0]),
331+
Platform::new(Point::new(0., 2.), vec![0]),
332+
Platform::new(Point::new(0., 3.), vec![0]),
333+
Platform::new(Point::new(0., 4.), vec![0]),
334+
Platform::new(Point::new(0., 5.), vec![0]),
335+
],
336+
vec![Route::new(
337+
true,
338+
vec![0, 1, 2, 3, 4],
339+
vec![
340+
Trip::new(1, vec![10, 20, 30, 40, 50, 60]),
341+
Trip::new(2, vec![30, 40, 50, 60, 70, 80]),
342+
Trip::new(3, vec![60, 70, 80, 90, 100, 110]),
343+
],
344+
)],
345+
vec![vec![]; 5],
346+
);
347+
let platforms = Platforms::from(Walking::from([(1, 5)]), Walking::from([(3, 10)]));
348+
let mut searcher = Searcher::new(&map, platforms);
349+
let expected: Vec<Path> = vec![Path::new(
350+
vec![Part::new(
351+
vec![
352+
coord! {x: 2., y: 0.},
353+
coord! {x: 3., y: 0.},
354+
coord! {x: 4., y: 0.},
355+
],
356+
Some(0),
357+
)],
358+
50,
359+
)];
360+
assert_eq!(expected, searcher.run(1));
361+
}
362+
363+
#[test]
364+
fn circle_on_seam() {
365+
let map = PublicTransport::new(
366+
vec![
367+
Platform::new(Point::new(0., 1.), vec![0]),
368+
Platform::new(Point::new(0., 2.), vec![0]),
369+
Platform::new(Point::new(0., 3.), vec![0]),
370+
Platform::new(Point::new(0., 4.), vec![0]),
371+
Platform::new(Point::new(0., 5.), vec![0]),
372+
],
373+
vec![Route::new(
374+
true,
375+
vec![0, 1, 2, 3, 4],
376+
vec![
377+
Trip::new(1, vec![10, 20, 30, 40, 50, 60]),
378+
Trip::new(2, vec![30, 40, 50, 60, 70, 80]),
379+
Trip::new(3, vec![60, 70, 80, 90, 100, 110]),
380+
],
381+
)],
382+
vec![vec![]; 5],
383+
);
384+
let platforms = Platforms::from(Walking::from([(3, 5)]), Walking::from([(1, 5)]));
385+
let mut searcher = Searcher::new(&map, platforms);
386+
// TODO - how to join two parts?
387+
let expected: Vec<Path> = vec![Path::new(
388+
vec![
389+
Part::new(vec![coord! {x: 4., y: 0.}, coord! {x: 5., y: 0.}], Some(0)),
390+
Part::new(
391+
vec![
392+
coord! {x: 5., y: 0.},
393+
coord! {x: 1., y: 0.},
394+
coord! {x: 2., y: 0.},
395+
],
396+
Some(0),
397+
),
398+
],
399+
75,
400+
)];
401+
assert_eq!(expected, searcher.run(1));
402+
}
403+
404+
#[test]
405+
fn bidirectional_circle() {
406+
let map = PublicTransport::new(
407+
vec![
408+
Platform::new(Point::new(0., 1.), vec![0, 1]),
409+
Platform::new(Point::new(0., 2.), vec![0, 1]),
410+
Platform::new(Point::new(0., 3.), vec![0, 1]),
411+
Platform::new(Point::new(0., 4.), vec![0, 1]),
412+
Platform::new(Point::new(0., 5.), vec![0, 1]),
413+
],
414+
vec![
415+
Route::new(
416+
true,
417+
vec![0, 1, 2, 3, 4],
418+
vec![
419+
Trip::new(1, vec![10, 20, 30, 40, 50, 60]),
420+
Trip::new(2, vec![30, 40, 50, 60, 70, 80]),
421+
Trip::new(3, vec![60, 70, 80, 90, 100, 110]),
422+
],
423+
),
424+
Route::new(
425+
true,
426+
vec![4, 3, 2, 1, 0],
427+
vec![
428+
Trip::new(4, vec![15, 25, 35, 45, 55, 65]),
429+
Trip::new(5, vec![35, 45, 55, 65, 75, 85]),
430+
Trip::new(6, vec![65, 75, 85, 95, 105, 115]),
431+
],
432+
),
433+
],
434+
vec![vec![]; 5],
435+
);
436+
let platforms = Platforms::from(Walking::from([(3, 5)]), Walking::from([(1, 5)]));
437+
let mut searcher = Searcher::new(&map, platforms);
438+
let expected: Vec<Path> = vec![Path::new(
439+
vec![Part::new(
440+
vec![
441+
coord! {x: 4., y: 0.},
442+
coord! {x: 3., y: 0.},
443+
coord! {x: 2., y: 0.},
444+
],
445+
Some(1),
446+
)],
447+
50,
448+
)];
449+
assert_eq!(expected, searcher.run(1));
450+
}
451+
452+
#[test]
453+
fn bidirectional_circle_on_seam() {
454+
let map = PublicTransport::new(
455+
vec![
456+
Platform::new(Point::new(0., 1.), vec![0, 1]),
457+
Platform::new(Point::new(0., 2.), vec![0, 1]),
458+
Platform::new(Point::new(0., 3.), vec![0, 1]),
459+
Platform::new(Point::new(0., 4.), vec![0, 1]),
460+
Platform::new(Point::new(0., 5.), vec![0, 1]),
461+
],
462+
vec![
463+
Route::new(
464+
true,
465+
vec![0, 1, 2, 3, 4],
466+
vec![
467+
Trip::new(1, vec![10, 30, 50, 70, 90, 110]),
468+
Trip::new(2, vec![60, 80, 100, 120, 140, 160]),
469+
Trip::new(3, vec![110, 130, 150, 170, 190, 210]),
470+
],
471+
),
472+
Route::new(
473+
true,
474+
vec![4, 3, 2, 1, 0],
475+
vec![
476+
Trip::new(4, vec![15, 25, 35, 45, 55, 65]),
477+
Trip::new(5, vec![35, 45, 55, 65, 75, 85]),
478+
Trip::new(6, vec![65, 75, 85, 95, 105, 115]),
479+
],
480+
),
481+
],
482+
vec![vec![]; 5],
483+
);
484+
let platforms = Platforms::from(Walking::from([(1, 5)]), Walking::from([(3, 5)]));
485+
let mut searcher = Searcher::new(&map, platforms);
486+
let expected: Vec<Path> = vec![Path::new(
487+
vec![
488+
Part::new(vec![coord! {x: 2., y: 0.}, coord! {x: 1., y: 0.}], Some(1)),
489+
Part::new(
490+
vec![
491+
coord! {x: 1., y: 0.},
492+
coord! {x: 5., y: 0.},
493+
coord! {x: 4., y: 0.},
494+
],
495+
Some(1),
496+
),
497+
],
498+
80,
499+
)];
500+
assert_eq!(expected, searcher.run(26));
501+
}

0 commit comments

Comments
 (0)
Please sign in to comment.