""" 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"Start: {route['source']}
" f"Distance: {route['distance_display']}
" f"Travel time: {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"End: {route['destination']}
" f"Total toll: {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 = "Rate unavailable" elif e_rate != c_rate: rate_html = ( f"{electronic_label}: {icon_char}{e_rate:.2f}
" f"Cash: {icon_char}{c_rate:.2f}" ) else: rate_html = f"Toll: {icon_char}{rate:.2f}" highway_html = f"Highway: {plaza['highway_number']}
" if plaza["highway_number"] else "" source_html = f"
Source: {plaza['source']}" popup_html = f"""
{plaza['plaza_name']}
{highway_html} {rate_html} {source_html}
""" 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'
{symbol}
' )