Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Implementing a lightning fast route search engine in Typescript

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.

Titouan Galopin

April 05, 2018
Tweet

More Decks by Titouan Galopin

Other Decks in Technology

Transcript

  1. Symfony since 2015 (PHP framework) Titouan Galopin @tgalopin Created bus.io

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

    4. Connection Scan Algorithm 5. Implementation
  3. Let users find the quickest way to go from a

    point A (coordinates) to point B by bus
  4. “I want to go from the Golden Gate Bridge to

    LinkedIn Headquarters leaving at 6:58”
  5. This problem is not new, mathematicians already thought about it

    Fortunately, in Computer Science we love Mathematics!
  6. Graphs Invented in 1735 by Euler Powerful model for Computer

    Science (from AI to blockchain, compilers, ...)
  7. Djikstra algorithm: 1956 O(n * log n) Bellman–Ford algorithm: 1958

    O(n * m) n: number of nodes m : number of edges
  8. 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
  9. Still today, the best generic algorithm to solve SPP is

    in O(n * log n) n: number of nodes m : number of edges
  10. Connection Scan Algorithm Developed in Germany in 2012 Targets SPP

    with timetables Overall complexity: O(c * log c) Runtime complexity: O(c) Linear!
  11. 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 ... ...
  12. Preparation (before the query) Create a list of all the

    connections ordered by ascending departing time O(c * log c)
  13. // 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 };
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. More features! Handle walks between stops and at the start/end

    of the journey Select the 5 best results in the next minutes
  22. OSRM for walks Modern C++ routing engine for shortest paths

    in road networks http://project-osrm.org
  23. 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