Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import plotly.express as px | |
| import plotly.graph_objects as go | |
| import folium | |
| from folium import LinearColormap | |
| import requests | |
| from datetime import datetime, timedelta | |
| from streamlit_folium import st_folium | |
| import pytz | |
| from datetime import datetime | |
| # Set page layout to wide | |
| st.set_page_config(layout="wide", page_title="Real-Time Relative Humidity Data Dashboard") | |
| # Function to load data | |
| # Cache data to avoid reloading every time | |
| def load_data(): | |
| with st.spinner("Loading data..."): | |
| response = requests.get("https://csdi.vercel.app/weather/rhum") | |
| data = response.json() | |
| features = data['features'] | |
| df = pd.json_normalize(features) | |
| df.rename(columns={ | |
| 'properties.Relative Humidity(percent)': 'Relative Humidity (%)', | |
| 'properties.Automatic Weather Station': 'Station Name', | |
| 'geometry.coordinates': 'Coordinates' | |
| }, inplace=True) | |
| df.dropna(subset=['Relative Humidity (%)'], inplace=True) | |
| hk_tz = pytz.timezone('Asia/Hong_Kong') | |
| fetch_time = datetime.now(hk_tz).strftime('%Y-%m-%dT%H:%M:%S') | |
| return df, fetch_time | |
| # Check if the data has been loaded before | |
| if 'last_run' not in st.session_state or (datetime.now() - st.session_state.last_run) > timedelta(minutes=5): | |
| st.session_state.df, st.session_state.fetch_time = load_data() | |
| st.session_state.last_run = datetime.now() | |
| # Data | |
| df = st.session_state.df | |
| fetch_time = st.session_state.fetch_time | |
| # Compute statistics | |
| humidity_data = df['Relative Humidity (%)'] | |
| avg_humidity = humidity_data.mean() | |
| max_humidity = humidity_data.max() | |
| min_humidity = humidity_data.min() | |
| std_humidity = humidity_data.std() | |
| # Create three columns | |
| col1, col2, col3 = st.columns([1.65, 2, 1.15]) | |
| # Column 1: Histogram and statistics | |
| with col1: | |
| # Define colors for gradient | |
| color_scale = ['#58a0db', '#0033cc'] | |
| # Create histogram | |
| fig = px.histogram(df, x='Relative Humidity (%)', nbins=20, | |
| labels={'Relative Humidity (%)': 'Relative Humidity (%)'}, | |
| title='Relative Humidity Histogram', | |
| color_discrete_sequence=color_scale) | |
| # Add average line | |
| fig.add_shape( | |
| go.layout.Shape( | |
| type="line", | |
| x0=avg_humidity, | |
| y0=0, | |
| x1=avg_humidity, | |
| y1=df['Relative Humidity (%)'].value_counts().max(), | |
| line=dict(color="red", width=2, dash="dash"), | |
| ) | |
| ) | |
| # Update layout | |
| fig.update_layout( | |
| xaxis_title='Relative Humidity (%)', | |
| yaxis_title='Count', | |
| title='Relative Humidity Distribution', | |
| bargap=0.2, | |
| title_font_size=20, | |
| xaxis_title_font_size=14, | |
| yaxis_title_font_size=14, | |
| height=350, | |
| shapes=[{ | |
| 'type': 'rect', | |
| 'x0': min_humidity, | |
| 'x1': max_humidity, | |
| 'y0': 0, | |
| 'y1': df['Relative Humidity (%)'].value_counts().max(), | |
| 'fillcolor': 'rgba(0, 100, 255, 0.2)', | |
| 'line': { | |
| 'color': 'rgba(0, 100, 255, 0.2)', | |
| 'width': 0 | |
| }, | |
| 'opacity': 0.1 | |
| }] | |
| ) | |
| # Add annotations | |
| fig.add_annotation( | |
| x=avg_humidity, | |
| y=df['Relative Humidity (%)'].value_counts().max() * 0.9, | |
| text=f"Average: {avg_humidity:.2f}%", | |
| showarrow=True, | |
| arrowhead=1 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.caption(f"Data fetched at: {fetch_time}") | |
| # Display statistics | |
| col_1, col_2 = st.columns([1, 1]) | |
| with col_1: | |
| st.metric(label="Average R.Humidity (%)", value=f"{avg_humidity:.2f}") | |
| st.metric(label="Minimum R.Humidity (%)", value=f"{min_humidity:.2f}") | |
| with col_2: | |
| st.metric(label="Maximum R.Humidity (%)", value=f"{max_humidity:.2f}") | |
| st.metric(label="Std. Dev (%)", value=f"{std_humidity:.2f}") | |
| # Function to convert humidity to color based on gradient | |
| def humidity_to_color(humidity, min_humidity, max_humidity): | |
| if pd.isna(humidity): | |
| return 'rgba(0, 0, 0, 0)' # Return a transparent color if the humidity is NaN | |
| norm_humidity = (humidity - min_humidity) / (max_humidity - min_humidity) | |
| # Colors from light blue (#add8e6) to dark blue (#00008b) | |
| if norm_humidity < 0.5: | |
| r = int(173 + (0 - 173) * (2 * norm_humidity)) | |
| g = int(216 + (0 - 216) * (2 * norm_humidity)) | |
| b = int(230 + (139 - 230) * (2 * norm_humidity)) | |
| else: | |
| r = int(0 + (0 - 0) * (2 * (norm_humidity - 0.5))) | |
| g = int(0 + (0 - 0) * (2 * (norm_humidity - 0.5))) | |
| b = int(139 + (139 - 139) * (2 * (norm_humidity - 0.5))) | |
| return f'rgb({r}, {g}, {b})' | |
| # Column 2: Map | |
| with col2: | |
| with st.spinner("Loading map..."): | |
| m = folium.Map(location=[22.3547, 114.1483], zoom_start=11, tiles='https://landsd.azure-api.net/dev/osm/xyz/basemap/gs/WGS84/tile/{z}/{x}/{y}.png?key=f4d3e21d4fc14954a1d5930d4dde3809',attr="Map infortmation from Lands Department") | |
| folium.TileLayer( | |
| tiles='https://mapapi.geodata.gov.hk/gs/api/v1.0.0/xyz/label/hk/en/wgs84/{z}/{x}/{y}.png', | |
| attr="Map infortmation from Lands Department" | |
| ).add_to(m) | |
| min_humidity = df['Relative Humidity (%)'].min() | |
| max_humidity = df['Relative Humidity (%)'].max() | |
| colormap = LinearColormap( | |
| colors=['#58a0db', 'blue'], | |
| index=[min_humidity, max_humidity], | |
| vmin=min_humidity, | |
| vmax=max_humidity, | |
| caption='Relative Humidity (%)' | |
| ) | |
| colormap.add_to(m) | |
| for _, row in df.iterrows(): | |
| humidity = row['Relative Humidity (%)'] | |
| color = humidity_to_color(humidity, min_humidity, max_humidity) | |
| folium.Marker( | |
| location=[row['Coordinates'][1], row['Coordinates'][0]], | |
| popup=f"<p style='font-size: 12px; background-color: white; padding: 5px; border-radius: 5px;'>{row['Station Name']}: {humidity:.1f}%</p>", | |
| icon=folium.DivIcon( | |
| html=f'<div style="font-size: 10pt; color: {color}; padding: 2px; border-radius: 5px;">' | |
| f'<strong>{humidity:.1f}%</strong></div>' | |
| ) | |
| ).add_to(m) | |
| st_folium(m,use_container_width=True , height=650) | |
| # Column 3: Data Table | |
| with col3: | |
| st.markdown( | |
| """ | |
| <style> | |
| .dataframe-container { | |
| height: 600px; | |
| overflow-y: auto; | |
| } | |
| .dataframe th, .dataframe td { | |
| text-align: left; | |
| padding: 8px; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # Rename column for display | |
| df_display = df[['Station Name', 'Relative Humidity (%)']].rename(columns={'Relative Humidity (%)': 'R.Humidity'}) | |
| st.dataframe(df_display, height=600) | |
| # Refresh Button | |
| if st.button("Refresh Data"): | |
| with st.spinner("Refreshing data..."): | |
| st.session_state.df, st.session_state.fetch_time = load_data() | |
| st.session_state.last_run = datetime.now() | |
| st.experimental_rerun() | |