Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import json | |
| import pandas as pd | |
| import folium | |
| from streamlit_folium import st_folium | |
| import plotly.graph_objects as go | |
| import numpy as np | |
| from datetime import datetime, timezone | |
| import time | |
| import pytz | |
| # Set page layout to wide | |
| st.set_page_config(layout="wide", page_title="Real-Time Temperature Data Dashboard") | |
| # Function to fetch JSON data with caching and expiration | |
| # Cache data for 5 minutes (300 seconds) | |
| def fetch_data(): | |
| url = 'https://csdi.vercel.app/weather/temp' | |
| response = requests.get(url) | |
| hk_tz = pytz.timezone('Asia/Hong_Kong') | |
| fetch_time = datetime.now(hk_tz).strftime('%Y-%m-%dT%H:%M:%S') | |
| return json.loads(response.text), fetch_time | |
| # Fetch the JSON data | |
| data, fetch_time = fetch_data() | |
| # Create a Pandas DataFrame from the JSON data | |
| features = data['features'] | |
| df = pd.json_normalize(features) | |
| # Rename columns for easier access | |
| df.rename(columns={ | |
| 'properties.Automatic Weather Station': 'Station', | |
| 'properties.Air Temperature(degree Celsius)': 'Temperature', | |
| 'geometry.coordinates': 'Coordinates' | |
| }, inplace=True) | |
| # Split Coordinates into separate Longitude and Latitude columns | |
| df[['Longitude', 'Latitude']] = pd.DataFrame(df['Coordinates'].tolist(), index=df.index) | |
| # Extract temperature data | |
| temps = df['Temperature'].dropna().tolist() | |
| # Create three columns | |
| col1, col2, col3 = st.columns([1.65, 2, 1.15]) | |
| # Column 1: Histogram and statistics with two-sigma analysis | |
| with col1: | |
| # Row 1: Histogram | |
| with st.container(): | |
| # Convert list to pandas Series | |
| temps_series = pd.Series(temps) | |
| # Calculate histogram data | |
| hist_data = np.histogram(temps_series, bins=10) | |
| bin_edges = hist_data[1] | |
| counts = hist_data[0] | |
| # Create a color gradient from blue to red | |
| def get_color(value, min_value, max_value): | |
| ratio = (value - min_value) / (max_value - min_value) | |
| r = int(255 * ratio) # Red component | |
| b = int(255 * (1 - ratio)) # Blue component | |
| return f'rgb({r}, 0, {b})' | |
| # Create histogram with Plotly Graph Objects | |
| fig = go.Figure() | |
| # Add histogram bars with gradient colors | |
| for i in range(len(bin_edges) - 1): | |
| bin_center = (bin_edges[i] + bin_edges[i + 1]) / 2 | |
| color = get_color(bin_center, bin_edges.min(), bin_edges.max()) | |
| fig.add_trace(go.Bar( | |
| x=[f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}'], | |
| y=[counts[i]], | |
| marker_color=color, | |
| name=f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}' | |
| )) | |
| # Customize layout | |
| fig.update_layout( | |
| xaxis_title='Temperature (°C)', | |
| yaxis_title='Count', | |
| title='Temperature Distribution', | |
| bargap=0.2, # Adjust gap between bars | |
| title_font_size=20, | |
| xaxis_title_font_size=14, | |
| yaxis_title_font_size=14, | |
| height=350, # Set plot height | |
| xaxis=dict(title_font_size=14), | |
| yaxis=dict(title_font_size=14) | |
| ) | |
| # Display the plot in Streamlit | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.caption(f"Data fetched at: {fetch_time}") | |
| # Row 2: Statistics | |
| with st.container(): | |
| col_1, col_2 = st.columns([1, 1]) | |
| with col_1: | |
| if temps: | |
| avg_temp = np.mean(temps) | |
| std_temp = np.std(temps) | |
| max_temp = np.max(temps) | |
| min_temp = np.min(temps) | |
| two_sigma_range = (avg_temp - 2 * std_temp, avg_temp + 2 * std_temp) | |
| st.metric(label="Average Temperature (°C)", value=f"{avg_temp:.2f}") | |
| st.metric(label="Minimum Temperature (°C)", value=f"{min_temp:.2f}") | |
| with col_2: | |
| st.metric(label="Maximum Temperature (°C)", value=f"{max_temp:.2f}") | |
| st.metric(label="Std. Dev (°C)", value=f"{std_temp:.2f}") | |
| # Column 2: Map | |
| def temperature_to_color(temp, min_temp, max_temp): | |
| """Convert temperature to a color based on the gradient from blue (low) to red (high).""" | |
| norm_temp = (temp - min_temp) / (max_temp - min_temp) | |
| red = int(255 * norm_temp) | |
| blue = int(255 * (1 - norm_temp)) | |
| return f'rgb({red}, 0, {blue})' | |
| with col2: | |
| # Create the base 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) | |
| # Determine min and max temperatures for color scaling | |
| min_temp = df['Temperature'].min() | |
| max_temp = df['Temperature'].max() | |
| # Create a color scale legend | |
| colormap = folium.LinearColormap( | |
| colors=['blue', 'white', 'red'], | |
| index=[min_temp, (min_temp + max_temp) / 2, max_temp], | |
| vmin=min_temp, | |
| vmax=max_temp, | |
| caption='Temperature (°C)' | |
| ) | |
| colormap.add_to(m) | |
| # Iterate through each row in the DataFrame | |
| for _, row in df.iterrows(): | |
| lat = row['Latitude'] | |
| lon = row['Longitude'] | |
| station = row['Station'] | |
| temp = row['Temperature'] | |
| # Determine the color based on the temperature | |
| color = temperature_to_color(temp, min_temp, max_temp) if pd.notna(temp) else 'gray' | |
| # Create a marker with temperature data | |
| folium.Marker( | |
| location=[lat, lon], | |
| popup=f"<p style='font-size: 12px; background-color: white; padding: 5px; border-radius: 5px;'>{station}: {temp:.1f}°C</p>", | |
| icon=folium.DivIcon( | |
| html=f'<div style="font-size: 10pt; color: {color}; padding: 2px; border-radius: 5px;">' | |
| f'<strong>{temp:.1f}°C</strong></div>' | |
| ) | |
| ).add_to(m) | |
| # Render the map in Streamlit | |
| st_folium(m, use_container_width=True , height=650) | |
| # Column 3: Data table | |
| with col3: | |
| # Set the table height using CSS | |
| st.markdown( | |
| """ | |
| <style> | |
| .dataframe-container { | |
| height: 600px; | |
| overflow-y: auto; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # Display the DataFrame with the custom CSS class | |
| st.dataframe(df[['Station', 'Temperature', 'Latitude', 'Longitude']], height=600) | |
| # Add a refresh button | |
| if st.button("Refresh Data"): | |
| st.experimental_rerun() | |
| # Automatically rerun every 5 minutes | |
| if 'last_ran' not in st.session_state: | |
| st.session_state.last_ran = datetime.now(timezone.utc) | |
| current_time = datetime.now(timezone.utc) | |
| if (current_time - st.session_state.last_ran).total_seconds() > 300: | |
| st.session_state.last_ran = current_time | |
| st.experimental_rerun() |