Building Real-Time Fleet Tracking with HERE APIs
Fleet tracking at scale is a data pipeline problem, not a map problem. The map is the easy part. The hard part is ingesting millions of position updates per hour, validating them, keeping hot state low-latency, and rendering only what the user needs to see.
The Problem
A fleet of 10,000 vehicles reporting every 5 seconds produces 2,000 position updates per second. During shift changes, that rate can spike to 3–5×. Each update must be validated, persisted, and made queryable within milliseconds. Miss that window, and the map shows stale data.
The Architecture
A production fleet tracking system has three layers:
- Ingestion — Vehicles publish positions via MQTT into a position ingestion service.
- State — The service writes current positions to Redis and persists all positions to PostgreSQL.
- Presentation — A tile server reads from Redis and serves the frontend.
Position Ingestion
The ingestion layer is where most systems break. Three design decisions matter:
- Use MQTT, not HTTP. MQTT provides persistent connections, low overhead, and built-in QoS. Most fleet telematics devices support it natively. Raw UDP is faster but sacrifices delivery guarantees that compound at scale.
- Batch writes to Redis. Accumulate 50–100 positions before writing atomically. One write per position will saturate your connection pool under load.
- Implement backpressure. When Redis throughput falls behind ingestion rate, drop stale positions rather than accumulating unbounded lag.
// Batch ingestion in Rust
async fn ingest_positions(batch: Vec<PositionReport>, redis: &RedisClient) {
let mut pipe = redis.pipeline();
for report in batch {
let key = format!("vehicle:{}", report.vehicle_id);
pipe.set_ex(key, serialize(&report), 300); // 5 min TTL
}
pipe.exec_async().await?;
}
HERE Fleet Integration
HERE's Fleet API provides routing, traffic, and geocoding. It is not designed for high-frequency position updates. Use it for what it does well:
- Route calculation — Optimal paths from origin to destination with live traffic.
- Geocoding — Address to coordinate conversion for origin and destination resolution.
- Reverse geocoding — Coordinate to address conversion for delivery confirmation.
Frontend Performance
Three rules keep the frontend responsive at scale:
- Debounce position updates. Batch at 60 frames per second maximum. Do not re-render on every WebSocket message.
- Interpolate between positions. Smooth vehicle movement with linear interpolation to avoid visual stutter.
- Cull outside the viewport. Render only vehicles visible in the current map bounds.
function interpolatePosition(
prev: Position,
next: Position,
progress: number
): Position {
return {
lat: prev.lat + (next.lat - prev.lat) * progress,
lng: prev.lng + (next.lng - prev.lng) * progress,
heading: prev.heading + (next.heading - prev.heading) * progress,
};
}
Lessons from Production
After deploying fleet tracking for logistics companies across North America and Europe:
- GPS drift is real. Implement snap-to-road algorithms or minimum-movement thresholds that filter positions within 5 meters of the last known.
- Store all timestamps in UTC. A vehicle crossing from CET to EET reports timestamps differently. UTC eliminates ambiguity.
- Buffer for offline scenarios. Vehicles lose signal in tunnels and parking garages. Buffer positions on the device and replay on reconnect to preserve trip continuity.
Technology Selection
| Layer | Use | Avoid |
|---|---|---|
| HERE Fleet API | Routing, traffic, geocoding | Real-time position ingestion |
| Redis | Last-known positions, hot state | Long-term storage, analytics |
| PostgreSQL | Trip history, persistent storage | Real-time query serving |
| Mapbox GL | Interactive maps, custom styling | Server-side data processing |