CSDI-Weather / pages /1_Wind.py
OttoYu's picture
Upload 9 files
d4bea00 verified
import streamlit as st
import requests
import folium
from streamlit_folium import st_folium
import pandas as pd
import plotly.graph_objs as go
import branca.colormap as cm
import pytz
from datetime import datetime
# Set page layout to wide
st.set_page_config(layout="wide", page_title="Real-Time Wind Data Dashboard")
@st.cache_data(ttl=300)
def fetch_geojson_data(url):
response = requests.get(url)
data = response.json()
hk_tz = pytz.timezone('Asia/Hong_Kong')
fetch_time = datetime.now(hk_tz).strftime('%Y-%m-%dT%H:%M:%S')
return data, fetch_time
# Function to calculate wind statistics
def calculate_wind_stats(features):
gust_speeds = [feature['properties']['10-Minute Maximum Gust(km/hour)'] for feature in features if
feature['properties']['10-Minute Maximum Gust(km/hour)'] is not None]
mean_speeds = [feature['properties']['10-Minute Mean Speed(km/hour)'] for feature in features if
feature['properties']['10-Minute Mean Speed(km/hour)'] is not None]
if not gust_speeds:
return None, None, None, None
avg_gust = sum(gust_speeds) / len(gust_speeds)
min_gust = min(gust_speeds)
max_gust = max(gust_speeds)
avg_mean_speed = sum(mean_speeds) / len(mean_speeds) if mean_speeds else None
return avg_gust, min_gust, max_gust, avg_mean_speed
# Function to convert wind direction to degrees
def mean_wind_direction_to_degrees(direction):
directions = {
'North': 0, 'Northeast': 45, 'East': 90, 'Southeast': 135,
'South': 180, 'Southwest': 225, 'West': 270, 'Northwest': 315
}
return directions.get(direction, 0)
# Fetch GeoJSON data
url = 'https://csdi.vercel.app/weather/wind'
geo_data, fetch_time = fetch_geojson_data(url)
# Calculate wind statistics
avg_gust, min_gust, max_gust, avg_mean_speed = calculate_wind_stats(geo_data['features'])
# Create a map centered on a specific location
map_center = [22.35473034278638, 114.14827142452518] # Coordinates of Hong Kong
my_map = folium.Map(location=map_center, zoom_start=10.65, 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(my_map)
# Create a colormap for wind speed with limited width
colormap = cm.LinearColormap(colors=['#000000', '#0066eb', '#ff3d77', '#eb0000'],
vmin=0, vmax=85)
my_map.add_child(colormap)
# Function to calculate arrow size based on wind speed
def get_arrow_size(speed):
if speed is None:
return 20
return max(20, min(50, speed * 2))
# Add the GeoJSON data to the map with arrow markers
for feature in geo_data['features']:
coordinates = feature['geometry']['coordinates']
mean_wind_direction = feature['properties']['10-Minute Mean Wind Direction(Compass points)']
mean_speed = feature['properties']['10-Minute Mean Speed(km/hour)']
# Skip plotting if wind direction is null
if mean_wind_direction is None:
continue
# Calculate rotation angle for wind direction
rotation_angle = mean_wind_direction_to_degrees(mean_wind_direction)
# Calculate arrow size based on wind speed
arrow_size = get_arrow_size(mean_speed)
# Determine color based on wind speed
color = colormap(mean_speed) if mean_speed is not None else 'gray'
# Create an arrow marker for wind direction
folium.Marker(
location=[coordinates[1], coordinates[0]],
icon=folium.DivIcon(html=f"""
<div style="
width: {arrow_size}px; height: {arrow_size}px;
display: flex; align-items: center; justify-content: center;
transform: rotate({rotation_angle}deg);
">
<svg width="{arrow_size}" height="{arrow_size}" viewBox="0 0 24 24" fill="none" stroke="{color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="5" x2="12" y2="19"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</div>
"""),
popup=folium.Popup(f"""
<b>{feature['properties']['Automatic Weather Station']}</b><br>
Direction: {mean_wind_direction}<br>
Speed: {mean_speed} km/h<br>
Max Gust: {feature['properties']['10-Minute Maximum Gust(km/hour)']} km/h
""", max_width=300)
).add_to(my_map)
col1, col2, col3 = st.columns([1.65, 2, 1.15])
with col1:
if geo_data['features']:
wind_directions = [feature['properties']['10-Minute Mean Wind Direction(Compass points)'] for feature in
geo_data['features']]
direction_counts = {d: wind_directions.count(d) for d in
['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']}
# Prepare wind speeds for each direction
direction_speeds = {d: [] for d in
['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']}
for feature in geo_data['features']:
direction = feature['properties']['10-Minute Mean Wind Direction(Compass points)']
speed = feature['properties']['10-Minute Mean Speed(km/hour)']
if direction in direction_speeds and speed is not None:
direction_speeds[direction].append(speed)
# Calculate average wind speed for each direction
average_speeds = {d: sum(speeds) / len(speeds) if speeds else 0 for d, speeds in direction_speeds.items()}
# Plot wind direction rose with average wind speed
fig = go.Figure()
# Add polar bar for wind direction
fig.add_trace(go.Barpolar(
r=[direction_counts[d] for d in direction_counts.keys()],
theta=list(direction_counts.keys()),
name='Wind Direction Count',
marker_color='#0008ff',
opacity=0.5
))
# Add radial bar for average wind speed
fig.add_trace(go.Barpolar(
r=list(average_speeds.values()),
theta=list(average_speeds.keys()),
name='Average Wind Speed',
marker_color='#ff0019', # Orange color for wind speed
opacity=0.5,
thetaunit='radians', # Ensures radial bars are correctly positioned
base=0 # Base of the radial bars starts from 0
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=False,
range=[0, max(direction_counts.values())]
),
angularaxis=dict(
tickvals=list(direction_counts.keys()),
ticktext=['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'],
rotation=90, # Rotate to make North the top
direction='clockwise'
)
),
width=500,
height=380,
title={'text': 'Wind Direction and Average Speed Rose Plot', 'font': {'size': 18}},
legend={'x': 0.8, 'y': 0.95}
)
st.plotly_chart(fig, use_container_width=True)
st.caption(f"Data fetched at: {fetch_time}")
if avg_gust is not None:
col1a, col1b = st.columns(2)
with col1a:
st.metric(label="Avg Max Gust (km/h)", value=f"{avg_gust:.2f}")
st.metric(label="Min Max Gust (km/h)", value=f"{min_gust}")
with col1b:
st.metric(label="Max Max Gust (km/h)", value=f"{max_gust}")
if avg_mean_speed is not None:
st.metric(label="Avg Mean Speed (km/h)", value=f"{avg_mean_speed:.2f}")
else:
st.write("No valid wind data available to calculate statistics.")
gust_speeds = [feature['properties']['10-Minute Maximum Gust(km/hour)'] for feature in geo_data['features'] if
feature['properties']['10-Minute Maximum Gust(km/hour)'] is not None]
with col3:
table_data = [{
'Weather Station': feature['properties']['Automatic Weather Station'],
'Mean Wind Direction': feature['properties']['10-Minute Mean Wind Direction(Compass points)'],
'Mean Speed(km/hour)': feature['properties']['10-Minute Mean Speed(km/hour)'],
'Maximum Gust(km/hour)': feature['properties']['10-Minute Maximum Gust(km/hour)']
} for feature in geo_data['features']]
st.dataframe(pd.DataFrame(table_data), height=600)
with col2:
# Display map
st_folium(my_map, use_container_width=True , height=650)