Spaces:
Sleeping
Sleeping
mattritchey
commited on
Commit
•
9a6c783
1
Parent(s):
932378b
Create app.py
Browse files
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)
|