Update src/streamlit_app.py
Browse files- src/streamlit_app.py +117 -117
src/streamlit_app.py
CHANGED
@@ -1,143 +1,143 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
-
import requests
|
4 |
import plotly.express as px
|
5 |
from datetime import date, timedelta
|
6 |
|
|
|
|
|
|
|
|
|
|
|
7 |
# --- Page Configuration ---
|
8 |
st.set_page_config(
|
9 |
-
page_title="Earthquake
|
10 |
-
page_icon="
|
11 |
layout="wide",
|
12 |
initial_sidebar_state="expanded"
|
13 |
)
|
14 |
|
15 |
-
# ---
|
16 |
-
@st.cache_data(ttl=
|
17 |
-
def
|
18 |
"""
|
19 |
-
Fetches earthquake data from the
|
20 |
-
Returns a pandas DataFrame or None if an error occurs.
|
21 |
"""
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
28 |
try:
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
except
|
36 |
-
st.error(f"
|
37 |
-
return
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
# --- UI Design ---
|
43 |
-
st.title("
|
44 |
-
st.markdown("
|
45 |
|
46 |
with st.sidebar:
|
47 |
st.header("π Search Parameters")
|
|
|
|
|
48 |
today = date.today()
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
|
|
|
|
|
|
53 |
search_button = st.button("Search Earthquakes", type="primary", use_container_width=True)
|
54 |
|
55 |
# --- Main Content Area ---
|
56 |
if search_button:
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
if df is not None and not df.empty:
|
61 |
-
# --- DIAGNOSTIC FIX IS HERE ---
|
62 |
-
# This logic now includes a debugging message to reveal the available columns.
|
63 |
-
time_col = None
|
64 |
-
if 'event_time' in df.columns:
|
65 |
-
time_col = 'event_time'
|
66 |
-
elif 'time' in df.columns:
|
67 |
-
time_col = 'time'
|
68 |
-
|
69 |
-
if time_col:
|
70 |
-
df['time'] = pd.to_datetime(df[time_col])
|
71 |
-
else:
|
72 |
-
# If no time column is found, print the available columns for debugging.
|
73 |
-
st.error("Data format error: Could not find a valid time column in the API response.")
|
74 |
-
st.warning("The API returned data, but the column names were not as expected.")
|
75 |
-
st.write("**Available columns found in the data:**", df.columns.tolist())
|
76 |
-
st.stop()
|
77 |
-
|
78 |
-
# --- The rest of the script continues as before ---
|
79 |
-
st.success(f"Found {len(df)} earthquakes matching your criteria.")
|
80 |
-
df['magnitude'] = df['mag'].astype(float)
|
81 |
-
df['depth'] = df['depth'].astype(float)
|
82 |
-
|
83 |
-
st.subheader("π Summary Statistics")
|
84 |
-
col1, col2, col3 = st.columns(3)
|
85 |
-
col1.metric("Total Earthquakes", f"{len(df):,}")
|
86 |
-
col2.metric("Largest Magnitude", f"{df['magnitude'].max():.2f}")
|
87 |
-
col3.metric("Deepest Event (km)", f"{df['depth'].max():.2f}")
|
88 |
-
|
89 |
-
st.markdown("---")
|
90 |
-
st.subheader("π Interactive Earthquake Map")
|
91 |
-
st.markdown("Hover over points for details. Circle size represents magnitude.")
|
92 |
-
|
93 |
-
fig = px.scatter_geo(
|
94 |
-
df,
|
95 |
-
lat='latitude',
|
96 |
-
lon='longitude',
|
97 |
-
size='magnitude',
|
98 |
-
color='depth',
|
99 |
-
hover_name='place',
|
100 |
-
hover_data={
|
101 |
-
'latitude': ':.2f',
|
102 |
-
'longitude': ':.2f',
|
103 |
-
'time': True,
|
104 |
-
'magnitude': ':.2f',
|
105 |
-
'depth': ':.2f km'
|
106 |
-
},
|
107 |
-
projection="natural earth",
|
108 |
-
title=f"Earthquakes from {start_date} to {end_date} (Magnitude > {min_magnitude})",
|
109 |
-
color_continuous_scale=px.colors.sequential.Plasma_r
|
110 |
-
)
|
111 |
-
|
112 |
-
fig.update_layout(
|
113 |
-
margin={"r":0,"t":40,"l":0,"b":0},
|
114 |
-
coloraxis_colorbar_title_text='Depth (km)'
|
115 |
-
)
|
116 |
-
st.plotly_chart(fig, use_container_width=True)
|
117 |
-
|
118 |
-
st.markdown("---")
|
119 |
-
st.subheader("π Detailed Earthquake Data")
|
120 |
-
|
121 |
-
display_df = df[['time', 'place', 'magnitude', 'depth', 'url']].copy()
|
122 |
-
display_df.rename(columns={
|
123 |
-
'time': 'Time (UTC)',
|
124 |
-
'place': 'Location',
|
125 |
-
'magnitude': 'Magnitude',
|
126 |
-
'depth': 'Depth (km)',
|
127 |
-
'url': 'More Info'
|
128 |
-
}, inplace=True)
|
129 |
-
|
130 |
-
st.dataframe(
|
131 |
-
display_df,
|
132 |
-
use_container_width=True,
|
133 |
-
height=400,
|
134 |
-
column_config={
|
135 |
-
"More Info": st.column_config.LinkColumn("USGS Event Page")
|
136 |
-
}
|
137 |
-
)
|
138 |
-
elif df is not None and df.empty:
|
139 |
-
st.warning(f"No earthquakes with magnitude {min_magnitude} or higher were found in the selected date range.")
|
140 |
else:
|
141 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
else:
|
143 |
-
st.info("
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
|
|
3 |
import plotly.express as px
|
4 |
from datetime import date, timedelta
|
5 |
|
6 |
+
# Import ObsPy for live data fetching
|
7 |
+
import obspy
|
8 |
+
from obspy.clients.fdsn import Client
|
9 |
+
from obspy import UTCDateTime
|
10 |
+
|
11 |
# --- Page Configuration ---
|
12 |
st.set_page_config(
|
13 |
+
page_title="Live Earthquake Explorer (ObsPy)",
|
14 |
+
page_icon="π°οΈ",
|
15 |
layout="wide",
|
16 |
initial_sidebar_state="expanded"
|
17 |
)
|
18 |
|
19 |
+
# --- Live Data Fetching Function using ObsPy ---
|
20 |
+
@st.cache_data(ttl=900) # Cache the data for 15 minutes
|
21 |
+
def fetch_live_data(start_date, end_date, min_mag):
|
22 |
"""
|
23 |
+
Fetches live earthquake data from the IRIS FDSN web service using ObsPy.
|
|
|
24 |
"""
|
25 |
+
# Initialize the FDSN client
|
26 |
+
client = Client("IRIS")
|
27 |
+
|
28 |
+
# Convert dates to ObsPy's UTCDateTime format
|
29 |
+
start_time = UTCDateTime(start_date)
|
30 |
+
# Add one day to the end date to ensure the entire day is included
|
31 |
+
end_time = UTCDateTime(end_date) + timedelta(days=1)
|
32 |
+
|
33 |
try:
|
34 |
+
# Fetch the earthquake catalog
|
35 |
+
catalog = client.get_events(
|
36 |
+
starttime=start_time,
|
37 |
+
endtime=end_time,
|
38 |
+
minmagnitude=min_mag
|
39 |
+
)
|
40 |
+
except Exception as e:
|
41 |
+
st.error(f"Could not retrieve data from IRIS FDSN service: {e}")
|
42 |
+
return pd.DataFrame() # Return empty dataframe on error
|
43 |
+
|
44 |
+
# Process the catalog into a list of dictionaries
|
45 |
+
event_list = []
|
46 |
+
for event in catalog:
|
47 |
+
# Skip events that lack a preferred origin or magnitude to prevent errors
|
48 |
+
if not (event.preferred_origin() and event.preferred_magnitude()):
|
49 |
+
continue
|
50 |
+
|
51 |
+
origin = event.preferred_origin()
|
52 |
+
magnitude = event.preferred_magnitude()
|
53 |
+
|
54 |
+
# Get location description
|
55 |
+
description = "N/A"
|
56 |
+
if event.descriptions and event.descriptions[0].text:
|
57 |
+
description = event.descriptions[0].text
|
58 |
+
|
59 |
+
event_list.append({
|
60 |
+
"latitude": origin.latitude,
|
61 |
+
"longitude": origin.longitude,
|
62 |
+
"depth": origin.depth / 1000, # Convert depth from meters to km
|
63 |
+
"magnitude": magnitude.mag,
|
64 |
+
"time": origin.time.datetime,
|
65 |
+
"place": description,
|
66 |
+
})
|
67 |
+
|
68 |
+
if not event_list:
|
69 |
+
return pd.DataFrame()
|
70 |
+
|
71 |
+
return pd.DataFrame(event_list)
|
72 |
|
73 |
# --- UI Design ---
|
74 |
+
st.title("π°οΈ Live Earthquake Explorer with ObsPy")
|
75 |
+
st.markdown("Data is fetched in real-time from the **IRIS FDSN** web service.")
|
76 |
|
77 |
with st.sidebar:
|
78 |
st.header("π Search Parameters")
|
79 |
+
|
80 |
+
# Set default dates: today and 30 days prior
|
81 |
today = date.today()
|
82 |
+
thirty_days_ago = today - timedelta(days=30)
|
83 |
+
|
84 |
+
start_date = st.date_input("Start Date", value=thirty_days_ago)
|
85 |
+
end_date = st.date_input("End Date", value=today)
|
86 |
+
|
87 |
+
min_magnitude = st.slider("Minimum Magnitude", 0.0, 10.0, 5.0, 0.1)
|
88 |
+
|
89 |
search_button = st.button("Search Earthquakes", type="primary", use_container_width=True)
|
90 |
|
91 |
# --- Main Content Area ---
|
92 |
if search_button:
|
93 |
+
# Validate date range
|
94 |
+
if start_date > end_date:
|
95 |
+
st.error("Error: Start date cannot be after end date.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
else:
|
97 |
+
with st.spinner(f"Fetching earthquakes from {start_date} to {end_date}..."):
|
98 |
+
df = fetch_live_data(start_date, end_date, min_magnitude)
|
99 |
+
|
100 |
+
if not df.empty:
|
101 |
+
st.success(f"Found {len(df)} earthquakes matching your criteria.")
|
102 |
+
|
103 |
+
# --- Display Summary Metrics ---
|
104 |
+
st.subheader("π Summary Statistics")
|
105 |
+
col1, col2, col3 = st.columns(3)
|
106 |
+
col1.metric("Total Earthquakes", f"{len(df):,}")
|
107 |
+
col2.metric("Largest Magnitude", f"{df['magnitude'].max():.1f}")
|
108 |
+
col3.metric("Deepest Event (km)", f"{df['depth'].max():.1f}")
|
109 |
+
|
110 |
+
st.markdown("---")
|
111 |
+
|
112 |
+
# --- Draw Earthquake Distribution Map ---
|
113 |
+
st.subheader("π Interactive Earthquake Map")
|
114 |
+
st.markdown("Hover over points for details. Circle size represents magnitude.")
|
115 |
+
|
116 |
+
fig = px.scatter_geo(
|
117 |
+
df,
|
118 |
+
lat='latitude',
|
119 |
+
lon='longitude',
|
120 |
+
size='magnitude',
|
121 |
+
color='depth',
|
122 |
+
hover_name='place',
|
123 |
+
hover_data={'latitude': ':.2f', 'longitude': ':.2f', 'time': '|%Y-%m-%d %H:%M', 'magnitude': ':.1f', 'depth': ':.1f km'},
|
124 |
+
projection="natural earth",
|
125 |
+
title=f"Live Earthquakes from {start_date} to {end_date} (Magnitude > {min_magnitude})",
|
126 |
+
color_continuous_scale=px.colors.sequential.Plasma_r
|
127 |
+
)
|
128 |
+
fig.update_layout(margin={"r":0,"t":40,"l":0,"b":0}, coloraxis_colorbar_title_text='Depth (km)')
|
129 |
+
st.plotly_chart(fig, use_container_width=True)
|
130 |
+
|
131 |
+
# --- Show Earthquakes in a Table ---
|
132 |
+
st.subheader("π Detailed Earthquake Data")
|
133 |
+
display_df = df[['time', 'place', 'magnitude', 'depth']].copy()
|
134 |
+
st.dataframe(
|
135 |
+
display_df.sort_values(by='time', ascending=False),
|
136 |
+
use_container_width=True,
|
137 |
+
height=400,
|
138 |
+
column_config={"time": st.column_config.DatetimeColumn("Time (UTC)", format="YYYY-MM-DD HH:mm")}
|
139 |
+
)
|
140 |
+
else:
|
141 |
+
st.warning("No earthquakes were found for the specified criteria. Try expanding the date range or lowering the magnitude.")
|
142 |
else:
|
143 |
+
st.info("Set your desired parameters in the sidebar and click 'Search Earthquakes' to begin.")
|