import numpy as np import folium import matplotlib.pyplot as plt from folium.raster_layers import ImageOverlay import geopandas as gpd from shapely.geometry import Point from scipy.ndimage import gaussian_filter from matplotlib.colors import LinearSegmentedColormap import pandas as pd from sklearn.ensemble import RandomForestRegressor from scipy.ndimage import gaussian_filter from matplotlib.colors import LinearSegmentedColormap from branca.element import Template, MacroElement # ==== 1. Dữ liệu ban đầu ==== # for idx, pre in enumerate(timelamp): def plot_map(filtered_df): idx = 'pre' vn_mainland = gpd.read_file("map/gadm41_VNM_1.json") hoang_sa = gpd.read_file("map/gadm36_XSP_0.json") truong_sa = gpd.read_file("map/gadm36_XPI_0.json") # Gộp lại thành một GeoDataFrame vn_full = gpd.GeoDataFrame(pd.concat([vn_mainland, hoang_sa, truong_sa], ignore_index=True), crs='EPSG:4326') m = folium.Map(location=[14.0583, 108.2772], zoom_start=6, tiles='CartoDB Positron') X = filtered_df[['Kinh độ', 'Vĩ độ']] y = filtered_df['AQI_PM2.5'] model = RandomForestRegressor(n_estimators=100, random_state=42) model.fit(X, y) # === 2. Tạo lưới để dự đoán === lat_min, lat_max = X['Kinh độ'].min()-1, X['Kinh độ'].max()+1 lon_min, lon_max = X['Vĩ độ'].min()-1, X['Vĩ độ'].max()+1 grid_lat = np.linspace(lat_min, lat_max, 300) grid_lon = np.linspace(lon_min, lon_max, 300) grid_lon_mesh, grid_lat_mesh = np.meshgrid(grid_lon, grid_lat) grid_points = np.c_[grid_lat_mesh.ravel(), grid_lon_mesh.ravel()] grid_intensity = model.predict(grid_points).reshape(grid_lat_mesh.shape) grid_intensity = gaussian_filter(grid_intensity, sigma=2) vn_union = vn_full.union_all() # Nối tất cả vùng lại thành 1 polygon # Tạo mask: điểm nào ngoài biên VN thì đặt NaN mask = np.full(grid_intensity.shape, False) for i in range(grid_lat_mesh.shape[0]): for j in range(grid_lat_mesh.shape[1]): point = Point(grid_lon_mesh[i, j], grid_lat_mesh[i, j]) if not vn_union.contains(point): mask[i, j] = True grid_intensity_masked = np.ma.array(grid_intensity, mask=mask) plt.figure(figsize=(6, 6)) # plt.imshow(grid_intensity_masked, extent=(lon_min, lon_max, lat_min, lat_max), # origin='lower', cmap='hot', alpha=0.6) # Danh sách màu chuẩn AQI theo thứ tự tăng dần aqi_colors = [ '#00e400', # Green (0–50) '#ffff00', # Yellow (51–100) '#ff7e00', # Orange (101–150) '#ff0000', # Red (151–200) '#8f3f97', # Purple (201–300) '#7e0023' # Maroon (301–500) ] # Tạo colormap nội suy từ danh sách trên aqi_cmap = LinearSegmentedColormap.from_list("aqi_smooth", aqi_colors, N=100) plt.imshow( grid_intensity_masked, extent=(lon_min, lon_max, lat_min, lat_max), origin='lower', cmap=aqi_cmap, # Hoặc 'plasma', 'turbo', 'jet', 'YlOrRd' alpha=0.75, vmin=0, vmax=400 ) plt.axis('off') plt.savefig(f"images/heatmap_overlay {idx}.png", bbox_inches='tight', pad_inches=0, transparent=True) plt.close() # ==== 4. Tạo bản đồ và chèn ảnh ==== # Overlay ảnh lên đúng vùng địa lý ImageOverlay( image=f"images/heatmap_overlay {idx}.png", bounds=[[lat_min, lon_min], [lat_max, lon_max]], opacity=0.6, interactive=True, cross_origin=False ).add_to(m) # Đọc và thêm biên giới VN nếu có try: folium.GeoJson( vn_full, name='Biên giới', style_function=lambda x: { 'fillColor': 'none', 'color': 'black', 'weight': 1, 'dashArray': '5, 5' } ).add_to(m) except: print("Không tìm thấy tệp GeoJSON biên giới.") def get_folium_color(aqi): if aqi <= 50: return 'green' elif aqi <= 100: return 'beige' # gần giống yellow elif aqi <= 150: return 'orange' elif aqi <= 200: return 'red' elif aqi <= 300: return 'purple' else: return 'darkred' for name, lat, lon, aqi_value in zip(filtered_df["Tên"], filtered_df["Kinh độ"], filtered_df["Vĩ độ"], filtered_df["AQI_PM2.5"]): # aqi_value = intensity_values[list(coordinates.keys()).index(name)] fill_color = ( 'green' if aqi_value <= 50 else 'yellow' if aqi_value <= 100 else 'orange' if aqi_value <= 150 else 'red' if aqi_value <= 200 else 'purple' if aqi_value <= 300 else 'maroon' ) folium.Marker( location=(lat, lon), popup=f"{name}: AQI PM2.5 = {aqi_value:.1f}", icon=folium.Icon(color=get_folium_color(aqi_value), icon="info-sign") # icon bạn có thể đổi thành 'cloud', 'leaf', 'home'... ).add_to(m) folium.CircleMarker( location=(lat, lon), radius=6, popup=f"{name}: pm2.5 {aqi_value}", color='black', weight=0.5, fill=True, fill_color=fill_color, fill_opacity=0.8 ).add_to(m) folium.Marker( location=[16.5053, 111.9537], icon=folium.DivIcon( html='
Hoàng Sa
' ) ).add_to(m) folium.Marker( location=[9.9342, 114.3302], icon=folium.DivIcon( html='
Trường Sa
' ) ).add_to(m) folium.LayerControl().add_to(m) # m.save(f"heatmap_image_overlay{idx}.html") print("✅ Đã tạo bản đồ heatmap với overlay hình ảnh: heatmap_image_overlay.html") colorbar_template = """ {% macro html(this, kwargs) %}
0     50     100     150     200     300     500+
{% endmacro %} """ colorbar = MacroElement() colorbar._template = Template(colorbar_template) m.get_root().add_child(colorbar) m.save(f"heatmap_image_overlaycolorbar.html") m return m