DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Bringing Graph Analytics to Snowflake With Neo4j
  • 3D Air Quality Maps With Neo4j, Python, and R
  • Running Sentiment Analysis Inside Neo4j With a Java Plugin
  • Real-Time Supply Chain Event Streaming With Kafka and Neo4j

Trending

  • Databricks Lakebase: Give Your Agent a Branch, Not Your Production Database
  • Prevent Duplicate API Calls With Idempotency: Patterns That Work
  • How to Test GET API Requests With Playwright TypeScript
  • MCP for Enterprise Tasks: Making the Rare Frequent Enough to Master
  1. DZone
  2. Data Engineering
  3. Databases
  4. Real-Time Vehicle Tracking With Neo4j, Databricks Lakebase, and OpenStreetMap

Real-Time Vehicle Tracking With Neo4j, Databricks Lakebase, and OpenStreetMap

Build a real-time fleet operations dashboard using Neo4j Aura for the road network graph, Lakebase for live vehicle positions, and Lakehouse for historical analytics.

By 
Akmal Chaudhri user avatar
Akmal Chaudhri
DZone Core CORE ·
Sep. 15, 26 · Analysis
Likes (0)
Comment
Save
Tweet
Share
146 Views

Join the DZone community and get the full member experience.

Join For Free

Most vehicle tracking systems ask one database to do everything. For example, store the road network, query it with recursive CTEs, write live positions to the same database, run analytics on the same tables, and so on. It works until the graph queries slow down, high-frequency writes start competing with reads, and analytics queries time out.

This article shows a different approach: three databases, each doing what it's genuinely good at. Neo4j Aura for the road network graph, Databricks Lakebase for live vehicle positions, and Databricks Lakehouse for historical analytics. Ten simulated vehicles move around a real city following real road connections loaded from OpenStreetMap. Two Streamlit dashboards show live positions and analytics. The whole system is driven by a single YAML configuration file, so switching from London to San Francisco or Singapore means changing a single file and rerunning five notebooks.

The full source code is available on GitHub.

The Three-System Architecture

The architecture has three distinct layers:

  • Neo4j Aura holds the road network — intersections, road segments, zone topology, and shortest paths. It answers graph questions that a relational database may handle awkwardly.
  • Databricks Lakebase holds the live operational data — vehicle positions written every two seconds by a simulator, vehicle statuses, and trip records. It's a fully managed Postgres database inside Databricks, handling OLTP workloads with standard psycopg2 connectivity.
  • Databricks Lakehouse holds the analytical history — position data synced from Lakebase into a Delta table, aggregated by zone and road segment.

None of these systems knows about the others. The intelligence sits in the application layer — the simulator, the Streamlit dashboards and the analytics notebook — which orchestrates queries across all three and combines the results.

The Road Network in Aura

We'll use OSMnx to download the drivable road network for the London Borough of Merton from OpenStreetMap and load it into Aura. The graph model is straightforward:

Cypher
 
(:Intersection {node_id, lat, lon, street_count, location})
    -[:ROAD {osmid, name, highway, maxspeed, oneway, length_m}]->
(:Intersection)


Merton's road network produces thousands of intersection nodes and thousands of directed road relationships. A POINT INDEX on the location property enables fast nearest-neighbor lookups -- finding the intersection closest to any GPS coordinate runs in milliseconds.

The reason for Aura is simple: the road network is a graph and graph queries are where Aura excels. Finding the shortest path between two zones is a single Cypher function call:

Cypher
 
MATCH path = shortestPath((start)-[:ROAD*..300]->(end))
RETURN length(path) AS hops


The equivalent in SQL requires a recursive CTE that grows in complexity with every additional hop. For zone reachability queries — "which zones can a vehicle reach within two hops?" — the difference is even more pronounced.

We also define five logical zones as bounding boxes within the borough and store them as Zone nodes with ADJACENT_TO relationships. This gives us a zone adjacency graph that the simulator uses for routing decisions.

The Simulator

The simulator loads the entire road graph from Aura into memory at startup — one query, one dictionary, no further Aura calls during the simulation loop. It then places ten vehicles at their home intersections and moves each one along Breadth-First Search (BFS)-computed routes.

Vehicles don't route randomly. They have a home zone and a 70% chance of staying in or near it. The remaining 30% of the time, they cross into any zone in the borough, producing occasional longer cross-city runs. Every two seconds, each vehicle writes its current coordinates to Lakebase:

Python
 
cursor.execute("""
    INSERT INTO vehicle_positions
        (vehicle_id, lat, lon, speed_kmh, current_zone)
    VALUES (%s, %s, %s, %s, %s)
""", (vehicle_id, lat, lon, speed_kmh, current_zone))


The simulator runs as a background subprocess launched from a Jupyter notebook, continuing independently while the Streamlit dashboards are open.

The Live Vehicle Tracker

The vehicle tracker (app.py) refreshes every three seconds and shows three pydeck layers on a CARTO basemap:

  • Vehicle icons – one car icon per vehicle at its current GPS position
  • Trail lines – each vehicle's last 20 positions, colored by home zone
  • Shortest path – a black line showing the road-network shortest path between any two selected zones, computed on demand from Aura

Figure 1 shows vehicles moving on the Merton map with trail lines and a shortest path highlighted between two zones.

Figure 1. Streamlit Vehicle Tracker

Figure 1. Streamlit Vehicle Tracker.


The sidebar shows a bar chart of zone activity over the last 10 minutes and a nearest-driver lookup -- given a zone, which vehicle is currently closest to it? The haversine distance calculation runs against the latest position of every vehicle, using zone center coordinates that map to real road intersections.

The Analytics Dashboard

The analytics dashboard (analytics_app.py) connects to all three systems simultaneously. Every 30 seconds, it syncs new position records from Lakebase into a Lakehouse Delta table and runs two analytical queries.

Figure 2 shows an analytics dashboard with zone activity over time across all five zones.

Figure 2. Analytics Dashboard

Figure 2. Analytics Dashboard.


The chart on the left-hand side shows position update counts per zone per minute over the last hour — a live view of which parts of the city are busiest:

SQL
 
SELECT current_zone AS zone,
       DATE_TRUNC('minute', recorded_at) AS minute,
       COUNT(*) AS updates
FROM vehicle_positions_delta
WHERE current_zone IS NOT NULL
GROUP BY current_zone, DATE_TRUNC('minute', recorded_at)
ORDER BY minute, zone


The chart on the right-hand side is the architectural highlight: a cross-system join that answers "which named roads carry the most vehicle traffic?" Lakebase has the position records (latitude, longitude, per vehicle per tick). Aura has the road names (what named road each intersection belongs to). Neither system alone can answer the question.

The join runs in Python using pandas. Road names and coordinates are loaded from Aura once at startup and cached. Position coordinates come from Lakebase via the Lakehouse Delta table on each refresh. Coordinates are rounded to three decimal places (~100m precision) and joined:

Python
 
joined = pos_df.merge(
    road_df[["road_name", "highway", "lat_r", "lon_r"]],
    on=["lat_r", "lon_r"],
    how="inner"
)


Primary roads dominate because BFS routing naturally follows main roads when finding shortest paths.

The YAML Configuration System

Every city-specific value lives in a single config.yaml file which contains zone definitions, vehicle assignments, map coordinates, and the OpenStreetMap place name.

YAML
 
city:
  name: "London Borough of Merton"
  osmnx_place: "London Borough of Merton, UK"
  network_type: "drive"
  map_lat: 51.410
  map_lon: -0.188
  map_zoom: 12


Switching cities means copying a different config file and re-running five notebooks. Three example config files are included: Merton (London), San Francisco, and Singapore. For cities where OSMnx's place name geocoding doesn't produce a usable polygon boundary, a pyrosm-based approach clips a Geofabrik regional file to a bounding box instead. The pre-clipped files for San Francisco and Singapore are included in the GitHub repo, so you can run those configs without any additional data preparation.

A companion config_validator.py validates the file on load and raises clear errors if anything is missing or malformed.

Why Three Systems?

The answer is that each system does something the others can't do efficiently.

Neo4j Aura handles graph traversals — shortest paths, multi-hop reachability, nearest-node spatial lookups. These are awkward in SQL and natural in Cypher.

Databricks Lakebase handles high-frequency OLTP writes — hundreds of inserts per minute, sustained, with foreign key constraints and BIGSERIAL auto-increment.

Databricks Lakehouse handles analytical aggregations over historical data — counting position records by zone and minute, joining across large datasets. Columnar storage and parallel execution make this fast.

The three-system architecture isn't complexity for its own sake. Each system earns its place by doing something the others would handle poorly.

The Free Online Book

The full system — all notebooks, both Streamlit apps, the YAML config system and seven chapters of detailed explanation — is available as a free online book.

The book covers the road network loading and data cleaning, zone and adjacency graph setup, Lakebase table design, the BFS simulator, both Streamlit dashboards, the analytics notebook, and all the gotchas and lessons learned. The code is on GitHub under Apache 2.0. The pre-clipped OSM data files are available under the Open Database License (ODbL).

Summary

We've built a real-time fleet operations dashboard using three database systems, each doing what it does best: Neo4j Aura for road network graph queries and shortest path computation, Databricks Lakebase for high-frequency vehicle position writes, and Databricks Lakehouse for historical analytics over Delta tables. The interesting engineering is in the joins that cross system boundaries — finding the nearest driver uses Aura's spatial index, routing vehicles uses BFS over an in-memory graph loaded from Aura, and identifying the busiest named roads joins position data with road names via pandas. A YAML configuration file drives the entire system, making it straightforward to point the same codebase at a different city. The architecture demonstrates that a multi-database approach isn't inherently complex — it becomes simpler when each system has a clear, non-overlapping role.

The full source code is available on GitHub.

Neo4j OpenStreetMap

Opinions expressed by DZone contributors are their own.

Related

  • Bringing Graph Analytics to Snowflake With Neo4j
  • 3D Air Quality Maps With Neo4j, Python, and R
  • Running Sentiment Analysis Inside Neo4j With a Java Plugin
  • Real-Time Supply Chain Event Streaming With Kafka and Neo4j

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook