cwadayi commited on
Commit
ff71114
Β·
verified Β·
1 Parent(s): a91379a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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 Activity Explorer",
10
- page_icon="πŸ—ΊοΈ",
11
  layout="wide",
12
  initial_sidebar_state="expanded"
13
  )
14
 
15
- # --- API Data Fetching Function ---
16
- @st.cache_data(ttl=600)
17
- def fetch_earthquakes(start_date, end_date, min_magnitude):
18
  """
19
- Fetches earthquake data from the specified API endpoint.
20
- Returns a pandas DataFrame or None if an error occurs.
21
  """
22
- API_URL = "https://cwadayi-python-app.hf.space/earthquakes"
23
- params = {
24
- "start_date": start_date,
25
- "end_date": end_date,
26
- "min_magnitude": min_magnitude
27
- }
 
 
28
  try:
29
- response = requests.get(API_URL, params=params, timeout=20)
30
- response.raise_for_status()
31
- data = response.json()
32
- if not data:
33
- return pd.DataFrame()
34
- return pd.DataFrame(data)
35
- except requests.exceptions.RequestException as e:
36
- st.error(f"API Request Failed: {e}")
37
- return None
38
- except ValueError:
39
- st.error("Failed to decode API response. The API might be temporarily down.")
40
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
  # --- UI Design ---
43
- st.title("πŸ—ΊοΈ Global Earthquake Activity Explorer")
44
- st.markdown("Analyze and visualize recent earthquake events around the world.")
45
 
46
  with st.sidebar:
47
  st.header("πŸ” Search Parameters")
 
 
48
  today = date.today()
49
- default_start_date = today - timedelta(days=90)
50
- start_date = st.date_input("Start Date", value=default_start_date, min_value=date(1900, 1, 1), max_value=today, help="Select the beginning of the date range.")
51
- end_date = st.date_input("End Date", value=today, min_value=start_date, max_value=today, help="Select the end of the date range.")
52
- min_magnitude = st.slider("Minimum Magnitude", min_value=0.0, max_value=10.0, value=5.5, step=0.1, help="Drag the slider to set the minimum magnitude for queried earthquakes.")
 
 
 
53
  search_button = st.button("Search Earthquakes", type="primary", use_container_width=True)
54
 
55
  # --- Main Content Area ---
56
  if search_button:
57
- with st.spinner('Fetching earthquake data from the server... Please wait.'):
58
- df = fetch_earthquakes(start_date, end_date, min_magnitude)
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.error("Could not retrieve data. Please try again later.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  else:
143
- st.info("Please set your desired parameters in the sidebar and click 'Search Earthquakes' to begin.")
 
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.")