Tous les articles

·4 min de lecture

Placing a train on the map without any position data

SNCF never publishes the position of trains in service. Ramedex estimates it by crossing three open sources: GTFS timetables, SIRI delays and track geometry. The same calculation runs on server and browser, with accepted limits.

In Ramedex, you capture the real trains that pass within 12 km of you. To capture trains, you first need to know where they are. And the answer is disappointing: the position of SNCF trains in service is published nowhere. No API, no dataset, no real-time feed that would hand you a GPS point. I had to estimate it, and estimate it well enough, because the whole game rests on it.

What does exist, in the open, comes in three pieces.

  • The planned timetables of every train, in GTFS format. Every route, every station served, every arrival and departure time.
  • Real-time delays, in SIRI Lite, refreshed every 2 minutes.
  • The geometric layout of the tracks, published by SNCF Réseau for the open lines and completed with OpenStreetMap for the rest.

None of the three gives a position. Taken together, they let me work one out.

A fraction of time, a fraction of track

Between two consecutive stations, the calculation fits in a few lines. I start from the departure time at station A and the arrival time at station B, both shifted by the last known delay for that train. The current time gives me the fraction of time elapsed since departure. Simplified version:

// A train's progress along a leg, between 0 and 1.
function progression(depart, arrivee, maintenant) {
  if (maintenant <= depart) return 0;
  if (maintenant >= arrivee) return 1;
  return (maintenant - depart) / (arrivee - depart);
}

That fraction isn't applied along a straight line between the two stations, but along the length of the leg's actual track. The track is a polyline, each segment has a length, I add them up and stop at the point that matches the fraction.

The straight line between two stations is the idea that comes first, and for the game it would almost be enough. The capture radius is 12 km, and a railway line rarely strays that far from its chord. Visually, though, it doesn't hold up. A railway follows valleys and works around the terrain. Trains cutting across fields, on a map where the rails are drawn, show up straight away. When the geometry is missing, the straight line is the fallback.

Interpolation assumes a constant speed, which is wrong. A train accelerates out of a station, slows down on approach, sits still during the stop. Over a leg that takes several minutes, those errors partly cancel each other out, and a collection game doesn't need anything finer.

At the platform, on the announced track

Between its arrival time and its departure time at a station, the train isn't moving. Interpolation would make it drift through the stop, so that window is a case of its own. The train is at the platform. If the arrival track is announced, I put it there, with a bit of label cleanup. 1B names a section of track 1: I look for that section first, and fall back to track 1 if it isn't known. If the track is unknown, the train goes to the station's generic point, the station building. That's rough for a big station, where the platforms spread out, but the stop is short.

The same calculation on both sides

This calculation runs in two places. On the server, every train in circulation is recalculated every 30 seconds, once for all players. In the browser, the same formula keeps trains moving between two refreshes, so the animation stays continuous. And when a player captures a train, the server recalculates that train's position at the moment of the capture to validate it.

That imposes one constraint: the formula has to be exactly the same on both sides. If the browser animated trains with anything other than linear interpolation, an easing curve for example, the displayed position and the validated position would drift apart. The player would aim at one spot and the server would judge another. Same interpolation, same delay shifts, same handling of the platform case. The calculation exists in two languages, PHP on the server, JavaScript in the browser.

Then there's getting the tracks to the phone. A leg is a series of points, and at the scale of the whole network that's far too much data to send in one go. The browser only receives the geometry of the current leg and the ones after it, enough to last until the next refresh. Each track is sent once, encoded as a polyline, and trains refer to it by a key. When two trains follow each other on the same line, the geometry goes over the wire once.

The limits are known and accepted. I keep a single delay per train, updated at every fetch, every 2 minutes, and apply it to all its times. Time lost mid-route only shows up at the next fetch. When the geometry is missing, straight line, and the train no longer follows the rails exactly. And a delayed train stays on the map up to 3 hours after its theoretical arrival, then I drop it: past that point I don't know enough about the train to keep estimating.

In the end, geometry isn't the weak point of the system. With a few hundred metres of error, a position is still invisible at the scale of a 12 km capture radius. The weak point is time. A delay taken mid-leg doesn't exist yet for the system. The displayed train runs ahead of the real one, until the next fetch, where everything lines up again.