import folium import os import time import random import pandas as pd from pathlib import Path from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager # Delhi center coordinates DELHI_LAT = 28.7041 DELHI_LON = 77.1025 # Base hotspots data with AQI values and weight ratios # Weight = how much this location deviates from average (>1 = higher, <1 = lower) BASE_HOTSPOTS = [ {"name": "Chandni Chowk", "lat": 28.656, "lon": 77.231, "base_aqi": 384, "weight": 1.2092}, {"name": "Nehru Nagar", "lat": 28.5697, "lon": 77.253, "base_aqi": 384, "weight": 1.2092}, {"name": "New Moti Bagh", "lat": 28.582, "lon": 77.1717, "base_aqi": 377, "weight": 1.1872}, {"name": "Shadipur", "lat": 28.6517, "lon": 77.1582, "base_aqi": 375, "weight": 1.1809}, {"name": "Wazirpur", "lat": 28.6967, "lon": 77.1658, "base_aqi": 354, "weight": 1.1148}, {"name": "Mundka", "lat": 28.6794, "lon": 77.0284, "base_aqi": 353, "weight": 1.1116}, {"name": "Punjabi Bagh", "lat": 28.673, "lon": 77.1374, "base_aqi": 349, "weight": 1.0990}, {"name": "RK Puram", "lat": 28.5505, "lon": 77.1849, "base_aqi": 342, "weight": 1.0770}, {"name": "Jahangirpuri", "lat": 28.716, "lon": 77.1829, "base_aqi": 338, "weight": 1.0644}, {"name": "Okhla Phase-2", "lat": 28.5365, "lon": 77.2803, "base_aqi": 337, "weight": 1.0612}, {"name": "Dwarka Sector-8", "lat": 28.5656, "lon": 77.067, "base_aqi": 336, "weight": 1.0581}, {"name": "Dhyanchand Stadium", "lat": 28.6125, "lon": 77.2372, "base_aqi": 335, "weight": 1.0549}, {"name": "Patparganj", "lat": 28.612, "lon": 77.292, "base_aqi": 334, "weight": 1.0518}, {"name": "Bawana", "lat": 28.8, "lon": 77.03, "base_aqi": 328, "weight": 1.0329}, {"name": "Sirifort", "lat": 28.5521, "lon": 77.2193, "base_aqi": 326, "weight": 1.0266}, {"name": "ITO", "lat": 28.6294, "lon": 77.241, "base_aqi": 324, "weight": 1.0203}, {"name": "Karni Singh Shooting Range", "lat": 28.4998, "lon": 77.2668, "base_aqi": 323, "weight": 1.0171}, {"name": "Sonia Vihar", "lat": 28.7074, "lon": 77.2599, "base_aqi": 317, "weight": 0.9982}, {"name": "JLN Stadium", "lat": 28.5828, "lon": 77.2344, "base_aqi": 314, "weight": 0.9888}, {"name": "Rohini", "lat": 28.7019, "lon": 77.0984, "base_aqi": 312, "weight": 0.9825}, {"name": "Narela", "lat": 28.85, "lon": 77.1, "base_aqi": 311, "weight": 0.9793}, {"name": "Mandir Marg", "lat": 28.6325, "lon": 77.1994, "base_aqi": 311, "weight": 0.9793}, {"name": "IGI Airport T3", "lat": 28.5562, "lon": 77.1, "base_aqi": 305, "weight": 0.9605}, {"name": "Vivek Vihar", "lat": 28.6635, "lon": 77.3152, "base_aqi": 303, "weight": 0.9542}, {"name": "Pusa IMD", "lat": 28.6335, "lon": 77.1651, "base_aqi": 298, "weight": 0.9384}, {"name": "Burari Crossing", "lat": 28.7592, "lon": 77.1938, "base_aqi": 294, "weight": 0.9258}, {"name": "Aurobindo Marg", "lat": 28.545, "lon": 77.205, "base_aqi": 287, "weight": 0.9038}, {"name": "Dilshad Garden (IHBAS)", "lat": 28.681, "lon": 77.305, "base_aqi": 283, "weight": 0.8912}, {"name": "Lodhi Road", "lat": 28.5921, "lon": 77.2284, "base_aqi": 282, "weight": 0.8880}, {"name": "NSIT-Dwarka", "lat": 28.6081, "lon": 77.0193, "base_aqi": 275, "weight": 0.8660}, {"name": "Alipur", "lat": 28.8, "lon": 77.15, "base_aqi": 273, "weight": 0.8597}, {"name": "Najafgarh", "lat": 28.6125, "lon": 76.983, "base_aqi": 262, "weight": 0.8250}, {"name": "CRRI-Mathura Road", "lat": 28.5518, "lon": 77.2752, "base_aqi": 252, "weight": 0.7936}, {"name": "DTU", "lat": 28.7501, "lon": 77.1177, "base_aqi": 219, "weight": 0.6896}, ] # Baseline average AQI (average of all base_aqi values) BASELINE_AVG_AQI = sum(h['base_aqi'] for h in BASE_HOTSPOTS) / len(BASE_HOTSPOTS) def get_emission_scaling_factor(year: int) -> float: """ Calculate the scaling factor for a forecast year relative to 2025 baseline. Returns the ratio of year's avg emissions to 2025's avg emissions. """ try: # Load forecast data forecast_path = Path(__file__).parent / "emission_forecast_3years_full.csv" if not forecast_path.exists(): print(f"Forecast file not found: {forecast_path}") return 1.0 df = pd.read_csv(forecast_path) df['Date'] = pd.to_datetime(df['Date']) df['Year'] = df['Date'].dt.year # Get baseline 2025 average baseline_2025 = df[df['Year'] == 2025]['Total_Emission'].mean() # Get selected year average year_avg = df[df['Year'] == year]['Total_Emission'].mean() if pd.isna(baseline_2025) or pd.isna(year_avg) or baseline_2025 == 0: print(f"Could not calculate scaling factor for year {year}") return 1.0 scaling_factor = year_avg / baseline_2025 print(f"Year {year}: avg emission = {year_avg:.2f}, baseline = {baseline_2025:.2f}, scaling = {scaling_factor:.4f}") return scaling_factor except Exception as e: print(f"Error calculating emission scaling factor: {e}") return 1.0 def add_delhi_boundary_to_map(m): """Helper to fetch and add Delhi boundary to a map.""" import requests try: geojson_url = "https://raw.githubusercontent.com/datameet/Municipal_Spatial_Data/master/Delhi/Delhi_Boundary.geojson" # Add Boundary Outline folium.GeoJson( geojson_url, name="Delhi Boundary", style_function=lambda x: { 'fillColor': 'none', 'color': 'black', # Black for visibility on light tiles 'weight': 3, 'dashArray': '5, 5', 'opacity': 0.6 } ).add_to(m) return True except Exception as e: print(f"Error adding boundary: {e}") return False def generate_heatmap_html(): """Generates a Folium map with Grid Heatmap (Clipped to Delhi).""" # Create base map m = folium.Map( location=[DELHI_LAT, DELHI_LON], zoom_start=10, control_scale=True ) # Heatmap Grid Generation import requests from shapely.geometry import shape, Point from shapely.ops import unary_union # Fetch Delhi Boundary for layout clipping & display # We fetch manually here for the shapely polygon, AND add the visual layer has_boundary = False delhi_polygon = None # Add visual boundary add_delhi_boundary_to_map(m) try: geojson_url = "https://raw.githubusercontent.com/datameet/Municipal_Spatial_Data/master/Delhi/Delhi_Boundary.geojson" resp = requests.get(geojson_url) data = resp.json() # Create a unified polygon for checking features = data.get('features', []) shapes = [shape(f['geometry']) for f in features] delhi_polygon = unary_union(shapes) has_boundary = True except Exception as e: print(f"Error processing boundary for clipping: {e}") # Grid config lat_min, lat_max = 28.40, 28.88 lon_min, lon_max = 76.85, 77.35 step = 0.015 # Approx 1.5km grid size def get_color(value): """Red-scale heatmap colors like the reference image.""" if value < 100: return "#fee5d9" # Very Light Pink if value < 200: return "#fcae91" # Light Pink/Orange if value < 300: return "#fb6a4a" # Salmon if value < 400: return "#de2d26" # Red return "#a50f15" # Dark Red/Brown lat = lat_min while lat < lat_max: lon = lon_min while lon < lon_max: # Check if center of grid is inside Delhi center_point = Point(lon + step/2, lat + step/2) if has_boundary and not delhi_polygon.contains(center_point): # Skip if outside lon += step continue # Generate simulated AQI/CO2 data with spatial coherence # Higher near center (Delhi) dist_center = ((lat - DELHI_LAT)**2 + (lon - DELHI_LON)**2)**0.5 # Base value + random noise - distance decay base_value = 450 - (dist_center * 800) val = base_value + random.randint(-50, 50) val = max(50, min(500, val)) # Clamp 50-500 color = get_color(val) # Draw grid cell folium.Rectangle( bounds=[[lat, lon], [lat + step, lon + step]], color=None, # No border fill=True, fill_color=color, fill_opacity=0.6, # Slightly transparent tooltip=f"Zone AQI: {int(val)}" ).add_to(m) lon += step lat += step return m.get_root().render() def generate_hotspots_html(): """Generates the original Hotspot Map with markers.""" m = folium.Map(location=[DELHI_LAT, DELHI_LON], zoom_start=11) # Add Visual Boundary add_delhi_boundary_to_map(m) # Original Hotspots Data hotspots = [ {"name": "Chandni Chowk", "lat": 28.656, "lon": 77.231, "aqi": 384}, {"name": "Nehru Nagar", "lat": 28.5697, "lon": 77.253, "aqi": 384}, {"name": "New Moti Bagh", "lat": 28.582, "lon": 77.1717, "aqi": 377}, {"name": "Shadipur", "lat": 28.6517, "lon": 77.1582, "aqi": 375}, {"name": "Wazirpur", "lat": 28.6967, "lon": 77.1658, "aqi": 354}, {"name": "Ashok Vihar", "lat": 28.6856, "lon": 77.178, "aqi": 231}, {"name": "Mundka", "lat": 28.6794, "lon": 77.0284, "aqi": 353}, {"name": "Punjabi Bagh", "lat": 28.673, "lon": 77.1374, "aqi": 349}, {"name": "RK Puram", "lat": 28.5505, "lon": 77.1849, "aqi": 342}, {"name": "Jahangirpuri", "lat": 28.716, "lon": 77.1829, "aqi": 338}, {"name": "Okhla Phase-2", "lat": 28.5365, "lon": 77.2803, "aqi": 337}, {"name": "Dwarka Sector-8", "lat": 28.5656, "lon": 77.067, "aqi": 336}, {"name": "Patparganj", "lat": 28.612, "lon": 77.292, "aqi": 334}, {"name": "Bawana", "lat": 28.8, "lon": 77.03, "aqi": 328}, {"name": "Sonia Vihar", "lat": 28.7074, "lon": 77.2599, "aqi": 317}, {"name": "Rohini", "lat": 28.7019, "lon": 77.0984, "aqi": 312}, {"name": "Narela", "lat": 28.85, "lon": 77.1, "aqi": 311}, {"name": "Mandir Marg", "lat": 28.6325, "lon": 77.1994, "aqi": 311}, {"name": "Vivek Vihar", "lat": 28.6635, "lon": 77.3152, "aqi": 303}, {"name": "Anand Vihar", "lat": 28.6508, "lon": 77.3152, "aqi": 335}, {"name": "Dhyanchand Stadium", "lat": 28.6125, "lon": 77.2372, "aqi": 335}, {"name": "Sirifort", "lat": 28.5521, "lon": 77.2193, "aqi": 326}, {"name": "Karni Singh Shooting Range", "lat": 28.4998, "lon": 77.2668, "aqi": 323}, {"name": "ITO", "lat": 28.6294, "lon": 77.241, "aqi": 324}, {"name": "JLN Stadium", "lat": 28.5828, "lon": 77.2344, "aqi": 314}, {"name": "IGI Airport T3", "lat": 28.5562, "lon": 77.1, "aqi": 305}, {"name": "Pusa IMD", "lat": 28.6335, "lon": 77.1651, "aqi": 298}, {"name": "Burari Crossing", "lat": 28.7592, "lon": 77.1938, "aqi": 294}, {"name": "Aurobindo Marg", "lat": 28.545, "lon": 77.205, "aqi": 287}, {"name": "Dilshad Garden (IHBAS)", "lat": 28.681, "lon": 77.305, "aqi": 283}, {"name": "Lodhi Road", "lat": 28.5921, "lon": 77.2284, "aqi": 282}, {"name": "NSIT-Dwarka", "lat": 28.6081, "lon": 77.0193, "aqi": 275}, {"name": "Alipur", "lat": 28.8, "lon": 77.15, "aqi": 273}, {"name": "Najafgarh", "lat": 28.6125, "lon": 76.983, "aqi": 262}, {"name": "CRRI-Mathura Road", "lat": 28.5518, "lon": 77.2752, "aqi": 252}, {"name": "DTU", "lat": 28.7501, "lon": 77.1177, "aqi": 219}, {"name": "DU North Campus", "lat": 28.69, "lon": 77.21, "aqi": 298}, {"name": "Ayanagar", "lat": 28.4809, "lon": 77.1255, "aqi": 342} ] for spot in hotspots: color = "gray" aqi = spot['aqi'] if isinstance(aqi, (int, float)): color = "green" if aqi > 100: color = "yellow" if aqi > 200: color = "orange" if aqi > 300: color = "red" if aqi > 400: color = "purple" folium.CircleMarker( location=[spot['lat'], spot['lon']], radius=15, popup=f"{spot['name']}: AQI {spot['aqi']}", color=color, fill=True, fill_color=color, fill_opacity=0.7 ).add_to(m) folium.Marker( location=[spot['lat'], spot['lon']], icon=folium.DivIcon(html=f'