CSDI-Weather / pages /2_Temperature.py
OttoYu's picture
Upload 9 files
d4bea00 verified
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
@st.cache_data(ttl=300) # 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()