toll-calculator / src /map_builder.py
vaisagan2020
Add complete Highway Toll Calculator β€” India & US
1f3eae2
Raw
History Blame Contribute Delete
6.32 kB
"""
Component 8 β€” Interactive Folium Map.
Draws:
β€’ Full route polyline (color-coded by country)
β€’ Source / destination markers with custom icons
β€’ Toll plaza markers (green/orange/red by cost) with rich popups
showing plaza name, highway, vehicle rate, electronic vs cash split,
and data source.
"""
from typing import Dict, Any, List
import folium
from folium.plugins import Fullscreen
# Country theme colors
_THEMES = {
"India": {"route": "#FF9933", "accent": "#138808", "bg": "#FFFFFF"},
"United States": {"route": "#B22234", "accent": "#3C3B6E", "bg": "#FFFFFF"},
}
# Plaza cost thresholds for color coding (in currency units)
_LOW_THRESHOLD = {
"India": 50,
"United States": 3,
}
_MID_THRESHOLD = {
"India": 150,
"United States": 10,
}
def build_map(result: Dict[str, Any]) -> folium.Map:
"""
Build and return a Folium map from a calculator result dict.
"""
country = result["country"]
route = result["route"]
plazas: List[Dict] = result["plazas"]
theme = _THEMES.get(country, _THEMES["United States"])
currency_symbol = "β‚Ή" if country == "India" else "$"
electronic_label = "FASTag" if country == "India" else "E-ZPass"
coords = route["coords"]
mid_idx = len(coords) // 2
center = coords[mid_idx]
m = folium.Map(
location=list(center),
zoom_start=7,
tiles="CartoDB positron",
prefer_canvas=True,
)
Fullscreen(position="topright").add_to(m)
# ── Route polyline ────────────────────────────────────────────────────────
folium.PolyLine(
locations=[[lat, lon] for lat, lon in coords],
color=theme["route"],
weight=5,
opacity=0.85,
tooltip=f"{route['source']} β†’ {route['destination']}",
).add_to(m)
# ── Source marker ────────────────────────────────────────────────────────
src = route["source_coords"]
folium.Marker(
location=[src[0], src[1]],
popup=folium.Popup(
f"<b>Start:</b> {route['source']}<br>"
f"<b>Distance:</b> {route['distance_display']}<br>"
f"<b>Travel time:</b> {route['duration_display']}",
max_width=220,
),
icon=folium.Icon(
color="green",
icon="play",
prefix="fa",
),
tooltip=f"Start: {route['source']}",
).add_to(m)
# ── Destination marker ────────────────────────────────────────────────────
dst = route["destination_coords"]
folium.Marker(
location=[dst[0], dst[1]],
popup=folium.Popup(
f"<b>End:</b> {route['destination']}<br>"
f"<b>Total toll:</b> {currency_symbol}{result['total_toll']:.2f}",
max_width=220,
),
icon=folium.Icon(
color="red",
icon="flag-checkered",
prefix="fa",
),
tooltip=f"End: {route['destination']}",
).add_to(m)
# ── Toll plaza markers ────────────────────────────────────────────────────
low_t = _LOW_THRESHOLD.get(country, 5)
mid_t = _MID_THRESHOLD.get(country, 10)
for plaza in plazas:
lat, lon = plaza["latitude"], plaza["longitude"]
if lat == 0 and lon == 0:
continue
rate = plaza["rate"]
e_rate = plaza["electronic_rate"]
c_rate = plaza["cash_rate"]
unavailable = plaza.get("rate_unavailable", False)
if unavailable:
marker_color = "gray"
elif rate <= low_t:
marker_color = "green"
elif rate <= mid_t:
marker_color = "orange"
else:
marker_color = "red"
icon_char = "β‚Ή" if country == "India" else "$"
if unavailable:
rate_html = "<i>Rate unavailable</i>"
elif e_rate != c_rate:
rate_html = (
f"<b>{electronic_label}:</b> {icon_char}{e_rate:.2f}<br>"
f"<b>Cash:</b> {icon_char}{c_rate:.2f}"
)
else:
rate_html = f"<b>Toll:</b> {icon_char}{rate:.2f}"
highway_html = f"<b>Highway:</b> {plaza['highway_number']}<br>" if plaza["highway_number"] else ""
source_html = f"<br><small style='color:#888'>Source: {plaza['source']}</small>"
popup_html = f"""
<div style="font-family:sans-serif;font-size:13px;min-width:180px">
<b style="font-size:14px">{plaza['plaza_name']}</b><br>
{highway_html}
{rate_html}
{source_html}
</div>"""
folium.Marker(
location=[lat, lon],
popup=folium.Popup(popup_html, max_width=260),
icon=folium.DivIcon(
html=_plaza_icon_html(icon_char, marker_color),
icon_size=(32, 32),
icon_anchor=(16, 16),
),
tooltip=f"{plaza['plaza_name']} β€” {icon_char}{rate:.2f}",
).add_to(m)
# ── Fit map to route bounds ────────────────────────────────────────────────
if coords:
lats = [c[0] for c in coords]
lons = [c[1] for c in coords]
m.fit_bounds([[min(lats), min(lons)], [max(lats), max(lons)]])
return m
def _plaza_icon_html(symbol: str, color: str) -> str:
color_map = {
"green": "#27ae60",
"orange": "#e67e22",
"red": "#e74c3c",
"gray": "#95a5a6",
}
bg = color_map.get(color, "#95a5a6")
return (
f'<div style="'
f'background:{bg};'
f'color:white;'
f'border-radius:50%;'
f'width:28px;height:28px;'
f'display:flex;align-items:center;justify-content:center;'
f'font-size:14px;font-weight:bold;'
f'border:2px solid white;'
f'box-shadow:0 2px 4px rgba(0,0,0,0.4);'
f'">{symbol}</div>'
)