mattritchey commited on
Commit
9a6c783
1 Parent(s): 932378b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +280 -0
app.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from branca.element import Template, MacroElement
3
+ from folium.raster_layers import ImageOverlay
4
+ import re
5
+ import glob
6
+ import altair as alt
7
+ import pickle
8
+ import h5py
9
+ import rasterio
10
+ import streamlit as st
11
+ import os
12
+ import branca.colormap as cm
13
+ import folium
14
+ import numpy as np
15
+ import pandas as pd
16
+ from geopy.extra.rate_limiter import RateLimiter
17
+ from geopy.geocoders import Nominatim
18
+
19
+ import warnings
20
+ warnings.filterwarnings("ignore")
21
+
22
+
23
+ @st.cache_data
24
+ def convert_df(df):
25
+ return df.to_csv(index=0).encode('utf-8')
26
+
27
+
28
+ def geocode(address):
29
+ try:
30
+ address2 = address.replace(' ', '+').replace(',', '%2C')
31
+ df = pd.read_json(
32
+ f'https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address={address2}&benchmark=2020&format=json')
33
+ results = df.iloc[:1, 0][0][0]['coordinates']
34
+ lat, lon = results['y'], results['x']
35
+ except:
36
+ geolocator = Nominatim(user_agent="GTA Lookup")
37
+ geocode = RateLimiter(geolocator.geocode, min_delay_seconds=1)
38
+ location = geolocator.geocode(address)
39
+ lat, lon = location.latitude, location.longitude
40
+ return pd.DataFrame({'Lat': lat, 'Lon': lon}, index=[0])
41
+
42
+
43
+ def get_data(row, col, radius=8):
44
+ files = [
45
+ "data/2023_hail.h5",
46
+ "data/2022_hail.h5",
47
+ "data/2021_hail.h5",
48
+ "data/2020_hail.h5"
49
+ ]
50
+
51
+ all_data = []
52
+ all_dates = []
53
+ for f in files:
54
+ with h5py.File(f, 'r') as f:
55
+ data = f['hail'][:, row-radius:row +
56
+ radius+1, col-radius:col+radius+1]
57
+ dates = f['dates'][:]
58
+ all_data.append(data)
59
+ all_dates.append(dates)
60
+
61
+ data_mat = np.concatenate(all_data)
62
+ data_mat = np.where(data_mat < 0, 0, data_mat)*0.0393701
63
+ dates_mat = np.concatenate(all_dates)
64
+
65
+ data_actual = [i[radius, radius] for i in data_mat]
66
+ data_max = np.max(data_mat, axis=(1, 2))
67
+ data_max_2 = np.max(data_mat, axis=0)
68
+
69
+ df = pd.DataFrame({'Date': dates_mat,
70
+ 'Actual': data_actual,
71
+ 'Max': data_max})
72
+
73
+ df['Date'] = pd.to_datetime(df['Date'], format='%Y%m%d')
74
+ df['Date']=df['Date']+pd.Timedelta(days=1)
75
+
76
+ return df, data_max_2
77
+
78
+
79
+ def map_folium(lat, lon,files_dates_selected ):
80
+
81
+ # Create a base map
82
+ m = folium.Map(location=[lat, lon], zoom_start=5)
83
+ folium.Marker(location=[lat, lon], popup=address).add_to(m)
84
+
85
+ # Define the image bounds (SW and NE corners)
86
+ image_bounds = [[20.0000010001429, -129.99999999985712], [54.9999999998571, -60.00000200014287]]
87
+
88
+ # Add ImageOverlays for each image
89
+ dates = []
90
+ for f in files_dates_selected:
91
+ overlay = ImageOverlay(image=f, bounds=image_bounds,
92
+ opacity=.75,
93
+ mercator_project=False)
94
+ filename = os.path.basename(f)
95
+ date_str = re.search(r'(\d{8})', filename).group()
96
+ formatted_date = f"{date_str[:4]}-{date_str[4:6]}-{date_str[6:8]}"
97
+ dates.append(formatted_date)
98
+ overlay.add_to(m)
99
+
100
+
101
+ # HTML template for the slider control with dates
102
+ template_1 = '{% macro html(this, kwargs) %}' + f"""
103
+
104
+ <div id="slider-control" style="position: fixed; top: 50px; left: 50px; z-index: 9999; background-color: white; padding: 10px; border-radius: 5px; box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.3);">
105
+ <label for="image-slider">Select Date:</label>
106
+ <input type="range" min="0" max="{len(dates)-1}" value="{within_days}" class="slider" id="image-slider" style="width: 150px;" oninput="updateFromSlider(this.value)">
107
+ <input type="text" id="date-input" placeholder="YYYY-MM-DD" oninput="updateFromInput(this.value)">
108
+ <span id="slider-value">{dates[within_days]}</span>
109
+ </div>
110
+ <script>"""
111
+
112
+ template_2 = f"""
113
+ var dates = {dates};"""
114
+
115
+ template_3 = """
116
+ var currentIndex = 0;
117
+
118
+ function updateImage(index) {
119
+ index = Math.round(index); // Ensure the index is an integer
120
+ // Update the displayed date
121
+ document.getElementById('slider-value').innerHTML = dates[index];
122
+ document.getElementById('date-input').value = dates[index];
123
+
124
+ // Hide all images
125
+ document.querySelectorAll('.leaflet-image-layer').forEach(function(layer) {
126
+ layer.style.display = 'none';
127
+ });
128
+
129
+ // Show the current image
130
+ document.querySelectorAll('.leaflet-image-layer')[index].style.display = 'block';
131
+
132
+ currentIndex = index;
133
+ }
134
+
135
+ function updateFromSlider(value) {
136
+ updateImage(parseFloat(value));
137
+ }
138
+
139
+ function updateFromInput(inputDate) {
140
+ var index = dates.indexOf(inputDate);
141
+ if (index !== -1) {
142
+ document.getElementById('image-slider').value = index;
143
+ updateImage(index);
144
+ } else {
145
+ alert('Invalid date. Please enter a date in the format YYYY-MM-DD that exists in the dataset.');
146
+ }
147
+ }
148
+
149
+ // Initially show only the first image
150
+ document.addEventListener('DOMContentLoaded', function() {
151
+ document.querySelectorAll('.leaflet-image-layer').forEach(function(layer, index) {
152
+ layer.style.display = index === 0 ? 'block' : 'none';
153
+ });
154
+ });
155
+ </script>
156
+ {% endmacro %}
157
+ """
158
+ template = template_1+template_2+template_3
159
+
160
+
161
+ # Add the custom control to the map
162
+ macro = MacroElement()
163
+ macro._template = Template(template)
164
+ m.get_root().add_child(macro)
165
+
166
+
167
+ colormap_hail = cm.LinearColormap(
168
+ colors=['blue', 'lightblue', 'pink', 'red'], vmin=0.01, vmax=2)
169
+ # Add the color legend to the map
170
+ colormap_hail.caption = 'Legend: Hail (Inches)'
171
+ colormap_hail.add_to(m)
172
+ return m
173
+
174
+
175
+
176
+ #Set up 2 Columns
177
+ st.set_page_config(layout="wide")
178
+ col1, col2 = st.columns((2))
179
+
180
+
181
+ #Input Values
182
+ address = st.sidebar.text_input("Address", "123 Main Street, Dallas, TX 75126")
183
+ date_focus = st.sidebar.date_input("Date", pd.Timestamp(2023, 7, 1))
184
+ within_days = st.sidebar.selectbox('Days Within', (30, 90))
185
+ # start_date = st.sidebar.date_input("Start Date", pd.Timestamp(2023, 1, 1))
186
+ # end_date = st.sidebar.date_input("End Date", pd.Timestamp(2023, 12, 31))
187
+
188
+ start_date = date_focus+pd.Timedelta(days=-within_days)
189
+ end_date = date_focus+pd.Timedelta(days=within_days)
190
+
191
+ date_range = pd.date_range(start=start_date, end=end_date).strftime('%Y%m%d')
192
+
193
+ circle_radius = st.sidebar.selectbox('Box Radius (Miles)', (5, 10, 25))
194
+
195
+ zoom_dic = {5: 12, 10: 11, 25: 10}
196
+ zoom = zoom_dic[circle_radius]
197
+
198
+ #Geocode and get Data
199
+ result = geocode(address)
200
+ lat, lon = result.values[0]
201
+
202
+
203
+ crs_dic = pickle.load(open('data/mrms_hail_crs.pkl', 'rb'))
204
+ transform = crs_dic['affine']
205
+
206
+ row, col = rasterio.transform.rowcol(transform, lon, lat)
207
+
208
+ # center=row,col
209
+ radius = int(np.ceil(circle_radius*1.6))
210
+ crop_coords = col-radius, row-radius, col+radius+1, row+radius+1
211
+
212
+ # Get Data
213
+ df_data, max_values = get_data(row, col, radius)
214
+
215
+ df_data = df_data.query(f"'{start_date}'<=Date<='{end_date}'")
216
+ df_data['Max'] = df_data['Max'].round(3)
217
+ df_data['Actual'] = df_data['Actual'].round(3)
218
+
219
+
220
+ # Create the bar chart
221
+ fig = alt.Chart(df_data).mark_bar(size=3, color='red').encode(
222
+ x='Date:T', # Temporal data type
223
+ y='Actual:Q', # Quantitative data type
224
+ color='Actual:Q', # Color based on Actual values
225
+ tooltip=[ # Adding tooltips
226
+ alt.Tooltip('Date:T', title='Date'),
227
+ alt.Tooltip('Actual:Q', title='Actual Value'),
228
+ alt.Tooltip('Max:Q', title=f'Max Value with {circle_radius} Miles')
229
+ ]
230
+ ).configure(
231
+ view=alt.ViewConfig(
232
+ strokeOpacity=0 # No border around the chart
233
+ )
234
+ ).configure_axis(
235
+ grid=False # Disable grid lines
236
+ ).configure_legend(
237
+ fillColor='transparent', # Ensure no legend is shown
238
+ strokeColor='transparent'
239
+ )
240
+
241
+ files = glob.glob(r'webp\**\*.webp', recursive=True)
242
+ files_dates_selected = [i for i in files if any(
243
+ i for j in date_range if str(j) in re.search(r'(\d{8})', i).group())]
244
+
245
+
246
+ with col1:
247
+ st.title(f'Hail')
248
+ try:
249
+ st.altair_chart(fig, use_container_width=True)
250
+ csv = convert_df(df_data)
251
+ st.download_button(
252
+ label="Download data as CSV",
253
+ data=csv,
254
+ file_name='data.csv',
255
+ mime='text/csv')
256
+ except:
257
+ pass
258
+
259
+
260
+ with col2:
261
+ st.title('Hail Mesh')
262
+ if 'is_first_run' not in st.session_state:
263
+ # First run
264
+ st.session_state.is_first_run = True
265
+ st.components.v1.html(open("map.html", 'r').read(), height=500, width=500)
266
+ else:
267
+ with st.spinner("Loading... Please wait, it's gonna be great..."):
268
+ # st_folium(m, height=500)
269
+ # Not the first run; create a new map
270
+ m=map_folium(lat, lon,files_dates_selected )
271
+ m.save("map_new.html")
272
+ st.components.v1.html(open("map_new.html", 'r').read(), height=500, width=500)
273
+
274
+ # st.bokeh_chart(hv.render(nice_plot*points_lat_lon, backend='bokeh'),use_container_width=True)
275
+
276
+
277
+ st.markdown(""" <style>
278
+ #MainMenu {visibility: hidden;}
279
+ footer {visibility: hidden;}
280
+ </style> """, unsafe_allow_html=True)