OttoYu commited on
Commit
f08a1e9
·
verified ·
1 Parent(s): 309cc14

Delete pages

Browse files
pages/1_Wind.py DELETED
@@ -1,202 +0,0 @@
1
- import streamlit as st
2
- import requests
3
- import folium
4
- from streamlit_folium import st_folium
5
- import pandas as pd
6
- import plotly.graph_objs as go
7
- import branca.colormap as cm
8
- import time
9
-
10
- # Set page layout to wide
11
- st.set_page_config(layout="wide", page_title="Real-Time Wind Data Dashboard")
12
-
13
- @st.cache_data(ttl=300)
14
- def fetch_geojson_data(url):
15
- response = requests.get(url)
16
- data = response.json()
17
- fetch_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
18
- return data, fetch_time
19
-
20
- # Function to calculate wind statistics
21
- def calculate_wind_stats(features):
22
- gust_speeds = [feature['properties']['10-Minute Maximum Gust(km/hour)'] for feature in features if
23
- feature['properties']['10-Minute Maximum Gust(km/hour)'] is not None]
24
- mean_speeds = [feature['properties']['10-Minute Mean Speed(km/hour)'] for feature in features if
25
- feature['properties']['10-Minute Mean Speed(km/hour)'] is not None]
26
-
27
- if not gust_speeds:
28
- return None, None, None, None
29
- avg_gust = sum(gust_speeds) / len(gust_speeds)
30
- min_gust = min(gust_speeds)
31
- max_gust = max(gust_speeds)
32
- avg_mean_speed = sum(mean_speeds) / len(mean_speeds) if mean_speeds else None
33
- return avg_gust, min_gust, max_gust, avg_mean_speed
34
-
35
- # Function to convert wind direction to degrees
36
- def mean_wind_direction_to_degrees(direction):
37
- directions = {
38
- 'North': 0, 'Northeast': 45, 'East': 90, 'Southeast': 135,
39
- 'South': 180, 'Southwest': 225, 'West': 270, 'Northwest': 315
40
- }
41
- return directions.get(direction, 0)
42
-
43
- # Fetch GeoJSON data
44
- url = 'https://csdi.vercel.app/weather/wind'
45
- geo_data, fetch_time = fetch_geojson_data(url)
46
-
47
- # Calculate wind statistics
48
- avg_gust, min_gust, max_gust, avg_mean_speed = calculate_wind_stats(geo_data['features'])
49
-
50
- # Create a map centered on a specific location
51
- map_center = [22.35473034278638, 114.14827142452518] # Coordinates of Hong Kong
52
- my_map = folium.Map(location=map_center, zoom_start=10.35, tiles='CartoDB positron')
53
-
54
- # Create a colormap for wind speed with limited width
55
- colormap = cm.LinearColormap(colors=['#000000', '#0066eb', '#ff3d77', '#eb0000'],
56
- vmin=0, vmax=85)
57
- my_map.add_child(colormap)
58
-
59
- # Function to calculate arrow size based on wind speed
60
- def get_arrow_size(speed):
61
- if speed is None:
62
- return 20
63
- return max(20, min(50, speed * 2))
64
-
65
- # Add the GeoJSON data to the map with arrow markers
66
- for feature in geo_data['features']:
67
- coordinates = feature['geometry']['coordinates']
68
- mean_wind_direction = feature['properties']['10-Minute Mean Wind Direction(Compass points)']
69
- mean_speed = feature['properties']['10-Minute Mean Speed(km/hour)']
70
-
71
- # Skip plotting if wind direction is null
72
- if mean_wind_direction is None:
73
- continue
74
-
75
- # Calculate rotation angle for wind direction
76
- rotation_angle = mean_wind_direction_to_degrees(mean_wind_direction)
77
-
78
- # Calculate arrow size based on wind speed
79
- arrow_size = get_arrow_size(mean_speed)
80
-
81
- # Determine color based on wind speed
82
- color = colormap(mean_speed) if mean_speed is not None else 'gray'
83
-
84
- # Create an arrow marker for wind direction
85
- folium.Marker(
86
- location=[coordinates[1], coordinates[0]],
87
- icon=folium.DivIcon(html=f"""
88
- <div style="
89
- width: {arrow_size}px; height: {arrow_size}px;
90
- display: flex; align-items: center; justify-content: center;
91
- transform: rotate({rotation_angle}deg);
92
- ">
93
- <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">
94
- <line x1="12" y1="5" x2="12" y2="19"></line>
95
- <polyline points="5 12 12 5 19 12"></polyline>
96
- </svg>
97
- </div>
98
- """),
99
- popup=folium.Popup(f"""
100
- <b>{feature['properties']['Automatic Weather Station']}</b><br>
101
- Direction: {mean_wind_direction}<br>
102
- Speed: {mean_speed} km/h<br>
103
- Max Gust: {feature['properties']['10-Minute Maximum Gust(km/hour)']} km/h
104
- """, max_width=300)
105
- ).add_to(my_map)
106
-
107
- col1, col2, col3 = st.columns([1.65, 2, 1.15])
108
-
109
- with col1:
110
- if geo_data['features']:
111
- wind_directions = [feature['properties']['10-Minute Mean Wind Direction(Compass points)'] for feature in
112
- geo_data['features']]
113
- direction_counts = {d: wind_directions.count(d) for d in
114
- ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']}
115
-
116
- # Prepare wind speeds for each direction
117
- direction_speeds = {d: [] for d in
118
- ['North', 'Northeast', 'East', 'Southeast', 'South', 'Southwest', 'West', 'Northwest']}
119
- for feature in geo_data['features']:
120
- direction = feature['properties']['10-Minute Mean Wind Direction(Compass points)']
121
- speed = feature['properties']['10-Minute Mean Speed(km/hour)']
122
- if direction in direction_speeds and speed is not None:
123
- direction_speeds[direction].append(speed)
124
-
125
- # Calculate average wind speed for each direction
126
- average_speeds = {d: sum(speeds) / len(speeds) if speeds else 0 for d, speeds in direction_speeds.items()}
127
-
128
- # Plot wind direction rose with average wind speed
129
- fig = go.Figure()
130
-
131
- # Add polar bar for wind direction
132
- fig.add_trace(go.Barpolar(
133
- r=[direction_counts[d] for d in direction_counts.keys()],
134
- theta=list(direction_counts.keys()),
135
- name='Wind Direction Count',
136
- marker_color='#0008ff',
137
- opacity=0.5
138
- ))
139
-
140
- # Add radial bar for average wind speed
141
- fig.add_trace(go.Barpolar(
142
- r=list(average_speeds.values()),
143
- theta=list(average_speeds.keys()),
144
- name='Average Wind Speed',
145
- marker_color='#ff0019', # Orange color for wind speed
146
- opacity=0.5,
147
- thetaunit='radians', # Ensures radial bars are correctly positioned
148
- base=0 # Base of the radial bars starts from 0
149
- ))
150
-
151
- fig.update_layout(
152
- polar=dict(
153
- radialaxis=dict(
154
- visible=False,
155
- range=[0, max(direction_counts.values())]
156
- ),
157
- angularaxis=dict(
158
- tickvals=list(direction_counts.keys()),
159
- ticktext=['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'],
160
- rotation=90, # Rotate to make North the top
161
- direction='clockwise'
162
- )
163
- ),
164
- width=500,
165
- height=380,
166
- title={'text': 'Wind Direction and Average Speed Rose Plot', 'font': {'size': 18}},
167
- legend={'x': 0.8, 'y': 0.95}
168
- )
169
-
170
- st.plotly_chart(fig, use_container_width=True)
171
-
172
- st.caption(f"Data fetched at: {fetch_time}")
173
-
174
- if avg_gust is not None:
175
- col1a, col1b = st.columns(2)
176
- with col1a:
177
- st.metric(label="Avg Max Gust (km/h)", value=f"{avg_gust:.2f}")
178
- st.metric(label="Min Max Gust (km/h)", value=f"{min_gust}")
179
- with col1b:
180
- st.metric(label="Max Max Gust (km/h)", value=f"{max_gust}")
181
- if avg_mean_speed is not None:
182
- st.metric(label="Avg Mean Speed (km/h)", value=f"{avg_mean_speed:.2f}")
183
- else:
184
- st.write("No valid wind data available to calculate statistics.")
185
-
186
- gust_speeds = [feature['properties']['10-Minute Maximum Gust(km/hour)'] for feature in geo_data['features'] if
187
- feature['properties']['10-Minute Maximum Gust(km/hour)'] is not None]
188
-
189
-
190
- with col3:
191
- table_data = [{
192
- 'Weather Station': feature['properties']['Automatic Weather Station'],
193
- 'Mean Wind Direction': feature['properties']['10-Minute Mean Wind Direction(Compass points)'],
194
- 'Mean Speed(km/hour)': feature['properties']['10-Minute Mean Speed(km/hour)'],
195
- 'Maximum Gust(km/hour)': feature['properties']['10-Minute Maximum Gust(km/hour)']
196
- } for feature in geo_data['features']]
197
-
198
- st.dataframe(pd.DataFrame(table_data), height=600)
199
-
200
- with col2:
201
- # Display map
202
- st_folium(my_map, width=500, height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/2_Temperature.py DELETED
@@ -1,196 +0,0 @@
1
- import streamlit as st
2
- import requests
3
- import json
4
- import pandas as pd
5
- import folium
6
- from streamlit_folium import st_folium
7
- import plotly.graph_objects as go
8
- import numpy as np
9
- from datetime import datetime, timezone
10
- import time
11
-
12
- # Set page layout to wide
13
- st.set_page_config(layout="wide", page_title="Real-Time Temperature Data Dashboard")
14
-
15
- # Function to fetch JSON data with caching and expiration
16
- @st.cache_data(ttl=300) # Cache data for 5 minutes (300 seconds)
17
- def fetch_data():
18
- url = 'https://csdi.vercel.app/weather/temp'
19
- response = requests.get(url)
20
- fetch_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
21
- return json.loads(response.text), fetch_time
22
-
23
- # Fetch the JSON data
24
- data, fetch_time = fetch_data()
25
-
26
- # Create a Pandas DataFrame from the JSON data
27
- features = data['features']
28
- df = pd.json_normalize(features)
29
-
30
- # Rename columns for easier access
31
- df.rename(columns={
32
- 'properties.Automatic Weather Station': 'Station',
33
- 'properties.Air Temperature(degree Celsius)': 'Temperature',
34
- 'geometry.coordinates': 'Coordinates'
35
- }, inplace=True)
36
-
37
- # Split Coordinates into separate Longitude and Latitude columns
38
- df[['Longitude', 'Latitude']] = pd.DataFrame(df['Coordinates'].tolist(), index=df.index)
39
-
40
- # Extract temperature data
41
- temps = df['Temperature'].dropna().tolist()
42
-
43
- # Create three columns
44
- col1, col2, col3 = st.columns([1.65, 2, 1.15])
45
-
46
- # Column 1: Histogram and statistics with two-sigma analysis
47
- with col1:
48
- # Row 1: Histogram
49
- with st.container():
50
- # Convert list to pandas Series
51
- temps_series = pd.Series(temps)
52
-
53
- # Calculate histogram data
54
- hist_data = np.histogram(temps_series, bins=10)
55
- bin_edges = hist_data[1]
56
- counts = hist_data[0]
57
-
58
-
59
- # Create a color gradient from blue to red
60
- def get_color(value, min_value, max_value):
61
- ratio = (value - min_value) / (max_value - min_value)
62
- r = int(255 * ratio) # Red component
63
- b = int(255 * (1 - ratio)) # Blue component
64
- return f'rgb({r}, 0, {b})'
65
-
66
-
67
- # Create histogram with Plotly Graph Objects
68
- fig = go.Figure()
69
-
70
- # Add histogram bars with gradient colors
71
- for i in range(len(bin_edges) - 1):
72
- bin_center = (bin_edges[i] + bin_edges[i + 1]) / 2
73
- color = get_color(bin_center, bin_edges.min(), bin_edges.max())
74
- fig.add_trace(go.Bar(
75
- x=[f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}'],
76
- y=[counts[i]],
77
- marker_color=color,
78
- name=f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}'
79
- ))
80
-
81
- # Customize layout
82
- fig.update_layout(
83
- xaxis_title='Temperature (°C)',
84
- yaxis_title='Count',
85
- title='Temperature Distribution',
86
- bargap=0.2, # Adjust gap between bars
87
- title_font_size=20,
88
- xaxis_title_font_size=14,
89
- yaxis_title_font_size=14,
90
- height=350, # Set plot height
91
- xaxis=dict(title_font_size=14),
92
- yaxis=dict(title_font_size=14)
93
- )
94
-
95
- # Display the plot in Streamlit
96
- st.plotly_chart(fig, use_container_width=True)
97
- st.caption(f"Data fetched at: {fetch_time}")
98
-
99
- # Row 2: Statistics
100
- with st.container():
101
- col_1, col_2 = st.columns([1, 1])
102
- with col_1:
103
- if temps:
104
- avg_temp = np.mean(temps)
105
- std_temp = np.std(temps)
106
- max_temp = np.max(temps)
107
- min_temp = np.min(temps)
108
-
109
- two_sigma_range = (avg_temp - 2 * std_temp, avg_temp + 2 * std_temp)
110
-
111
- st.metric(label="Average Temperature (°C)", value=f"{avg_temp:.2f}")
112
- st.metric(label="Minimum Temperature (°C)", value=f"{min_temp:.2f}")
113
- with col_2:
114
- st.metric(label="Maximum Temperature (°C)", value=f"{max_temp:.2f}")
115
- st.metric(label="Std. Dev (°C)", value=f"{std_temp:.2f}")
116
-
117
-
118
- # Column 2: Map
119
- def temperature_to_color(temp, min_temp, max_temp):
120
- """Convert temperature to a color based on the gradient from blue (low) to red (high)."""
121
- norm_temp = (temp - min_temp) / (max_temp - min_temp)
122
- red = int(255 * norm_temp)
123
- blue = int(255 * (1 - norm_temp))
124
- return f'rgb({red}, 0, {blue})'
125
-
126
- with col2:
127
- # Create the base map
128
- m = folium.Map(location=[22.3547, 114.1483], zoom_start=11, tiles='CartoDB positron')
129
-
130
- # Determine min and max temperatures for color scaling
131
- min_temp = df['Temperature'].min()
132
- max_temp = df['Temperature'].max()
133
-
134
- # Create a color scale legend
135
- colormap = folium.LinearColormap(
136
- colors=['blue', 'white', 'red'],
137
- index=[min_temp, (min_temp + max_temp) / 2, max_temp],
138
- vmin=min_temp,
139
- vmax=max_temp,
140
- caption='Temperature (°C)'
141
- )
142
- colormap.add_to(m)
143
-
144
- # Iterate through each row in the DataFrame
145
- for _, row in df.iterrows():
146
- lat = row['Latitude']
147
- lon = row['Longitude']
148
- station = row['Station']
149
- temp = row['Temperature']
150
-
151
- # Determine the color based on the temperature
152
- color = temperature_to_color(temp, min_temp, max_temp) if pd.notna(temp) else 'gray'
153
-
154
- # Create a marker with temperature data
155
- folium.Marker(
156
- location=[lat, lon],
157
- popup=f"<p style='font-size: 12px; background-color: white; padding: 5px; border-radius: 5px;'>{station}: {temp:.1f}°C</p>",
158
- icon=folium.DivIcon(
159
- html=f'<div style="font-size: 10pt; color: {color}; padding: 2px; border-radius: 5px;">'
160
- f'<strong>{temp:.1f}°C</strong></div>'
161
- )
162
- ).add_to(m)
163
-
164
- # Render the map in Streamlit
165
- st_folium(m, width=500, height=600)
166
-
167
- # Column 3: Data table
168
- with col3:
169
- # Set the table height using CSS
170
- st.markdown(
171
- """
172
- <style>
173
- .dataframe-container {
174
- height: 600px;
175
- overflow-y: auto;
176
- }
177
- </style>
178
- """,
179
- unsafe_allow_html=True
180
- )
181
-
182
- # Display the DataFrame with the custom CSS class
183
- st.dataframe(df[['Station', 'Temperature', 'Latitude', 'Longitude']], height=600)
184
-
185
- # Add a refresh button
186
- if st.button("Refresh Data"):
187
- st.experimental_rerun()
188
-
189
- # Automatically rerun every 5 minutes
190
- if 'last_ran' not in st.session_state:
191
- st.session_state.last_ran = datetime.now(timezone.utc)
192
-
193
- current_time = datetime.now(timezone.utc)
194
- if (current_time - st.session_state.last_ran).total_seconds() > 300:
195
- st.session_state.last_ran = current_time
196
- st.experimental_rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/3_Humidity.py DELETED
@@ -1,197 +0,0 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import plotly.express as px
4
- import plotly.graph_objects as go
5
- import folium
6
- from folium import LinearColormap
7
- import requests
8
- from datetime import datetime, timedelta
9
- from streamlit_folium import st_folium
10
-
11
- # Set page layout to wide
12
- st.set_page_config(layout="wide", page_title="Real-Time Relative Humidity Data Dashboard")
13
-
14
- # Function to load data
15
- @st.cache_data(ttl=300) # Cache data to avoid reloading every time
16
- def load_data():
17
- with st.spinner("Loading data..."):
18
- response = requests.get("https://csdi.vercel.app/weather/rhum")
19
- data = response.json()
20
- features = data['features']
21
- df = pd.json_normalize(features)
22
- df.rename(columns={
23
- 'properties.Relative Humidity(percent)': 'Relative Humidity (%)',
24
- 'properties.Automatic Weather Station': 'Station Name',
25
- 'geometry.coordinates': 'Coordinates'
26
- }, inplace=True)
27
- df.dropna(subset=['Relative Humidity (%)'], inplace=True)
28
- fetch_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
29
- return df, fetch_time
30
-
31
- # Check if the data has been loaded before
32
- if 'last_run' not in st.session_state or (datetime.now() - st.session_state.last_run) > timedelta(minutes=5):
33
- st.session_state.df, st.session_state.fetch_time = load_data()
34
- st.session_state.last_run = datetime.now()
35
-
36
- # Data
37
- df = st.session_state.df
38
- fetch_time = st.session_state.fetch_time
39
-
40
- # Compute statistics
41
- humidity_data = df['Relative Humidity (%)']
42
- avg_humidity = humidity_data.mean()
43
- max_humidity = humidity_data.max()
44
- min_humidity = humidity_data.min()
45
- std_humidity = humidity_data.std()
46
-
47
- # Create three columns
48
- col1, col2, col3 = st.columns([1.65, 2, 1.15])
49
-
50
- # Column 1: Histogram and statistics
51
- with col1:
52
- # Define colors for gradient
53
- color_scale = ['#58a0db', '#0033cc']
54
-
55
- # Create histogram
56
- fig = px.histogram(df, x='Relative Humidity (%)', nbins=20,
57
- labels={'Relative Humidity (%)': 'Relative Humidity (%)'},
58
- title='Relative Humidity Histogram',
59
- color_discrete_sequence=color_scale)
60
-
61
- # Add average line
62
- fig.add_shape(
63
- go.layout.Shape(
64
- type="line",
65
- x0=avg_humidity,
66
- y0=0,
67
- x1=avg_humidity,
68
- y1=df['Relative Humidity (%)'].value_counts().max(),
69
- line=dict(color="red", width=2, dash="dash"),
70
- )
71
- )
72
-
73
- # Update layout
74
- fig.update_layout(
75
- xaxis_title='Relative Humidity (%)',
76
- yaxis_title='Count',
77
- title='Relative Humidity Distribution',
78
- bargap=0.2,
79
- title_font_size=20,
80
- xaxis_title_font_size=14,
81
- yaxis_title_font_size=14,
82
- height=350,
83
- shapes=[{
84
- 'type': 'rect',
85
- 'x0': min_humidity,
86
- 'x1': max_humidity,
87
- 'y0': 0,
88
- 'y1': df['Relative Humidity (%)'].value_counts().max(),
89
- 'fillcolor': 'rgba(0, 100, 255, 0.2)',
90
- 'line': {
91
- 'color': 'rgba(0, 100, 255, 0.2)',
92
- 'width': 0
93
- },
94
- 'opacity': 0.1
95
- }]
96
- )
97
-
98
- # Add annotations
99
- fig.add_annotation(
100
- x=avg_humidity,
101
- y=df['Relative Humidity (%)'].value_counts().max() * 0.9,
102
- text=f"Average: {avg_humidity:.2f}%",
103
- showarrow=True,
104
- arrowhead=1
105
- )
106
-
107
- st.plotly_chart(fig, use_container_width=True)
108
- st.caption(f"Data fetched at: {fetch_time}")
109
-
110
- # Display statistics
111
- col_1, col_2 = st.columns([1, 1])
112
- with col_1:
113
- st.metric(label="Average R.Humidity (%)", value=f"{avg_humidity:.2f}")
114
- st.metric(label="Minimum R.Humidity (%)", value=f"{min_humidity:.2f}")
115
- with col_2:
116
- st.metric(label="Maximum R.Humidity (%)", value=f"{max_humidity:.2f}")
117
- st.metric(label="Std. Dev (%)", value=f"{std_humidity:.2f}")
118
-
119
-
120
- # Function to convert humidity to color based on gradient
121
- def humidity_to_color(humidity, min_humidity, max_humidity):
122
- if pd.isna(humidity):
123
- return 'rgba(0, 0, 0, 0)' # Return a transparent color if the humidity is NaN
124
-
125
- norm_humidity = (humidity - min_humidity) / (max_humidity - min_humidity)
126
-
127
- # Colors from light blue (#add8e6) to dark blue (#00008b)
128
- if norm_humidity < 0.5:
129
- r = int(173 + (0 - 173) * (2 * norm_humidity))
130
- g = int(216 + (0 - 216) * (2 * norm_humidity))
131
- b = int(230 + (139 - 230) * (2 * norm_humidity))
132
- else:
133
- r = int(0 + (0 - 0) * (2 * (norm_humidity - 0.5)))
134
- g = int(0 + (0 - 0) * (2 * (norm_humidity - 0.5)))
135
- b = int(139 + (139 - 139) * (2 * (norm_humidity - 0.5)))
136
-
137
- return f'rgb({r}, {g}, {b})'
138
-
139
- # Column 2: Map
140
- with col2:
141
- with st.spinner("Loading map..."):
142
- m = folium.Map(location=[22.3547, 114.1483], zoom_start=11, tiles='CartoDB positron')
143
- min_humidity = df['Relative Humidity (%)'].min()
144
- max_humidity = df['Relative Humidity (%)'].max()
145
-
146
- colormap = LinearColormap(
147
- colors=['#58a0db', 'blue'],
148
- index=[min_humidity, max_humidity],
149
- vmin=min_humidity,
150
- vmax=max_humidity,
151
- caption='Relative Humidity (%)'
152
- )
153
- colormap.add_to(m)
154
-
155
- for _, row in df.iterrows():
156
- humidity = row['Relative Humidity (%)']
157
- color = humidity_to_color(humidity, min_humidity, max_humidity)
158
-
159
- folium.Marker(
160
- location=[row['Coordinates'][1], row['Coordinates'][0]],
161
- popup=f"<p style='font-size: 12px; background-color: white; padding: 5px; border-radius: 5px;'>{row['Station Name']}: {humidity:.1f}%</p>",
162
- icon=folium.DivIcon(
163
- html=f'<div style="font-size: 10pt; color: {color}; padding: 2px; border-radius: 5px;">'
164
- f'<strong>{humidity:.1f}%</strong></div>'
165
- )
166
- ).add_to(m)
167
-
168
- st_folium(m, width=500, height=600)
169
-
170
- # Column 3: Data Table
171
- with col3:
172
- st.markdown(
173
- """
174
- <style>
175
- .dataframe-container {
176
- height: 600px;
177
- overflow-y: auto;
178
- }
179
- .dataframe th, .dataframe td {
180
- text-align: left;
181
- padding: 8px;
182
- }
183
- </style>
184
- """,
185
- unsafe_allow_html=True
186
- )
187
-
188
- # Rename column for display
189
- df_display = df[['Station Name', 'Relative Humidity (%)']].rename(columns={'Relative Humidity (%)': 'R.Humidity'})
190
- st.dataframe(df_display, height=600)
191
-
192
- # Refresh Button
193
- if st.button("Refresh Data"):
194
- with st.spinner("Refreshing data..."):
195
- st.session_state.df, st.session_state.fetch_time = load_data()
196
- st.session_state.last_run = datetime.now()
197
- st.experimental_rerun()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/4_Smart Lampposts.py DELETED
@@ -1,174 +0,0 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import requests
4
- import plotly.express as px
5
- import plotly.graph_objs as go
6
- from folium import DivIcon
7
- import folium
8
- from streamlit_folium import st_folium
9
- from sklearn.linear_model import LinearRegression
10
- from sklearn.cluster import DBSCAN
11
- import matplotlib.cm as cm
12
- import matplotlib.colors as mcolors
13
- import time
14
- import json
15
-
16
- # Set page layout to wide
17
- st.set_page_config(layout="wide", page_title="Real-Time Smart Lamppost Data Dashboard")
18
-
19
- # Function to fetch JSON data with caching and expiration
20
- @st.cache_data(ttl=600)
21
- def fetch_data(url):
22
- response = requests.get(url)
23
- fetch_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
24
- return json.loads(response.text), fetch_time
25
-
26
- # Function to calculate "feels like" temperature
27
- def feels_like_temperature(temp_celsius, humidity_percent):
28
- return temp_celsius - (0.55 - 0.0055 * humidity_percent) * (temp_celsius - 14.5)
29
-
30
- # Function to process the raw data into a DataFrame
31
- def process_data(data):
32
- features = data['features']
33
- records = [
34
- {
35
- 'latitude': feature['geometry']['coordinates'][1],
36
- 'longitude': feature['geometry']['coordinates'][0],
37
- 'temperature': feature['properties'].get('Air temperature (°C) / 氣溫 (°C) / 气温 (°C)'),
38
- 'humidity': feature['properties'].get('Relative humidity (%) / 相對濕度 (%) / 相对湿度 (%)')
39
- }
40
- for feature in features
41
- ]
42
- df = pd.DataFrame(records)
43
-
44
- # Convert temperature and humidity to numeric, forcing errors to NaN
45
- df['temperature'] = pd.to_numeric(df['temperature'], errors='coerce')
46
- df['humidity'] = pd.to_numeric(df['humidity'], errors='coerce')
47
-
48
- # Drop rows with NaN values
49
- df = df.dropna(subset=['temperature', 'humidity'])
50
-
51
- # Calculate "feels like" temperature
52
- df['feels_like'] = df.apply(lambda row: feels_like_temperature(row['temperature'], row['humidity']), axis=1)
53
-
54
- return df
55
-
56
- # Fetch and process data
57
- url = "https://csdi.vercel.app/weather/smls"
58
- data, fetch_time = fetch_data(url)
59
- df = process_data(data)
60
-
61
- # Perform clustering using DBSCAN
62
- coords = df[['latitude', 'longitude']].values
63
- db = DBSCAN(eps=0.01, min_samples=5).fit(coords)
64
- df['cluster'] = db.labels_
65
-
66
- # Initialize the 'predicted_humidity' column with NaN
67
- df['predicted_humidity'] = pd.NA
68
-
69
- # Perform linear regression for each cluster
70
- for cluster in df['cluster'].unique():
71
- cluster_data = df[df['cluster'] == cluster]
72
- if len(cluster_data) > 1: # Only perform regression if there are enough points
73
- X = cluster_data['temperature'].values.reshape(-1, 1)
74
- y = cluster_data['humidity'].values
75
- reg = LinearRegression().fit(X, y)
76
- df.loc[df['cluster'] == cluster, 'predicted_humidity'] = reg.predict(X)
77
-
78
- # Calculate temperature statistics
79
- temp_stats = df['temperature'].describe()
80
- avg_temp = temp_stats['mean']
81
- min_temp = temp_stats['min']
82
- max_temp = temp_stats['max']
83
- std_temp = temp_stats['std']
84
-
85
- # Create regression plot using Plotly
86
- fig = px.scatter(df, x='temperature', y='humidity', color='cluster',
87
- title='Temperature vs. Relative Humidity with Regression by Cluster')
88
-
89
- # Add regression lines to the plot
90
- for cluster in df['cluster'].unique():
91
- cluster_data = df[df['cluster'] == cluster]
92
- if 'predicted_humidity' in cluster_data.columns and not cluster_data['predicted_humidity'].isna().all():
93
- fig.add_trace(go.Scatter(x=cluster_data['temperature'], y=cluster_data['predicted_humidity'], mode='lines',
94
- name=f'Cluster {cluster}'))
95
-
96
- # Column 1: Regression Plot, Data, and Statistics
97
- col1, col2, col3 = st.columns([1.65, 2, 1.15])
98
-
99
- with col1:
100
- st.plotly_chart(fig, use_container_width=True, height=300)
101
- st.caption(f"Data fetched at: {fetch_time}")
102
-
103
- # Display temperature statistics
104
- col_1, col_2 = st.columns([1, 1])
105
- with col_1:
106
- st.metric(label="Average Temperature (°C)", value=f"{avg_temp:.2f}")
107
- st.metric(label="Minimum Temperature (°C)", value=f"{min_temp:.2f}")
108
- with col_2:
109
- st.metric(label="Maximum Temperature (°C)", value=f"{max_temp:.2f}")
110
- st.metric(label="Std. Dev (°C)", value=f"{std_temp:.2f}")
111
-
112
- # Column 2: Map
113
- with col2:
114
- # Initialize the Folium map
115
- m = folium.Map(location=[22.320394086610452, 114.21626912476121], zoom_start=14, tiles='CartoDB positron')
116
-
117
- # Define a color map for clusters
118
- unique_clusters = df['cluster'].unique()
119
- colors = cm.get_cmap('tab10', len(unique_clusters)) # Using 'tab10' colormap for up to 10 clusters
120
- cluster_colors = {cluster: mcolors.to_hex(colors(i)) for i, cluster in enumerate(unique_clusters)}
121
-
122
- # Plot original data points
123
- for _, row in df.iterrows():
124
- folium.CircleMarker(
125
- location=[row['latitude'], row['longitude']],
126
- radius=5,
127
- color=cluster_colors[row['cluster']],
128
- fill=True,
129
- fill_color=cluster_colors[row['cluster']],
130
- fill_opacity=0.7,
131
- popup=f"Temp: {row['temperature']} °C<br>Humidity: {row['humidity']} %<br>Feels Like: {row['feels_like']:.2f} °C<br>Cluster: {row['cluster']}"
132
- ).add_to(m)
133
-
134
- # Calculate the average temperature for each cluster
135
- cluster_centers = df.groupby('cluster').agg({
136
- 'latitude': 'mean',
137
- 'longitude': 'mean',
138
- 'temperature': 'mean'
139
- }).reset_index()
140
-
141
- # Plot cluster centers
142
- for _, row in cluster_centers.iterrows():
143
- folium.Marker(
144
- location=[row['latitude'], row['longitude']],
145
- icon=DivIcon(
146
- icon_size=(150,36),
147
- icon_anchor=(85, 20), # Adjusted anchor position to move text away from the point
148
- html=f'<strong><div style="font-size: 15px; color: {cluster_colors[row["cluster"]]}">{row["temperature"]:.2f} °C</div></strong>'
149
- ),
150
- popup=f"Cluster: {row['cluster']}<br>Avg Temp: {row['temperature']:.2f} °C"
151
- ).add_to(m)
152
-
153
- # Display the map in Streamlit
154
- st_folium(m, width=500, height=600)
155
-
156
- # Column 3: Data Table
157
- with col3:
158
- st.markdown(
159
- """
160
- <style>
161
- .dataframe-container {
162
- height: 600px;
163
- overflow-y: auto;
164
- }
165
- .dataframe th, .dataframe td {
166
- text-align: left;
167
- padding: 8px;
168
- }
169
- </style>
170
- """,
171
- unsafe_allow_html=True
172
- )
173
- # Display the DataFrame
174
- st.dataframe(df[['latitude', 'longitude', 'temperature', 'humidity', 'feels_like', 'cluster']], height=600)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/5_Past Records.py DELETED
@@ -1,141 +0,0 @@
1
- import streamlit as st
2
- import pandas as pd
3
- import plotly.express as px
4
- import json
5
- import os
6
- import glob
7
-
8
- # Directory paths
9
- data_dir = 'past_temp'
10
- geojson_file = os.path.join(data_dir, 'FavgTS.geojson')
11
-
12
- # Load GeoJSON data
13
- def load_geojson():
14
- with open(geojson_file) as f:
15
- return json.load(f)
16
-
17
- # Create a dictionary to map short forms to long forms
18
- def create_station_map(geojson):
19
- feature_map = {}
20
- for feature in geojson['features']:
21
- short_name = feature['properties']['WeatherStationShortName']
22
- long_name = feature['properties']['WeatherStationName_en']
23
- feature_map[short_name] = long_name
24
- return feature_map
25
-
26
- # Load CSV files
27
- def load_csv_files():
28
- return glob.glob(os.path.join(data_dir, '*.csv'))
29
-
30
- # Plot time series
31
- def plot_time_series(df, station_name):
32
- df['Date'] = pd.to_datetime(df['Date'], format='%Y%m%d', errors='coerce')
33
- df['Year'] = df['Date'].dt.year
34
- df['Month'] = df['Date'].dt.month
35
-
36
- fig_all_years = px.line(df, x='Date', y='Value', color='Year',
37
- title=f'All-Year Temperature Time Series for {station_name}',
38
- labels={'Date': 'Date', 'Value': 'Temperature (°C)', 'Year': 'Year'},
39
- line_shape='linear')
40
- fig_all_years.update_layout(xaxis_title='Date', yaxis_title='Temperature (°C)')
41
-
42
- return fig_all_years
43
-
44
- # Plot monthly averages
45
- def plot_monthly_averages(df, station_name):
46
- df['Date'] = pd.to_datetime(df['Date'], format='%Y%m%d', errors='coerce')
47
- df['Year'] = df['Date'].dt.year
48
- df['Month'] = df['Date'].dt.month
49
-
50
- monthly_avg = df.groupby(['Year', 'Month'])['Value'].mean().reset_index()
51
-
52
- fig_monthly_avg = px.line(monthly_avg, x='Month', y='Value', color='Year',
53
- title=f'Monthly Average Temperature Time Series for {station_name}',
54
- labels={'Month': 'Month', 'Value': 'Average Temperature (°C)', 'Year': 'Year'},
55
- line_shape='linear')
56
- fig_monthly_avg.update_layout(xaxis_title='Month', yaxis_title='Average Temperature (°C)', xaxis_tickformat='%b')
57
-
58
- return fig_monthly_avg
59
-
60
- def plot_annual_average(df, station_name):
61
- annual_avg = df.groupby('Year')['Value'].mean().reset_index()
62
-
63
- fig_annual_avg = px.line(annual_avg, x='Year', y='Value',
64
- title=f'Annual Average Temperature Trend for {station_name}',
65
- labels={'Year': 'Year', 'Value': 'Average Temperature (°C)'},
66
- line_shape='linear')
67
- fig_annual_avg.update_layout(xaxis_title='Year', yaxis_title='Average Temperature (°C)')
68
-
69
- return fig_annual_avg
70
-
71
- # Streamlit app layout
72
- st.set_page_config(layout="wide", page_title="Temperature Time Series")
73
-
74
- # Load GeoJSON and create mapping
75
- geojson = load_geojson()
76
- station_map = create_station_map(geojson)
77
-
78
- # Load all CSV files
79
- csv_files = load_csv_files()
80
-
81
- # Initialize data storage for all CSV files
82
- all_data = []
83
-
84
- # Process each CSV file
85
- for file in csv_files:
86
- try:
87
- file_name = os.path.basename(file)
88
- short_form = file_name.split('.')[0] # Get the file name without extension
89
-
90
- df = pd.read_csv(file)
91
-
92
- if df.shape[1] < 2:
93
- st.error(f"File {file} does not have the expected number of columns. Skipping.")
94
- continue
95
-
96
- if df.columns[0] != 'Date':
97
- df.columns = ['Date', 'Value']
98
-
99
- long_form = station_map.get(short_form, "Unknown Station")
100
- df['Station'] = long_form
101
- all_data.append(df)
102
-
103
- except Exception as e:
104
- st.error(f"Error loading or processing file {file}: {e}")
105
-
106
- # Combine all data into a single DataFrame
107
- if all_data:
108
- combined_df = pd.concat(all_data, ignore_index=True)
109
- combined_df['Date'] = pd.to_datetime(combined_df['Date'], format='%Y%m%d', errors='coerce')
110
- combined_df = combined_df.dropna(subset=['Date'])
111
- combined_df['Year'] = combined_df['Date'].dt.year
112
- combined_df['Month'] = combined_df['Date'].dt.month
113
-
114
- stations = combined_df['Station'].unique()
115
- default_station = stations[0] if len(stations) > 0 else None
116
-
117
- if not stations.size:
118
- st.write("No stations available in the data.")
119
- else:
120
- st.subheader('Past Daily Average Temperature Time Series')
121
- selected_station = st.selectbox("Select a Station", options=stations, index=0)
122
-
123
- station_data = combined_df[combined_df['Station'] == selected_station]
124
-
125
- if not station_data.empty:
126
- # Create two columns for plots
127
- col1, col2 = st.columns([2,1.5])
128
-
129
- # Top plot: All-year time series
130
- with col1:
131
- fig_all_years = plot_time_series(station_data, selected_station)
132
- st.plotly_chart(fig_all_years, use_container_width=True)
133
-
134
- # Bottom plot: Monthly average temperatures
135
- with col2:
136
- fig_monthly_avg = plot_monthly_averages(station_data, selected_station)
137
- st.plotly_chart(fig_monthly_avg, use_container_width=True)
138
- else:
139
- st.write(f"No data available for the selected station '{selected_station}'.")
140
- else:
141
- st.write("No data to display.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/6_Heat island.py DELETED
@@ -1,183 +0,0 @@
1
- import streamlit as st
2
- import folium
3
- import json
4
- import plotly.express as px
5
- import pandas as pd
6
- from streamlit_folium import st_folium
7
- import plotly.graph_objs as go
8
-
9
- st.set_page_config(layout="wide", page_title="Heat Island Effect Analysis")
10
-
11
- def load_geojson(filepath):
12
- with open(filepath, 'r', encoding='utf-8') as f:
13
- return json.load(f)
14
-
15
- def plot_geojson(feature_group, geojson_data, property_name, colormap):
16
- folium.GeoJson(
17
- geojson_data,
18
- style_function=lambda feature: {
19
- 'fillColor': colormap(feature['properties'][property_name]),
20
- 'color': 'black',
21
- 'weight': 1,
22
- 'fillOpacity': 0.7,
23
- },
24
- popup=folium.GeoJsonPopup(fields=['NAME_EN', property_name], aliases=['District:', 'Value:']),
25
- ).add_to(feature_group)
26
-
27
- def compute_difference_geojson(geojson_2013, geojson_2023):
28
- difference_geojson = {"type": "FeatureCollection", "features": []}
29
-
30
- name_to_hot_nights_2013 = {
31
- feature['properties']['NAME_EN']: feature['properties']['Hot_Nights']
32
- for feature in geojson_2013['features']
33
- }
34
-
35
- for feature in geojson_2023['features']:
36
- name_en = feature['properties']['NAME_EN']
37
- hot_nights_2013 = name_to_hot_nights_2013.get(name_en, 0)
38
- hot_nights_2023 = feature['properties']['Hot_Nights']
39
- difference = hot_nights_2023 - hot_nights_2013
40
-
41
- feature['properties']['Difference'] = difference
42
- difference_geojson['features'].append(feature)
43
-
44
- return difference_geojson
45
-
46
- def geojson_to_dataframe(geojson_data, year):
47
- features = geojson_data['features']
48
- data = {
49
- 'District': [feature['properties']['NAME_EN'] for feature in features],
50
- 'Hot_Nights': [feature['properties']['Hot_Nights'] for feature in features],
51
- 'Year': [year] * len(features) # Add year column
52
- }
53
- return pd.DataFrame(data)
54
-
55
- geojson_2013 = load_geojson('ref/2013_hot.geojson')
56
- geojson_2023 = load_geojson('ref/2023_hot.geojson')
57
-
58
- hot_nights_2013 = [feature['properties']['Hot_Nights'] for feature in geojson_2013['features']]
59
- hot_nights_2023 = [feature['properties']['Hot_Nights'] for feature in geojson_2023['features']]
60
- all_hot_nights = hot_nights_2013 + hot_nights_2023
61
-
62
- colormap = folium.LinearColormap(
63
- colors=['white', 'orange', 'red'],
64
- vmin=min(all_hot_nights),
65
- vmax=max(all_hot_nights),
66
- caption='Hot Nights'
67
- )
68
-
69
- difference_geojson = compute_difference_geojson(geojson_2013, geojson_2023)
70
-
71
- diff_colormap = folium.LinearColormap(
72
- colors=['blue', 'lightblue', 'white', 'pink', 'red'],
73
- index=[-50, -10, 0, 10, 50],
74
- vmin=-50,
75
- vmax=50,
76
- caption='Change in Hot Nights'
77
- )
78
-
79
- m = folium.Map(location=[22.35994791346238, 114.15924623933743], zoom_start=11, tiles='CartoDB positron')
80
-
81
- feature_group_2013 = folium.FeatureGroup(name='2013 Hot Nights', show=False)
82
- feature_group_2023 = folium.FeatureGroup(name='2023 Hot Nights', show=False)
83
- feature_group_diff = folium.FeatureGroup(name='Change in Hot Nights', show=True)
84
-
85
- plot_geojson(feature_group_2013, geojson_2013, 'Hot_Nights', colormap)
86
- plot_geojson(feature_group_2023, geojson_2023, 'Hot_Nights', colormap)
87
- plot_geojson(feature_group_diff, difference_geojson, 'Difference', diff_colormap)
88
-
89
- feature_group_2013.add_to(m)
90
- feature_group_2023.add_to(m)
91
- feature_group_diff.add_to(m)
92
-
93
- layer_control = folium.LayerControl().add_to(m)
94
-
95
- colormap.add_to(m)
96
- diff_colormap.add_to(m)
97
-
98
- df_2013 = geojson_to_dataframe(geojson_2013, '2013')
99
- df_2023 = geojson_to_dataframe(geojson_2023, '2023')
100
-
101
- combined_df = pd.concat([df_2013, df_2023])
102
-
103
- def plot_combined_box_plot(df):
104
- fig = px.box(
105
- df,
106
- x='Year',
107
- y='Hot_Nights',
108
- title='Hot Nights (2013 vs 2023)',
109
- labels={'Hot_Nights': 'Number of Hot Nights', 'Year': 'Year'},
110
- color='Year'
111
- )
112
- fig.update_layout(
113
- yaxis_title='Number of Hot Nights',
114
- boxmode='group'
115
- )
116
- return fig
117
-
118
- data_table = pd.read_csv('ref/final_summary_with_available_stations.csv')
119
-
120
- stations = data_table['station_name'].unique()
121
-
122
- col1, col2, col3 = st.columns([1.35, 2, 1.1])
123
-
124
- with col1:
125
- st.subheader('Heat Island Effect')
126
- st.caption(
127
- 'The "heat island effect" refers to the temperature difference between urban and rural areas, particularly at night.')
128
- st.caption(
129
- 'This phenomenon is a result of the urbanization and development processes. During the day, the urban environment (such as cement pavement) absorbs and stores more heat from solar insolation compared to rural areas (vegetation). This heat is then slowly released in the evening and nighttime, leading to higher temperatures in the urban areas.')
130
-
131
- selected_station = st.selectbox('Select a Station:', options=stations)
132
-
133
- filtered_data_table = data_table[data_table['station_name'] == selected_station]
134
-
135
- fig = go.Figure()
136
-
137
- fig.add_trace(go.Scatter(
138
- x=filtered_data_table['month'],
139
- y=filtered_data_table['13day_temp'],
140
- mode='lines+markers',
141
- name='2013 Day Temp',
142
- line=dict(color='blue')
143
- ))
144
- fig.add_trace(go.Scatter(
145
- x=filtered_data_table['month'],
146
- y=filtered_data_table['13night_temp'],
147
- mode='lines+markers',
148
- name='2013 Night Temp',
149
- line=dict(color='blue', dash='dash')
150
- ))
151
- fig.add_trace(go.Scatter(
152
- x=filtered_data_table['month'],
153
- y=filtered_data_table['23day_temp'],
154
- mode='lines+markers',
155
- name='2023 Day Temp',
156
- line=dict(color='red')
157
- ))
158
- fig.add_trace(go.Scatter(
159
- x=filtered_data_table['month'],
160
- y=filtered_data_table['23night_temp'],
161
- mode='lines+markers',
162
- name='2023 Night Temp',
163
- line=dict(color='red', dash='dash')
164
- ))
165
-
166
- fig.update_layout(
167
- title=f'Temperature Comparison',
168
- xaxis_title='Month',
169
- yaxis_title='Temperature (°C)',
170
- legend_title='Legend',
171
- height =300
172
- )
173
-
174
- st.plotly_chart(fig, height=180)
175
-
176
- with col2:
177
- st_folium(m, width=550, height=650)
178
-
179
- with col3:
180
- st.caption(
181
- 'From data from the CO-WIN network, there has been a significant increase in the number of hot nights in Hong Kong. "Hot nights" refers to nights where the temperature remains above 28 degrees. Within the period from 2013 to 2023, 9 districts in Hong Kong have experienced an increase in the frequency of hot nights, the most significant are those in the urban.')
182
-
183
- st.plotly_chart(plot_combined_box_plot(combined_df), use_container_width=True ,height=380)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pages/7_CoWIN.py DELETED
@@ -1,172 +0,0 @@
1
- import streamlit as st
2
- import requests
3
- import json
4
- import pandas as pd
5
- import folium
6
- from streamlit_folium import st_folium
7
- import plotly.graph_objects as go
8
- import numpy as np
9
- from datetime import datetime
10
- from branca.colormap import LinearColormap
11
- import pytz
12
-
13
- st.set_page_config(layout="wide", page_title="Real-Time CoWIN Weather Data Dashboard")
14
-
15
- @st.cache_data(ttl=300) # Cache data for 5 minutes (300 seconds)
16
- def fetch_data():
17
- hk_tz = pytz.timezone('Asia/Hong_Kong')
18
- current_time = datetime.now(hk_tz).strftime('%Y-%m-%dT%H:%M:%S')
19
- url = f'https://cowin.hku.hk/API/data/CoWIN/map?time={current_time}'
20
- response = requests.get(url)
21
- return json.loads(response.text), current_time
22
-
23
- data, fetched_time = fetch_data()
24
-
25
- features = data
26
- df = pd.json_normalize(features)
27
-
28
- df.rename(columns={
29
- 'station': 'Station',
30
- 'temp': 'Temperature',
31
- 'lat': 'Latitude',
32
- 'lon': 'Longitude',
33
- 'wd': 'Wind Direction',
34
- 'ws': 'Wind Speed',
35
- 'rh': 'Relative Humidity',
36
- 'uv': 'UV Radiation',
37
- 'me_name': 'Name'
38
- }, inplace=True)
39
-
40
- attribute = st.selectbox(
41
- 'Select Weather Attributes to Plot and Map (Data from HKO-HKU CoWIN)',
42
- ['Temperature', 'Wind Speed', 'Relative Humidity', 'UV Radiation']
43
- )
44
-
45
- col1, col2, col3 = st.columns([1.65, 2, 1.2])
46
-
47
- with col1:
48
- attr_series = pd.Series(df[attribute].dropna())
49
-
50
- hist_data = np.histogram(attr_series, bins=10)
51
- bin_edges = hist_data[1]
52
- counts = hist_data[0]
53
-
54
- def get_color(value, min_value, max_value):
55
- ratio = (value - min_value) / (max_value - min_value)
56
- r = int(255 * ratio)
57
- b = int(255 * (1 - ratio))
58
- return f'rgb({r}, 0, {b})'
59
-
60
- fig = go.Figure()
61
-
62
- for i in range(len(bin_edges) - 1):
63
- bin_center = (bin_edges[i] + bin_edges[i + 1]) / 2
64
- color = get_color(bin_center, bin_edges.min(), bin_edges.max())
65
- fig.add_trace(go.Bar(
66
- x=[f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}'],
67
- y=[counts[i]],
68
- marker_color=color,
69
- name=f'{bin_edges[i]:.1f} - {bin_edges[i + 1]:.1f}'
70
- ))
71
-
72
- fig.update_layout(
73
- xaxis_title=f'{attribute}',
74
- yaxis_title='Count',
75
- title=f'{attribute} Distribution',
76
- bargap=0.2,
77
- title_font_size=20,
78
- xaxis_title_font_size=14,
79
- yaxis_title_font_size=14,
80
- height=350,
81
- xaxis=dict(title_font_size=14),
82
- yaxis=dict(title_font_size=14)
83
- )
84
-
85
- st.plotly_chart(fig, use_container_width=True)
86
- st.caption(f"Data fetched at: {fetched_time}")
87
-
88
- with st.container():
89
- col_1, col_2 = st.columns([1, 1])
90
- with col_1:
91
- if attr_series.size > 0:
92
- avg_attr = np.mean(attr_series)
93
- std_attr = np.std(attr_series)
94
- max_attr = np.max(attr_series)
95
- min_attr = np.min(attr_series)
96
-
97
- st.metric(label=f"Average {attribute}", value=f"{avg_attr:.2f}")
98
- st.metric(label=f"Minimum {attribute}", value=f"{min_attr:.2f}")
99
- with col_2:
100
- st.metric(label=f"Maximum {attribute}", value=f"{max_attr:.2f}")
101
- st.metric(label=f"Std. Dev {attribute}", value=f"{std_attr:.2f}")
102
-
103
- def attribute_to_color(value, min_value, max_value):
104
- """Convert a value to a color based on the gradient."""
105
- ratio = (value - min_value) / (max_value - min_value)
106
- return LinearColormap(['blue', 'purple', 'red']).rgb_hex_str(ratio)
107
-
108
- with col2:
109
- m = folium.Map(location=[22.3547, 114.1483], zoom_start=11, tiles='CartoDB positron')
110
-
111
- min_value = df[attribute].min()
112
- max_value = df[attribute].max()
113
-
114
- for _, row in df.iterrows():
115
- lat = row['Latitude']
116
- lon = row['Longitude']
117
- station = row['Station']
118
- name = row['Name']
119
- value = row[attribute]
120
-
121
- color = attribute_to_color(value, min_value, max_value) if pd.notna(value) else 'gray'
122
-
123
- folium.Marker(
124
- location=[lat, lon],
125
- popup=(
126
- f"<p style='font-size: 12px; background-color: white; padding: 5px; border-radius: 5px;'>"
127
- f"Station: {station}<br>"
128
- f"Name: {name}<br>"
129
- f"{attribute}: {value}<br>"
130
- f"</p>"
131
- ),
132
- icon=folium.DivIcon(
133
- html=f'<div style="font-size: 10pt; color: {color}; padding: 2px; border-radius: 5px;">'
134
- f'<strong>{value}</strong></div>'
135
- )
136
- ).add_to(m)
137
-
138
- # Create a color scale legend
139
- colormap = folium.LinearColormap(
140
- colors=['blue', 'purple', 'red'],
141
- index=[min_value, (min_value + max_value) / 2, max_value],
142
- vmin=min_value,
143
- vmax=max_value,
144
- caption=f'{attribute}'
145
- )
146
- colormap.add_to(m)
147
-
148
- st_folium(m, width=530, height=600)
149
-
150
- with col3:
151
- st.markdown(
152
- """
153
- <style>
154
- .dataframe-container {
155
- height: 600px;
156
- overflow-y: auto;
157
- }
158
- </style>
159
- """,
160
- unsafe_allow_html=True
161
- )
162
-
163
- st.dataframe(df[['Station', 'Name', 'Temperature', 'Wind Speed', 'Relative Humidity', 'UV Radiation', 'Latitude', 'Longitude']], height=600)
164
-
165
- if st.button("Refresh Data"):
166
- st.experimental_rerun()
167
-
168
- hk_tz = pytz.timezone('Asia/Hong_Kong')
169
- current_time = datetime.now(hk_tz)
170
- if 'last_ran' not in st.session_state or (current_time - st.session_state.last_ran.replace(tzinfo=hk_tz)).total_seconds() > 300:
171
- st.session_state.last_ran = current_time
172
- st.experimental_rerun()