We built a speed limits system with OpenStreetMap. 10x cheaper.

When you work with vehicle fleets, one of the basic functions any platform needs is knowing whether a vehicle is exceeding the speed limit. Sounds simple. It’s not.

The market solution is to buy access to a commercial maps API that returns the speed limit for a GPS coordinate. The problem: when you’re processing thousands of vehicles reporting position every 5 seconds, those API calls eat your entire margin.

We had a real problem, zero budget for commercial APIs, and a two-week deadline. Here’s how we solved it.

The problem in detail

A fleet management platform we operated processed telemetry from thousands of vehicles in real time. Each vehicle sent its GPS position every 5 seconds. For each position we needed to know: what’s the speed limit on this road? Is the vehicle exceeding it?

The leading commercial API charged per query. We ran the numbers: at our volume, the monthly cost was higher than what we earned per client. It wasn’t a price negotiation problem. It was a business model incompatible with ours.

The decision: build

The team’s first reaction was to find a cheaper API. The second was to evaluate if we could cache results. Both were optimizations of the wrong approach. What we needed was to eliminate the dependency.

OpenStreetMap (OSM) has speed limit data. It’s not perfect. Coverage varies by country and area. But for our markets (Chile, Mexico, and the United States), coverage was reasonably good for the main highways and freeways where speeding actually mattered.

The plan: extract speed limit data from OSM, preprocess it, index it, and serve queries locally. No external API. No per-query cost.

The implementation

Phase 1: Quick proof of concept.

Before committing weeks of work, we built a JavaScript prototype to validate the approach was viable. We took a subset of OSM data, ran basic queries, and verified that speed limit information was sufficient for our markets. In one day we had the confidence it was worth continuing.

Phase 2: Preprocessing and indexing.

OSM data comes in PBF format (Protocol Buffer Format). The raw file for an entire country weighs gigabytes. But we didn’t need everything. Just the road segments with maxspeed tags and their component nodes.

We reorganized all PBF data into Redis, with metadata compressed at the bit level. Each node and segment stored with the minimum necessary bytes: coordinates, speed limit, road type. Preprocessed and indexed so queries would be as fast as possible.

For the geospatial index we used RocksDB as a point sub-index. This allowed extremely efficient geographic bounding box searches: given a GPS point, find all candidate road nodes within a given radius in microseconds.

Phase 3: Intelligent decision logic.

This is where the solution stopped being a simple “find the nearest point” and got interesting.

A vehicle reporting a GPS coordinate can be near several streets simultaneously. At an intersection, there might be 4 or 5 road segments with different speed limits. Choosing the correct one isn’t trivial.

We implemented application-layer processing with inference rules based on reasoning: where is the vehicle heading? Where is it coming from? Which points make the most sense in the trajectory context? If the car is coming down a highway at 100 km/h, it’s probably not on the parallel service road with a 40 km/h limit.

For the final decision on the correct point, we used a feature and weight model. Features included: distance to segment, angle between the vehicle’s movement vector and segment direction, current speed vs candidate speed limit, road type, and continuity with the previous segment. Each feature had calibrated weights we adjusted empirically with real fleet data.

It wasn’t machine learning in the classical sense. It was a scoring model where the combination of factors produced the best possible decision with available information. Simple, fast, and surprisingly accurate.

Phase 4: Fallbacks.

For cases where OSM lacked specific data, we implemented a hierarchical fallback system:

  1. Specific speed limit for the road segment
  2. Default speed limit for the road type (highway, route, urban street)
  3. Country default speed limit

It’s not perfect. On a residential street without OSM data, we assumed the country’s urban default (60 km/h in Chile, 30 km/h in school zones). But for the main use case (speeding alerts on highways and routes), accuracy was excellent.

The results

  • Cost per query: Effectively zero. The service ran on infrastructure we already had
  • Latency: P99 of 2ms (vs 150-300ms from the commercial API)
  • Coverage: ~85% direct match with OSM, the rest covered by reasonable fallbacks
  • Build time: 2 weeks, 2 engineers
  • Savings: The monthly cost of the commercial API would have been equivalent to a senior engineer’s salary. Every month.

The service has been running in production without significant changes for several years.

What we learned

1. Open source isn’t free, but it’s cheap. OSM data is free, but extracting, processing, and indexing it requires real engineering work. The difference is you do that work once. The commercial API charges you every time.

2. Intelligence doesn’t have to be AI. The feature and weight model we used to choose the correct segment wasn’t a deep learning model. It was well-thought-out business logic, calibrated with real data. Sometimes the most effective solution is a scoring model with clear rules, not a neural network.

3. Constraints force creativity. If we’d had budget for the API, we would have bought it. We never would have built something that ended up being a competitive advantage. In economics they call it “innovation by constraint”: resource scarcity forces solutions that abundance would never have motivated.

When to build vs buy?

It doesn’t always make sense to build your own solution. The rule we use:

  • Buy when the external solution cost is marginal relative to your revenue
  • Buy when the solution requires expertise you don’t have and don’t need to develop
  • Build when external dependency threatens your business model
  • Build when volume makes per-query cost unsustainable
  • Build when you can build something good enough in a reasonable timeframe

In our case, all three “build” criteria were met. The decision was obvious in retrospect. But at the time, saying “we’re going to build our own speed limits service” sounded like reinventing the wheel. Sometimes reinventing the wheel is exactly the right call.