Titouan Galopin
April 05, 2018
330

Implementing a lightning fast route search engine in Typescript

In the context of building a mobile app for public transport, I had to implement a route search engine for the region of Oise, France. It was a more complex journey than I expected and I'd like to give a glimpse on how I implemented the Connection Scan Algorithm in Typescript, heavily leveraging the async features of node.

April 05, 2018

Transcript

April 2018
2. Symfony since 2015 (PHP framework) Titouan Galopin @tgalopin Created bus.io

in 2015 (public transport app : https://getbus.io)
3. Agenda 1. Goal 2. The routing problem 3. Historic solutions

4. Connection Scan Algorithm 5. Implementation

5. Let users find the quickest way to go from a

point A (coordinates) to point B by bus

7. The routing problem

Fortunately, in Computer Science we love Mathematics!

13. Graphs Invented in 1735 by Euler Powerful model for Computer

Science (from AI to blockchain, compilers, ...)

17. Djikstra algorithm: 1956 O(n * log n) n: number of

nodes m : number of edges
18. Djikstra algorithm: 1956 O(n * log n) Bellman–Ford algorithm: 1958

O(n * m) n: number of nodes m : number of edges
19. Djikstra algorithm: 1956 O(n * log n) Bellman–Ford algorithm: 1958

O(n * m) A* algorithm: 1968 Extension of Djikstra, O(n * log n) but much better in real life, most used today n: number of nodes m : number of edges
20. Still today, the best generic algorithm to solve SPP is

in O(n * log n) n: number of nodes m : number of edges

22. Main difference : the bus/train/… has specific timetables => additional

constraint => better complexity?

25. Connection Scan Algorithm Developed in Germany in 2012 Targets SPP

with timetables
26. Connection Scan Algorithm Developed in Germany in 2012 Targets SPP

with timetables Overall complexity: O(c * log c) Runtime complexity: O(c) Linear!

28. A to B 7:00 to 7:05 B to C 7:06

to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17 E to F 7:20 to 7:29 List of connections instead of Graph ... ...

30. Preparation (before the query) Create a list of all the

connections ordered by ascending departing time O(c * log c)

32. // Map of the the fastest ways to go to

each stop // node name => fastest connection to go to the node let inConnection: StringMap<Connection> = {}; // Map of earliest arrival time per node // node name => arrival time // (418 = 6 * 60 + 58) let arrivalTimes: StringMap<number> = { A: 418 };
33. for (let i in connections) { const connection = connections[i];

// If we never arrived to this connection’s departure stop, // we never will as connections are ordered by departure time if (typeof arrivalTimes[connection.departure_stop] === 'undefined') { continue; } // If the connection leaves before we arrive at its departure, it can't be used if (arrivalTimes[connection.departure_stop] > connection.departure_time) { continue; } // If there already was a connection but the previous connection arrived // earlier, keep the previous one if (arrivalTimes[connection.arrival_stop] < connection.arrival_time) { continue; } // Otherwise, we know for sure this connection is better than what we had arrivalTimes[connection.arrival_stop] = connection.arrival_time; inConnection[connection.arrival_stop] = connection; } A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17
34. for (let i in connections) { const connection = connections[i];

// If we never arrived to this connection’s departure stop, // we never will as connections are ordered by departure time if (typeof arrivalTimes[connection.departure_stop] === 'undefined') { continue; } // If the connection leaves before we arrive at its departure, it can't be used if (arrivalTimes[connection.departure_stop] > connection.departure_time) { continue; } // If there already was a connection but the previous connection arrived // earlier, keep the previous one if (arrivalTimes[connection.arrival_stop] < connection.arrival_time) { continue; } // Otherwise, we know for sure this connection is better than what we had arrivalTimes[connection.arrival_stop] = connection.arrival_time; inConnection[connection.arrival_stop] = connection; } A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17
35. for (let i in connections) { const connection = connections[i];

// If we never arrived to this connection’s departure stop, // we never will as connections are ordered by departure time if (typeof arrivalTimes[connection.departure_stop] === 'undefined') { continue; } // If the connection leaves before we arrive at its departure, it can't be used if (arrivalTimes[connection.departure_stop] > connection.departure_time) { continue; } // If there already was a connection but the previous connection arrived // earlier, keep the previous one if (arrivalTimes[connection.arrival_stop] < connection.arrival_time) { continue; } // Otherwise, we know for sure this connection is better than what we had arrivalTimes[connection.arrival_stop] = connection.arrival_time; inConnection[connection.arrival_stop] = connection; } A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17
36. for (let i in connections) { const connection = connections[i];

// If we never arrived to this connection’s departure stop, // we never will as connections are ordered by departure time if (typeof arrivalTimes[connection.departure_stop] === 'undefined') { continue; } // If the connection leaves before we arrive at its departure, it can't be used if (arrivalTimes[connection.departure_stop] > connection.departure_time) { continue; } // If there already was a connection but the previous connection arrived // earlier, keep the previous one if (arrivalTimes[connection.arrival_stop] < connection.arrival_time) { continue; } // Otherwise, we know for sure this connection is better than what we had arrivalTimes[connection.arrival_stop] = connection.arrival_time; inConnection[connection.arrival_stop] = connection; } A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17
37. for (let i in connections) { const connection = connections[i];

// If we never arrived to this connection’s departure stop, // we never will as connections are ordered by departure time if (typeof arrivalTimes[connection.departure_stop] === 'undefined') { continue; } // If the connection leaves before we arrive at its departure, it can't be used if (arrivalTimes[connection.departure_stop] > connection.departure_time) { continue; } // If there already was a connection but the previous connection arrived // earlier, keep the previous one if (arrivalTimes[connection.arrival_stop] < connection.arrival_time) { continue; } // Otherwise, we know for sure this connection is better than what we had arrivalTimes[connection.arrival_stop] = connection.arrival_time; inConnection[connection.arrival_stop] = connection; } A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17
38. A to B 7:00 to 7:05 B to C 7:06

to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 B to E 7:11 to 7:18 G to D 7:08 to 7:11 D to E 7:12 to 7:17 // We know these times are // the earliest possible arrival // times for each stop arrivalTimes: A: 6:58 B: 7:05 C: 7:09 D: 7:11 E: 7:15 // Moreover, we get a list of the // fastest way to get to each node inConnection: B: C: D: E: A to B 7:00 to 7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15
39. inConnection: B: C: D: E: A to B 7:00 to

7:05 B to C 7:06 to 7:09 B to D 7:07 to 7:11 C to E 7:10 to 7:15 let steps = []; let current = 'E'; while (current !== 'A') { let step = inConnection[current]; current = step['departure_stop']; steps.push(step); } // Result: A -> B -> C -> E
40. More features! Handle walks between stops and at the start/end

of the journey Select the 5 best results in the next minutes