| 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 |
| from plotly.subplots import make_subplots |
|
|
| |
| def download_csv(df, filename): |
| """Generate a download link for a dataframe""" |
| csv = df.to_csv(index=False) |
| b64 = base64.b64encode(csv.encode()).decode() |
| href = f'<a href="data:file/csv;base64,{b64}" download="{filename}.csv">Download {filename} CSV</a>' |
| return href |
|
|
| |
| 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 download_csv(df, filename): |
| """Generate a download link for a dataframe""" |
| csv = df.to_csv(index=False) |
| b64 = base64.b64encode(csv.encode()).decode() |
| href = f'<a href="data:file/csv;base64,{b64}" download="{filename}.csv">Download {filename} CSV</a>' |
| return href |
|
|
| |
| def create_3d_visualization(earthquake_data=None, weather_data=None): |
| """Create an interactive 3D visualization of Pakistan""" |
| threejs_html = """ |
| <div id="visualizationContainer" style="width: 100%; height: 600px;"> |
| <div style="position: absolute; top: 10px; left: 10px; background: rgba(255,255,255,0.8); padding: 10px; border-radius: 5px;"> |
| <h3 style="margin: 0;">Pakistan Terrain Map</h3> |
| <p style="margin: 5px 0;">π΄ Cities | π‘ Earthquake Events</p> |
| </div> |
| </div> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script> |
| <script> |
| const container = document.getElementById('visualizationContainer'); |
| const scene = new THREE.Scene(); |
| scene.background = new THREE.Color(0xc6e6ff); // Light blue sky |
| const camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000); |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); |
| renderer.setSize(container.clientWidth, container.clientHeight); |
| container.appendChild(renderer.domElement); |
| |
| // Pakistan outline coordinates (simplified) |
| const pakistanOutline = [ |
| [75.3, 35.5], [72.8, 36.5], [71.2, 34.7], [69.5, 34.0], |
| [66.8, 31.2], [63.2, 26.8], [61.5, 25.0], [63.5, 23.5], |
| [66.7, 24.5], [67.8, 24.8], [68.9, 27.0], [70.5, 28.5], |
| [72.0, 30.0], [74.0, 32.5], [75.3, 35.5] |
| ]; |
| |
| // Create Pakistan terrain |
| const shape = new THREE.Shape(); |
| pakistanOutline.forEach((point, index) => { |
| if (index === 0) shape.moveTo(point[0] - 67, point[1] - 30); |
| else shape.lineTo(point[0] - 67, point[1] - 30); |
| }); |
| |
| const geometry = new THREE.ExtrudeGeometry(shape, { |
| depth: 2, |
| bevelEnabled: true, |
| bevelSegments: 2, |
| steps: 2, |
| bevelSize: 0.3, |
| bevelThickness: 0.3 |
| }); |
| |
| const material = new THREE.MeshPhongMaterial({ |
| color: 0x4CAF50, |
| flatShading: true, |
| side: THREE.DoubleSide |
| }); |
| |
| const pakistan = new THREE.Mesh(geometry, material); |
| scene.add(pakistan); |
| |
| // Add major cities |
| const cities = { |
| 'Islamabad': [73.0931, 33.7294], |
| 'Karachi': [67.0011, 24.8607], |
| 'Lahore': [74.3587, 31.5204], |
| 'Peshawar': [71.5249, 34.0151], |
| 'Quetta': [66.9750, 30.1798] |
| }; |
| |
| Object.entries(cities).forEach(([name, coords]) => { |
| const cityMarker = new THREE.Mesh( |
| new THREE.SphereGeometry(0.3, 32, 32), |
| new THREE.MeshPhongMaterial({ color: 0xff0000 }) |
| ); |
| cityMarker.position.set(coords[0] - 67, coords[1] - 30, 2.5); |
| scene.add(cityMarker); |
| |
| // Add city label |
| const div = document.createElement('div'); |
| div.className = 'label'; |
| div.textContent = name; |
| div.style.color = 'black'; |
| div.style.position = 'absolute'; |
| div.style.padding = '2px'; |
| div.style.backgroundColor = 'rgba(255,255,255,0.7)'; |
| div.style.borderRadius = '3px'; |
| container.appendChild(div); |
| }); |
| """ |
|
|
| if earthquake_data: |
| threejs_html += """ |
| // Add earthquake markers |
| const earthquakes = """ + json.dumps(earthquake_data['features']) + """; |
| earthquakes.forEach(quake => { |
| const coords = quake.geometry.coordinates; |
| const magnitude = quake.properties.mag; |
| |
| const quakeMarker = new THREE.Mesh( |
| new THREE.SphereGeometry(magnitude * 0.2, 32, 32), |
| new THREE.MeshPhongMaterial({ |
| color: 0xFFD700, |
| opacity: 0.8, |
| transparent: true |
| }) |
| ); |
| quakeMarker.position.set(coords[0] - 67, coords[1] - 30, 2.5); |
| scene.add(quakeMarker); |
| }); |
| """ |
|
|
| threejs_html += """ |
| // Lighting |
| const ambientLight = new THREE.AmbientLight(0x404040, 1); |
| scene.add(ambientLight); |
| const directionalLight = new THREE.DirectionalLight(0xffffff, 1); |
| directionalLight.position.set(5, 5, 5); |
| scene.add(directionalLight); |
| |
| // Camera |
| camera.position.set(0, 0, 20); |
| camera.lookAt(scene.position); |
| |
| // Controls |
| const controls = { |
| rotation: true, |
| elevation: 1.0 |
| }; |
| |
| const gui = new dat.GUI({ autoPlace: false }); |
| container.appendChild(gui.domElement); |
| gui.add(controls, 'rotation').name('Auto Rotate'); |
| gui.add(controls, 'elevation', 0.5, 2).name('Terrain Height'); |
| |
| function animate() { |
| requestAnimationFrame(animate); |
| if (controls.rotation) { |
| pakistan.rotation.y += 0.005; |
| } |
| pakistan.scale.z = controls.elevation; |
| renderer.render(scene, camera); |
| } |
| animate(); |
| |
| // Handle window resize |
| window.addEventListener('resize', () => { |
| camera.aspect = container.clientWidth / container.clientHeight; |
| camera.updateProjectionMatrix(); |
| renderer.setSize(container.clientWidth, container.clientHeight); |
| }); |
| </script> |
| """ |
| components.html(threejs_html, height=600) |
|
|
| |
| 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 Visualization") |
| create_3d_visualization(earthquake_data) |
| |
| |
|
|
| |
| |
| 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_3d_visualization(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) |
| st.markdown("---") |
| st.markdown(""" |
| <div style='text-align: center'> |
| |
| <p>Created by Muhammad Shaheer</p> |
| |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| if __name__ == "__main__": |
| main() |