Compute and visualize:
The shortest driving distance from every location on German motorways
to the nearest HPC charging station (≥150 kW)
Final output:
- Vector tiles (
.mbtiles) - Smooth, zoomable motorway distance map (like your screenshot)
- Hoverable segments with distance info
We split the system into three reusable layers:
- Builds the routable graph from OSM
- Provides snapping + routing
- Used by all downstream steps
- Runs multi-source shortest path from all HPCs
- Produces a distance field over the graph
- Extracts motorway edges
- Converts to colored segments
- Exports MBTiles
Create a stable, reusable routing graph + server that is used by all later steps.
This is your core artifact:
- built once
- reused many times
graphhopper:
datareader.file: /data/germany-latest.osm.pbf
graph.location: /data/graph-cache-germany-car
graph.encoded_values: road_class,road_class_link,road_environment,road_access,max_speed,surface
graph.flag_encoders: car|turn_costs=true
profiles:
- name: car
vehicle: car
weighting: fastest
turn_costs: true
u_turn_costs: 60
profiles_ch:
- profile: car
server:
application_connectors:
- type: http
port: 8989
admin_connectors:
- type: http
port: 8990
logging:
level: INFOjava -jar graphhopper-web-<version>.jar server graphhopper-hpc.ymlOn first run:
- imports OSM
- builds routing graph
- stores at
/data/graph-cache-germany-car
On subsequent runs:
- no re-import happens
- graph is reused from disk
- server starts instantly
You now have:
- Graph cache (disk)
- Local routing server (
localhost:8989)
This is your foundation.
Instead of:
motorway point → all chargers
We do:
all chargers → entire graph (once)
This is a multi-source shortest path problem.
id,lat,lon,power_kw
Filter:
power_kw >= 150
This is take from the bnetza CSV in this project. Make sure to deduplicate chargers that are almost at the same position (within 50m) after filtering the power
For each charger:
- snap to GraphHopper graph
- get:
- snapped edge
- snapped coordinate
Initialize:
for each snapped HPC:
distance = 0
push into priority queue
Run Dijkstra over full car graph.
For every node:
distance_to_nearest_hpc[node]
Optional:
nearest_hpc_id[node]
Computation runs on:
- full road graph (not motorway-only)
Because routes include:
- motorway
- exits
- local roads
Iterate all edges:
Keep only:
road_class == MOTORWAY
For edge:
A ----(length L)---- B
We know:
dA = distance(A)
dB = distance(B)
dist(s) = min(
dA + s,
dB + (L - s)
)
Split each motorway edge into small segments:
- recommended: 250 m (maybe?!)
For each segment:
- compute midpoint
- assign distance --> check if this is meaningful...
Each segment:
LineString
distance_m
distance_km
edge_id
(optional) nearest_hpc_id
Use one of:
- GeoJSON (simple)
- FlatGeobuf (fast + compact)
- GeoPackage
{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [...] },
"properties": {
"distance_km": 7.4,
"edge_id": 123456
}
}- smooth zooming
- small size
- fast rendering
- supports styling -> (how is smooth coloring done?)
tippecanoe \
-o motorway_distance.mbtiles \
-l motorway_distance \
-zg \
--drop-densest-as-needed \
motorway.geojsonCan be activated with a checkbox and should be a geojson format because otherwise grouping looks bad in the map. Add second layer:
hpc_stations
Use:
- MapLibre GL JS
Color by:
distance_km
Example:
- 0–5 → green
- 5–10 → yellow-green
- 10–15 → yellow
- 15–20 → orange
- 20+ → red
On hover:
- show distance
- show nearest HPC (optional)
- configure GraphHopper
- import OSM
- create graph cache
- load graph
- snap HPCs
- run multi-source Dijkstra
- compute node distances
- filter motorway edges
- split into segments
- assign distance values
- export GeoJSON
- convert to MBTiles
- load tiles in map
- style + interact
Sampling is only for rendering.
Distance field = reusable dataset
Motorway-only graph is insufficient
Always render line segments, not point clouds
Efficient + scalable + frontend-friendly
| Parameter | Value |
|---|---|
| Segment length | 250 m |
| Profile | car |
| Weighting | fastest |
| Charger threshold | ≥150 kW |
- store nearest HPC id per segment
- compute travel time instead of distance
- filter by charger operator/network
- incremental updates
- motorway-only reduced graph optimization
You end up with:
- one reusable GraphHopper graph/server
- one computed distance field
- one MBTiles file
→ plug directly into your map UI
→ smooth zoom
→ consistent coloring
→ no sampling artifacts