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 @st.cache_data(ttl=300) # 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"

{row['Station Name']}: {humidity:.1f}%

", icon=folium.DivIcon( html=f'
' f'{humidity:.1f}%
' ) ).add_to(m) st_folium(m,use_container_width=True , height=650) # Column 3: Data Table with col3: st.markdown( """ """, 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()