|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import requests |
|
|
from bs4 import BeautifulSoup |
|
|
import folium |
|
|
from streamlit_folium import folium_static |
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
from datetime import datetime, timedelta |
|
|
import json |
|
|
from io import StringIO |
|
|
import streamlit.components.v1 as components |
|
|
import base64 |
|
|
from sklearn.ensemble import RandomForestRegressor |
|
|
from prophet import Prophet |
|
|
import tensorflow as tf |
|
|
from xgboost import XGBRegressor |
|
|
import seaborn as sns |
|
|
|
|
|
|
|
|
st.set_page_config(layout="wide", page_title="Pakistan Climate & Disaster Monitor", page_icon="π") |
|
|
|
|
|
class DataCollector: |
|
|
def __init__(self): |
|
|
self.cities = { |
|
|
'Islamabad': {'lat': 33.7294, 'lon': 73.0931}, |
|
|
'Karachi': {'lat': 24.8607, 'lon': 67.0011}, |
|
|
'Lahore': {'lat': 31.5204, 'lon': 74.3587}, |
|
|
'Peshawar': {'lat': 34.0151, 'lon': 71.5249}, |
|
|
'Quetta': {'lat': 30.1798, 'lon': 66.9750}, |
|
|
'Multan': {'lat': 30.1575, 'lon': 71.5249}, |
|
|
'Faisalabad': {'lat': 31.4504, 'lon': 73.1350}, |
|
|
'Rawalpindi': {'lat': 33.6007, 'lon': 73.0679}, |
|
|
'Gwadar': {'lat': 25.1216, 'lon': 62.3254}, |
|
|
'Hyderabad': {'lat': 25.3960, 'lon': 68.3578} |
|
|
} |
|
|
|
|
|
def fetch_weather_data(self): |
|
|
"""Fetch weather data from OpenMeteo""" |
|
|
weather_data = [] |
|
|
for city, coords in self.cities.items(): |
|
|
try: |
|
|
url = f"https://api.open-meteo.com/v1/forecast?latitude={coords['lat']}&longitude={coords['lon']}&hourly=temperature_2m,relativehumidity_2m,precipitation,windspeed_10m&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&timezone=auto&past_days=7" |
|
|
response = requests.get(url) |
|
|
data = response.json() |
|
|
|
|
|
|
|
|
hourly_df = pd.DataFrame({ |
|
|
'datetime': pd.to_datetime(data['hourly']['time']), |
|
|
'temperature': data['hourly']['temperature_2m'], |
|
|
'humidity': data['hourly']['relativehumidity_2m'], |
|
|
'precipitation': data['hourly']['precipitation'], |
|
|
'wind_speed': data['hourly']['windspeed_10m'] |
|
|
}) |
|
|
|
|
|
|
|
|
daily_df = pd.DataFrame({ |
|
|
'date': pd.to_datetime(data['daily']['time']), |
|
|
'temp_max': data['daily']['temperature_2m_max'], |
|
|
'temp_min': data['daily']['temperature_2m_min'], |
|
|
'precipitation_sum': data['daily']['precipitation_sum'] |
|
|
}) |
|
|
|
|
|
weather_data.append({ |
|
|
'city': city, |
|
|
'hourly': hourly_df, |
|
|
'daily': daily_df, |
|
|
'coords': coords |
|
|
}) |
|
|
except Exception as e: |
|
|
st.error(f"Error fetching weather data for {city}: {e}") |
|
|
continue |
|
|
|
|
|
return weather_data if weather_data else None |
|
|
|
|
|
def fetch_usgs_earthquake_data(self): |
|
|
"""Fetch earthquake data from USGS website""" |
|
|
try: |
|
|
|
|
|
url = "https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_month.geojson" |
|
|
response = requests.get(url) |
|
|
data = response.json() |
|
|
|
|
|
|
|
|
pakistan_data = { |
|
|
"type": "FeatureCollection", |
|
|
"features": [ |
|
|
feature for feature in data["features"] |
|
|
if 60.878 <= feature["geometry"]["coordinates"][0] <= 77.840 |
|
|
and 23.692 <= feature["geometry"]["coordinates"][1] <= 37.097 |
|
|
] |
|
|
} |
|
|
return pakistan_data |
|
|
except Exception as e: |
|
|
st.error(f"Error fetching earthquake data: {e}") |
|
|
return None |
|
|
|
|
|
def fetch_air_quality_data(self): |
|
|
"""Fetch air quality data from OpenMeteo""" |
|
|
aqi_data = [] |
|
|
for city, coords in self.cities.items(): |
|
|
try: |
|
|
url = f"https://air-quality-api.open-meteo.com/v1/air-quality?latitude={coords['lat']}&longitude={coords['lon']}&hourly=pm10,pm2_5,carbon_monoxide,nitrogen_dioxide,ozone&timezone=auto&past_days=7" |
|
|
response = requests.get(url) |
|
|
data = response.json() |
|
|
|
|
|
df = pd.DataFrame({ |
|
|
'datetime': pd.to_datetime(data['hourly']['time']), |
|
|
'PM10': data['hourly']['pm10'], |
|
|
'PM2.5': data['hourly']['pm2_5'], |
|
|
'CO': data['hourly']['carbon_monoxide'], |
|
|
'NO2': data['hourly']['nitrogen_dioxide'], |
|
|
'O3': data['hourly']['ozone'], |
|
|
'city': city |
|
|
}) |
|
|
aqi_data.append(df) |
|
|
except Exception as e: |
|
|
st.error(f"Error fetching AQI data for {city}: {e}") |
|
|
continue |
|
|
|
|
|
return pd.concat(aqi_data, ignore_index=True) if aqi_data else None |
|
|
|
|
|
def create_ml_features(self, weather_data): |
|
|
"""Create features for ML predictions""" |
|
|
features_df = pd.DataFrame() |
|
|
for city_data in weather_data: |
|
|
df = city_data['hourly'].copy() |
|
|
df['city'] = city_data['city'] |
|
|
|
|
|
|
|
|
df['hour'] = df['datetime'].dt.hour |
|
|
df['day'] = df['datetime'].dt.day |
|
|
df['month'] = df['datetime'].dt.month |
|
|
df['day_of_week'] = df['datetime'].dt.dayofweek |
|
|
|
|
|
|
|
|
df['temp_lag_1'] = df['temperature'].shift(1) |
|
|
df['temp_lag_24'] = df['temperature'].shift(24) |
|
|
|
|
|
|
|
|
df['temp_rolling_mean_6h'] = df['temperature'].rolling(window=6).mean() |
|
|
df['temp_rolling_mean_24h'] = df['temperature'].rolling(window=24).mean() |
|
|
|
|
|
features_df = pd.concat([features_df, df]) |
|
|
|
|
|
return features_df.dropna() |
|
|
|
|
|
def create_cesium_component(earthquake_data=None, weather_data=None): |
|
|
"""Enhanced Cesium 3D visualization""" |
|
|
cesium_html = """ |
|
|
<div id="cesiumContainer" style="width: 100%; height: 600px;"></div> |
|
|
<script src="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Cesium.js"></script> |
|
|
<link href="https://cesium.com/downloads/cesiumjs/releases/1.95/Build/Cesium/Widgets/widgets.css" rel="stylesheet"> |
|
|
<script> |
|
|
Cesium.Ion.defaultAccessToken = 'your-access-token'; |
|
|
const viewer = new Cesium.Viewer('cesiumContainer', { |
|
|
terrainProvider: Cesium.createWorldTerrain(), |
|
|
timeline: true, |
|
|
animation: true, |
|
|
baseLayerPicker: true, |
|
|
scene3DOnly: false, |
|
|
navigationHelpButton: true, |
|
|
navigationInstructionsInitiallyVisible: false, |
|
|
selectionIndicator: true, |
|
|
infoBox: true |
|
|
}); |
|
|
|
|
|
// Add Pakistan terrain |
|
|
viewer.scene.globe.enableLighting = true; |
|
|
viewer.scene.globe.terrainExaggeration = 1.5; |
|
|
|
|
|
// Add weather visualization |
|
|
const weatherEntities = new Cesium.CustomDataSource('Weather'); |
|
|
viewer.dataSources.add(weatherEntities); |
|
|
""" |
|
|
|
|
|
|
|
|
if earthquake_data: |
|
|
cesium_html += """ |
|
|
// Add earthquake visualization |
|
|
const earthquakeEntities = new Cesium.CustomDataSource('Earthquakes'); |
|
|
""" |
|
|
for eq in earthquake_data['features']: |
|
|
coords = eq['geometry']['coordinates'] |
|
|
mag = eq['properties']['mag'] |
|
|
cesium_html += f""" |
|
|
earthquakeEntities.entities.add({{ |
|
|
position: Cesium.Cartesian3.fromDegrees({coords[0]}, {coords[1]}, {coords[2]}), |
|
|
point: {{ |
|
|
pixelSize: {mag * 5}, |
|
|
color: Cesium.Color.RED.withAlpha(0.8), |
|
|
outlineColor: Cesium.Color.WHITE, |
|
|
outlineWidth: 2 |
|
|
}}, |
|
|
description: `Magnitude: {mag}<br>Depth: {coords[2]} km` |
|
|
}}); |
|
|
""" |
|
|
|
|
|
cesium_html += """ |
|
|
viewer.camera.flyTo({ |
|
|
destination: Cesium.Cartesian3.fromDegrees(69.3451, 30.3753, 1000000.0), |
|
|
orientation: { |
|
|
heading: Cesium.Math.toRadians(0.0), |
|
|
pitch: Cesium.Math.toRadians(-45.0), |
|
|
roll: 0.0 |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
""" |
|
|
components.html(cesium_html, height=600) |
|
|
|
|
|
def train_weather_model(features_df, city): |
|
|
"""Train ML model for weather predictions""" |
|
|
city_data = features_df[features_df['city'] == city].copy() |
|
|
|
|
|
|
|
|
feature_cols = ['hour', 'day', 'month', 'day_of_week', |
|
|
'temp_lag_1', 'temp_lag_24', |
|
|
'temp_rolling_mean_6h', 'temp_rolling_mean_24h'] |
|
|
X = city_data[feature_cols] |
|
|
y = city_data['temperature'] |
|
|
|
|
|
|
|
|
split_idx = int(len(X) * 0.8) |
|
|
X_train, X_test = X[:split_idx], X[split_idx:] |
|
|
y_train, y_test = y[:split_idx], y[split_idx:] |
|
|
|
|
|
|
|
|
model = XGBRegressor(n_estimators=100) |
|
|
model.fit(X_train, y_train) |
|
|
|
|
|
return model, X_test, y_test |
|
|
|
|
|
def show_weather_analysis(data_collector): |
|
|
st.header("Advanced Weather Analysis π€οΈ") |
|
|
|
|
|
weather_data = data_collector.fetch_weather_data() |
|
|
if weather_data: |
|
|
selected_city = st.selectbox( |
|
|
"Select City", |
|
|
options=[data['city'] for data in weather_data] |
|
|
) |
|
|
|
|
|
city_data = next(data for data in weather_data if data['city'] == selected_city) |
|
|
|
|
|
|
|
|
st.markdown(download_csv(city_data['hourly'], f"{selected_city}_weather_data"), unsafe_allow_html=True) |
|
|
|
|
|
tabs = st.tabs(["Temperature Analysis", "Precipitation Insights", |
|
|
"Wind Patterns", "Humidity Trends", "ML Predictions"]) |
|
|
|
|
|
with tabs[0]: |
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
|
|
|
fig = go.Figure() |
|
|
fig.add_trace(go.Scatter( |
|
|
x=city_data['hourly']['datetime'], |
|
|
y=city_data['hourly']['temperature'], |
|
|
name='Temperature', |
|
|
line=dict(color='red', width=2) |
|
|
)) |
|
|
fig.update_layout( |
|
|
title='Temperature Trend with Range', |
|
|
template='plotly_dark', |
|
|
hovermode='x unified' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
|
|
|
fig = px.histogram( |
|
|
city_data['hourly'], |
|
|
x='temperature', |
|
|
nbins=30, |
|
|
title='Temperature Distribution' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[1]: |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
fig = px.bar( |
|
|
city_data['daily'], |
|
|
x='date', |
|
|
y='precipitation_sum', |
|
|
title='Daily Precipitation', |
|
|
color='precipitation_sum', |
|
|
color_continuous_scale='Blues' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
|
|
|
precip_prob = (city_data['hourly']['precipitation'] > 0).mean() * 100 |
|
|
st.metric( |
|
|
"Precipitation Probability", |
|
|
f"{precip_prob:.1f}%", |
|
|
delta=f"{precip_prob - 50:.1f}%" |
|
|
) |
|
|
|
|
|
with tabs[2]: |
|
|
|
|
|
fig = go.Figure() |
|
|
fig.add_trace(go.Scatter( |
|
|
x=city_data['hourly']['datetime'], |
|
|
y=city_data['hourly']['wind_speed'], |
|
|
name='Wind Speed', |
|
|
line=dict(color='blue', width=2) |
|
|
)) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=city_data['hourly']['datetime'], |
|
|
y=city_data['hourly']['wind_speed'].rolling(24).mean(), |
|
|
name='24h Moving Average', |
|
|
line=dict(color='red', width=2, dash='dash') |
|
|
)) |
|
|
fig.update_layout( |
|
|
title='Wind Speed Analysis', |
|
|
template='plotly_dark', |
|
|
hovermode='x unified' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[3]: |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
fig = px.line( |
|
|
city_data['hourly'], |
|
|
x='datetime', |
|
|
y='humidity', |
|
|
title='Humidity Trends', |
|
|
color_discrete_sequence=['green'] |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
|
|
|
comfort_zones = pd.cut( |
|
|
city_data['hourly']['humidity'], |
|
|
bins=[0, 30, 45, 60, 100], |
|
|
labels=['Dry', 'Comfortable', 'Moderate', 'Humid'] |
|
|
).value_counts() |
|
|
fig = px.pie( |
|
|
values=comfort_zones.values, |
|
|
names=comfort_zones.index, |
|
|
title='Humidity Comfort Zones' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[4]: |
|
|
st.subheader("Machine Learning Weather Predictions") |
|
|
|
|
|
|
|
|
features_df = data_collector.create_ml_features(weather_data) |
|
|
model, X_test, y_test = train_weather_model(features_df, selected_city) |
|
|
|
|
|
|
|
|
predictions = model.predict(X_test) |
|
|
|
|
|
|
|
|
fig = go.Figure() |
|
|
fig.add_trace(go.Scatter( |
|
|
x=X_test.index, |
|
|
y=y_test, |
|
|
name='Actual Temperature', |
|
|
line=dict(color='blue') |
|
|
)) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=X_test.index, |
|
|
y=predictions, |
|
|
name='Predicted Temperature', |
|
|
line=dict(color='red', dash='dash') |
|
|
)) |
|
|
fig.update_layout( |
|
|
title='Temperature Predictions vs Actual', |
|
|
template='plotly_dark', |
|
|
hovermode='x unified' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
mae = np.mean(np.abs(predictions - y_test)) |
|
|
rmse = np.sqrt(np.mean((predictions - y_test)**2)) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
col1.metric("Mean Absolute Error", f"{mae:.2f}Β°C") |
|
|
col2.metric("Root Mean Square Error", f"{rmse:.2f}Β°C") |
|
|
|
|
|
def show_disaster_monitor(data_collector): |
|
|
st.header("Advanced Disaster Monitoring System π¨") |
|
|
|
|
|
earthquake_data = data_collector.fetch_usgs_earthquake_data() |
|
|
|
|
|
if earthquake_data: |
|
|
|
|
|
st.subheader("3D Terrain Analysis") |
|
|
create_cesium_component(earthquake_data) |
|
|
|
|
|
|
|
|
st.subheader("Earthquake Analysis Dashboard") |
|
|
|
|
|
|
|
|
eq_df = pd.DataFrame([ |
|
|
{ |
|
|
'time': datetime.fromtimestamp(eq['properties']['time']/1000), |
|
|
'magnitude': eq['properties']['mag'], |
|
|
'location': eq['properties']['place'], |
|
|
'depth': eq['geometry']['coordinates'][2], |
|
|
'lat': eq['geometry']['coordinates'][1], |
|
|
'lon': eq['geometry']['coordinates'][0] |
|
|
} |
|
|
for eq in earthquake_data['features'] |
|
|
]) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
|
|
with col1: |
|
|
|
|
|
fig = px.histogram( |
|
|
eq_df, |
|
|
x='magnitude', |
|
|
nbins=20, |
|
|
title='Earthquake Magnitude Distribution', |
|
|
color_discrete_sequence=['red'] |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
|
|
|
fig = px.scatter( |
|
|
eq_df, |
|
|
x='depth', |
|
|
y='magnitude', |
|
|
title='Depth vs Magnitude', |
|
|
color='magnitude', |
|
|
size='magnitude', |
|
|
color_continuous_scale='Viridis' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
st.subheader("Temporal Analysis") |
|
|
eq_df |
|
|
eq_df['date'] = eq_df['time'].dt.date |
|
|
daily_counts = eq_df.groupby('date').size().reset_index(name='count') |
|
|
|
|
|
fig = px.line( |
|
|
daily_counts, |
|
|
x='date', |
|
|
y='count', |
|
|
title='Daily Earthquake Frequency', |
|
|
line_shape='spline' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
st.subheader("Seismic Risk Assessment") |
|
|
risk_zones = folium.Map(location=[30.3753, 69.3451], zoom_start=5) |
|
|
|
|
|
|
|
|
heat_data = [[row['lat'], row['lon'], row['magnitude']] for _, row in eq_df.iterrows()] |
|
|
folium.plugins.HeatMap(heat_data).add_to(risk_zones) |
|
|
|
|
|
|
|
|
fault_lines = { |
|
|
'Main Boundary Thrust': [[34.0151, 71.5249], [33.7294, 73.0931]], |
|
|
'Chaman Fault': [[30.1798, 66.9750], [25.1216, 62.3254]], |
|
|
} |
|
|
|
|
|
for name, coords in fault_lines.items(): |
|
|
folium.PolyLine( |
|
|
coords, |
|
|
color='red', |
|
|
weight=2, |
|
|
popup=name |
|
|
).add_to(risk_zones) |
|
|
|
|
|
folium_static(risk_zones) |
|
|
|
|
|
|
|
|
st.subheader("Seismic Activity Prediction") |
|
|
|
|
|
|
|
|
daily_counts['ds'] = pd.to_datetime(daily_counts['date']) |
|
|
daily_counts['y'] = daily_counts['count'] |
|
|
|
|
|
|
|
|
model = Prophet(yearly_seasonality=True, weekly_seasonality=True) |
|
|
model.fit(daily_counts[['ds', 'y']]) |
|
|
|
|
|
|
|
|
future_dates = model.make_future_dataframe(periods=30) |
|
|
forecast = model.predict(future_dates) |
|
|
|
|
|
|
|
|
fig = go.Figure() |
|
|
fig.add_trace(go.Scatter( |
|
|
x=daily_counts['ds'], |
|
|
y=daily_counts['y'], |
|
|
name='Actual', |
|
|
line=dict(color='blue') |
|
|
)) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=forecast['ds'], |
|
|
y=forecast['yhat'], |
|
|
name='Predicted', |
|
|
line=dict(color='red', dash='dash') |
|
|
)) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=forecast['ds'], |
|
|
y=forecast['yhat_upper'], |
|
|
fill=None, |
|
|
mode='lines', |
|
|
line=dict(color='rgba(255,0,0,0)'), |
|
|
showlegend=False |
|
|
)) |
|
|
fig.add_trace(go.Scatter( |
|
|
x=forecast['ds'], |
|
|
y=forecast['yhat_lower'], |
|
|
fill='tonexty', |
|
|
mode='lines', |
|
|
line=dict(color='rgba(255,0,0,0)'), |
|
|
name='Prediction Interval' |
|
|
)) |
|
|
fig.update_layout( |
|
|
title='Seismic Activity Forecast (30 Days)', |
|
|
xaxis_title='Date', |
|
|
yaxis_title='Number of Earthquakes', |
|
|
template='plotly_dark' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
def show_environmental_data(data_collector): |
|
|
st.header("Advanced Environmental Analysis πΏ") |
|
|
|
|
|
aqi_data = data_collector.fetch_air_quality_data() |
|
|
|
|
|
if aqi_data is not None: |
|
|
selected_city = st.selectbox("Select City", aqi_data['city'].unique()) |
|
|
city_data = aqi_data[aqi_data['city'] == selected_city].copy() |
|
|
|
|
|
|
|
|
st.markdown(download_csv(city_data, f"{selected_city}_air_quality_data"), unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
weights = { |
|
|
'PM2.5': 0.3, |
|
|
'PM10': 0.2, |
|
|
'NO2': 0.2, |
|
|
'O3': 0.2, |
|
|
'CO': 0.1 |
|
|
} |
|
|
|
|
|
|
|
|
for pollutant in weights.keys(): |
|
|
max_val = city_data[pollutant].max() |
|
|
city_data[f'{pollutant}_normalized'] = city_data[pollutant] / max_val * 100 |
|
|
city_data[f'{pollutant}_weighted'] = city_data[f'{pollutant}_normalized'] * weights[pollutant] |
|
|
|
|
|
city_data['AQI'] = sum(city_data[f'{p}_weighted'] for p in weights.keys()) |
|
|
|
|
|
tabs = st.tabs(["AQI Dashboard", "Pollutant Analysis", "Trends & Forecasting", "Health Impact"]) |
|
|
|
|
|
with tabs[0]: |
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
|
|
current_aqi = city_data['AQI'].iloc[-1] |
|
|
with col1: |
|
|
st.metric( |
|
|
"Current AQI", |
|
|
f"{current_aqi:.1f}", |
|
|
delta=f"{current_aqi - city_data['AQI'].iloc[-2]:.1f}" |
|
|
) |
|
|
|
|
|
|
|
|
aqi_categories = pd.cut( |
|
|
city_data['AQI'], |
|
|
bins=[0, 50, 100, 150, 200, 300, float('inf')], |
|
|
labels=['Good', 'Moderate', 'Unhealthy for Sensitive Groups', 'Unhealthy', 'Very Unhealthy', 'Hazardous'] |
|
|
).value_counts() |
|
|
|
|
|
with col2: |
|
|
fig = px.pie( |
|
|
values=aqi_categories.values, |
|
|
names=aqi_categories.index, |
|
|
title='AQI Distribution' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col3: |
|
|
|
|
|
hourly_avg = city_data.groupby(city_data['datetime'].dt.hour)['AQI'].mean() |
|
|
fig = px.line( |
|
|
x=hourly_avg.index, |
|
|
y=hourly_avg.values, |
|
|
title='Daily AQI Pattern', |
|
|
labels={'x': 'Hour of Day', 'y': 'Average AQI'} |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[1]: |
|
|
|
|
|
pollutants = ['PM2.5', 'PM10', 'CO', 'NO2', 'O3'] |
|
|
corr_matrix = city_data[pollutants].corr() |
|
|
|
|
|
fig = px.imshow( |
|
|
corr_matrix, |
|
|
title='Pollutant Correlation Matrix', |
|
|
color_continuous_scale='RdBu' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
|
|
|
selected_pollutant = st.selectbox("Select Pollutant", pollutants) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
fig = px.line( |
|
|
city_data, |
|
|
x='datetime', |
|
|
y=selected_pollutant, |
|
|
title=f'{selected_pollutant} Trend' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with col2: |
|
|
fig = px.box( |
|
|
city_data, |
|
|
y=selected_pollutant, |
|
|
title=f'{selected_pollutant} Distribution' |
|
|
) |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[2]: |
|
|
|
|
|
from statsmodels.tsa.seasonal import seasonal_decompose |
|
|
|
|
|
|
|
|
hourly_data = city_data.set_index('datetime')['AQI'].resample('H').mean() |
|
|
decomposition = seasonal_decompose(hourly_data, period=24) |
|
|
|
|
|
fig = make_subplots(rows=4, cols=1, subplot_titles=('Observed', 'Trend', 'Seasonal', 'Residual')) |
|
|
fig.add_trace(go.Scatter(x=hourly_data.index, y=hourly_data.values, name='Observed'), row=1, col=1) |
|
|
fig.add_trace(go.Scatter(x=hourly_data.index, y=decomposition.trend, name='Trend'), row=2, col=1) |
|
|
fig.add_trace(go.Scatter(x=hourly_data.index, y=decomposition.seasonal, name='Seasonal'), row=3, col=1) |
|
|
fig.add_trace(go.Scatter(x=hourly_data.index, y=decomposition.resid, name='Residual'), row=4, col=1) |
|
|
fig.update_layout(height=800, title_text="AQI Time Series Decomposition") |
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
|
|
with tabs[3]: |
|
|
st.subheader("Health Impact Assessment") |
|
|
|
|
|
|
|
|
impact_thresholds = { |
|
|
'PM2.5': [12, 35.4, 55.4, 150.4], |
|
|
'PM10': [54, 154, 254, 354], |
|
|
'NO2': [53, 100, 360, 649], |
|
|
'O3': [54, 70, 85, 105], |
|
|
'CO': [4.4, 9.4, 12.4, 15.4] |
|
|
} |
|
|
|
|
|
|
|
|
current_risks = {} |
|
|
for pollutant, thresholds in impact_thresholds.items(): |
|
|
current_val = city_data[pollutant].iloc[-1] |
|
|
if current_val <= thresholds[0]: |
|
|
risk = 'Low' |
|
|
elif current_val <= thresholds[1]: |
|
|
risk = 'Moderate' |
|
|
elif current_val <= thresholds[2]: |
|
|
risk = 'High' |
|
|
else: |
|
|
risk = 'Very High' |
|
|
current_risks[pollutant] = {'value': current_val, 'risk': risk} |
|
|
|
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
for pollutant, data in current_risks.items(): |
|
|
st.metric( |
|
|
f"{pollutant} Health Risk", |
|
|
data['risk'], |
|
|
f"{data['value']:.1f}" |
|
|
) |
|
|
|
|
|
with col2: |
|
|
|
|
|
if current_aqi <= 50: |
|
|
st.success("Air quality is good. Outdoor activities are safe.") |
|
|
elif current_aqi <= 100: |
|
|
st.info("Sensitive individuals should consider reducing prolonged outdoor exertion.") |
|
|
elif current_aqi <= 150: |
|
|
st.warning("Everyone should reduce prolonged outdoor exertion.") |
|
|
else: |
|
|
st.error("Avoid outdoor activities. Use air purifiers indoors.") |
|
|
|
|
|
def main(): |
|
|
st.title("π Pakistan Climate & Disaster Monitoring System") |
|
|
|
|
|
|
|
|
st.sidebar.image("https://upload.wikimedia.org/wikipedia/commons/3/32/Flag_of_Pakistan.svg", width=100) |
|
|
st.sidebar.title("Dashboard Controls") |
|
|
|
|
|
data_collector = DataCollector() |
|
|
|
|
|
|
|
|
page = st.sidebar.radio( |
|
|
"Select Module", |
|
|
["Weather Analysis", "Disaster Monitor", "Environmental Data"], |
|
|
format_func=lambda x: f"π {x}" if x == "Weather Analysis" else |
|
|
f"π¨ {x}" if x == "Disaster Monitor" else |
|
|
f"πΏ {x}" |
|
|
) |
|
|
|
|
|
|
|
|
st.sidebar.markdown("---") |
|
|
st.sidebar.markdown(f"Last updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") |
|
|
|
|
|
if page == "Weather Analysis": |
|
|
show_weather_analysis(data_collector) |
|
|
elif page == "Disaster Monitor": |
|
|
show_disaster_monitor(data_collector) |
|
|
elif page == "Environmental Data": |
|
|
show_environmental_data(data_collector) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
main() |