
# I Built a Real-Time Public Transport Map for the Netherlands — Here’s What I Learned
What started as a simple idea —
“What if I could see public transport across the Netherlands moving on one live map?”
— quickly turned into a much bigger engineering project.
I’ve been building OVspot, a real-time public transport map that combines trains, buses, trams, ferries, stops, delays, journey progress and estimated positions in a single interface.
You can try it here:
And building it taught me that “show vehicles on a map” is definitely not as simple as it sounds.
The idea
Most public transport apps focus on planning a journey.
OVspot takes a different approach.
Instead of asking:
“How do I get from A to B?”
OVspot lets you explore what is actually happening on the network.
You can open the map and see:
- trains
- buses
- trams
- ferries
- stations and stops
- destinations
- delays
- vehicle journeys
- upcoming stops
- estimated and live positions
The goal is to make the Dutch public transport network feel visible.
Screenshot suggestion #1
Use a large desktop screenshot here showing the Netherlands with many vehicles visible.
Caption:
OVspot showing live and estimated public transport vehicles across the Netherlands.
The hard part: not every vehicle has GPS
One of the biggest challenges is that not all operators provide vehicle positions in the same way.
Some vehicles have real-time positioning data.
Others only have timetable information.
If I only displayed vehicles with GPS, large parts of the network would simply disappear.
So OVspot also calculates estimated positions.
For those trips, it uses GTFS data such as:
tripsstop_times- route shapes
shape_dist_traveled- departure and arrival times
The basic idea is:
scheduled trip progress
↓
distance travelled along route
↓
interpolate position on GTFS shape
↓
estimated lat/lon
This allows OVspot to place a vehicle approximately where it should be according to the timetable.
Ferries were a good example
I first used this technique for the GVB ferries in Amsterdam.
Later, I realized the same system could be used for ferry operators around the Dutch Wadden Islands.
OVspot can now estimate positions for:
- TESO — Den Helder ↔ Texel
- Rederij Doeksen — Harlingen ↔ Terschelling / Vlieland
- Wagenborg — Ameland and Schiermonnikoog services
These operators have GTFS route shapes and distance information, so their scheduled progress can be projected onto the actual ferry route.
The important part is that OVspot does not present these positions as GPS.
They are explicitly treated as estimated positions.
Screenshot suggestion #2
Zoom in on Texel, Terschelling or Ameland with a ferry visible.
Caption:
Ferry positions can be estimated from GTFS shapes when no live GPS position is available.
“Live” data has different levels of quality
Something I underestimated at first was how important data confidence is.
A position can come from:
- live GPS
- real-time journey updates
- timetable interpolation
- cached data after a feed outage
Those are very different things.
So OVspot distinguishes between states such as:
Live
The vehicle has recent real-time information.
Estimated
Its position has been calculated from available journey data.
Timetable
There is no real-time information available.
This sounds like a small UI detail, but I think it is essential.
A map should not imply more certainty than the underlying data actually provides.
Thousands of moving vehicles are expensive
The next challenge was performance.
Rendering 20 markers on a map is easy.
Rendering hundreds or thousands of moving vehicles with labels, interaction and continuous updates is not.
At one point I experimented with regular DOM-based markers.
That worked fine at low numbers.
At high vehicle counts, it absolutely did not.
I ended up working on things like:
- canvas-based rendering
- zoom-dependent labels
- adaptive detail levels
- viewport filtering
- limiting DOM work
- avoiding unnecessary rerenders
- reducing expensive station labels
- measuring frame rate during pan and zoom
The map now deliberately changes how much detail it draws depending on the zoom level and current load.
One of the biggest lessons:
Performance is not something you optimize after building a map like this. It becomes part of the product design.
Screenshot suggestion #3
Use a screenshot of a dense area such as Amsterdam, Rotterdam or Utrecht.
Caption:
Dense urban areas can contain hundreds of visible vehicles, making rendering strategy important.
The popup became surprisingly complicated
Clicking on a vehicle originally opened a tiny information box.
That slowly evolved into something much more detailed.
The current vehicle popup can include:
- line
- destination
- carrier
- journey number
- delay
- data quality
- previous stop
- next stop
- complete journey progression
- estimated arrival
- route display
Even the wording required thought.
For example, I originally displayed:
“Nu hier” — “Here now”
next to the most recent stop.
But that was misleading.
The stop might simply be the last known point in the journey.
The vehicle could already be hundreds of meters away.
So the UI now only says “Nu hier” when the vehicle is actually:
- within about 50 meters of the stop
- and almost stationary
Otherwise that stop is shown as:
“Gepasseerd” — “Passed”
That is a tiny change, but it makes the interface much more truthful.
Screenshot suggestion #4
Show a vehicle popup with:
- journey timeline
- next stop
- delay
- Live / Estimated badge
This is probably the most useful screenshot for developers because it immediately shows that OVspot is more than just map markers.
Public transport data has a lot of edge cases
A huge part of the work happens before anything reaches the map.
Things I’ve encountered include:
- missing route shapes
- missing trip IDs
- stale feeds
- duplicate station names
- different operator naming conventions
- multiple platforms
- parent stations
- cross-border services
- vehicles outside their expected route
- impossible coordinates
- deadhead trips that passengers should never see
- different line numbering systems
- services without GPS
- timetable changes during the day
The frontend is probably the most visible part of OVspot.
But a lot of the project is really about turning inconsistent transport data into something predictable.
Detecting bad vehicle data
Another feature I’ve been working on is detecting suspicious positions.
For example:
- coordinates at
0,0 - vehicles outside a realistic geographic area
- impossible speeds
- a bus hundreds of meters away from its own route
- stale vehicle updates
Instead of blindly trusting every feed message, OVspot tries to validate the data first.
That is especially important in a live map because one broken coordinate can suddenly put a bus somewhere in Africa.
Yes, that happened.
Search also became more complicated than expected
OVspot search now needs to understand several completely different things:
- vehicle numbers
- stations
- stops
- line numbers
- journey numbers
- vehicle blocks / duties
The useful behavior is not just returning results.
When you click one, the map should immediately:
- find the right object
- move to it
- zoom to a useful level
- open the relevant popup
Those tiny interaction details make a huge difference to how usable a live map feels.
What I learned
A few things this project has taught me:
1. Real-time data is rarely simply “live”
You need to understand how old it is, where it came from and how reliable it is.
2. GTFS is much more powerful than just departure times
Shapes and shape_dist_traveled make surprisingly good position estimation possible.
3. Map performance becomes a systems problem
Backend payload size, update frequency, rendering strategy and UI complexity all affect each other.
4. Small UX wording matters
“Here now” versus “passed” can completely change how users interpret the data.
5. Edge cases are the project
The normal case is easy.
The weird cases are where most of the engineering time goes.
What’s next?
There is still plenty I want to improve:
- better position estimation
- more international trains
- improved ferry data
- better station pages
- more real-time quality indicators
- stronger anomaly detection
- faster mobile performance
- richer vehicle journey information
OVspot is still evolving, and I’m finding new edge cases almost every day.
If you work with:
- GTFS
- GTFS-Realtime
- public transport APIs
- mapping
- Leaflet
- real-time systems
- data visualization
I’d be very interested in your feedback.
You can try it here:
What would you improve first?
Top comments (0)