Slide 1

Slide 1 text

Implementing a lightning fast route search engine in Typescript SFNode April 2018

Slide 2

Slide 2 text

Symfony since 2015 (PHP framework) Titouan Galopin @tgalopin Created bus.io in 2015 (public transport app : https://getbus.io)

Slide 3

Slide 3 text

Agenda 1. Goal 2. The routing problem 3. Historic solutions 4. Connection Scan Algorithm 5. Implementation

Slide 4

Slide 4 text

Goal

Slide 5

Slide 5 text

Let users find the quickest way to go from a point A (coordinates) to point B by bus

Slide 6

Slide 6 text

“I want to go from the Golden Gate Bridge to LinkedIn Headquarters leaving at 6:58”

Slide 7

Slide 7 text

The routing problem

Slide 8

Slide 8 text

This problem is not new, mathematicians already thought about it

Slide 9

Slide 9 text

This problem is not new, mathematicians already thought about it Fortunately, in Computer Science we love Mathematics!

Slide 10

Slide 10 text

Equivalent problem in Maths: Shortest Path Problem (SPP)

Slide 11

Slide 11 text

Graphs

Slide 12

Slide 12 text

Graphs Invented in 1735 by Euler

Slide 13

Slide 13 text

Graphs Invented in 1735 by Euler Powerful model for Computer Science (from AI to blockchain, compilers, ...)

Slide 14

Slide 14 text

Non-oriented Oriented Source : https://fr.wikipedia.org/wiki/Théorie_des_graphes

Slide 15

Slide 15 text

Source : https://en.wikipedia.org/wiki/Shortest_path_problem SPP : weighted oriented graphs

Slide 16

Slide 16 text

Historic solutions

Slide 17

Slide 17 text

Djikstra algorithm: 1956 O(n * log n) n: number of nodes m : number of edges

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Still today, the best generic algorithm to solve SPP is in O(n * log n) n: number of nodes m : number of edges

Slide 21

Slide 21 text

What about more specific problems like public transports?

Slide 22

Slide 22 text

Main difference : the bus/train/… has specific timetables => additional constraint => better complexity?

Slide 23

Slide 23 text

Connection Scan Algorithm

Slide 24

Slide 24 text

Connection Scan Algorithm Developed in Germany in 2012

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Connection Scan Algorithm Developed in Germany in 2012 Targets SPP with timetables Overall complexity: O(c * log c) Runtime complexity: O(c) Linear!

Slide 27

Slide 27 text

Connection A to B 7:42 to 7:45

Slide 28

Slide 28 text

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 ... ...

Slide 29

Slide 29 text

Implementation

Slide 30

Slide 30 text

Preparation (before the query) Create a list of all the connections ordered by ascending departing time O(c * log c)

Slide 31

Slide 31 text

Runtime (during the query)

Slide 32

Slide 32 text

// Map of the the fastest ways to go to each stop // node name => fastest connection to go to the node let inConnection: StringMap = {}; // Map of earliest arrival time per node // node name => arrival time // (418 = 6 * 60 + 58) let arrivalTimes: StringMap = { A: 418 };

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

More features! Handle walks between stops and at the start/end of the journey Select the 5 best results in the next minutes

Slide 41

Slide 41 text

But how to keep performance? Async!

Slide 42

Slide 42 text

OSRM for walks Modern C++ routing engine for shortest paths in road networks http://project-osrm.org

Slide 43

Slide 43 text

Search leaving at time T Prepare in advance the connections Load the connections CSA Result 1 Query Search leaving at time T + 1 Search leaving at time T + 2 ... Select best results User Prepare in advance the walks between stops Find walks from start point Find walks to end point CSA Result 2 Search leaving at time T + 3

Slide 44

Slide 44 text

In production https://getbus.io 370 nodes 10998 connections 20 results computed, 10 bests selected 150ms per query

Slide 45

Slide 45 text

Thanks! Questions?