CCockrum commited on
Commit
dd3973b
·
verified ·
1 Parent(s): 4c45957

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +44 -116
app.py CHANGED
@@ -1,28 +1,19 @@
1
- import streamlit as st
2
  import os
3
- import requests
 
4
  import pandas as pd
5
  import plotly.express as px
6
- from dotenv import load_dotenv
7
- from datetime import datetime, timedelta
8
 
9
  # App title and description
10
- st.title("🌠 NASA Near-Earth Objects Tracker")
11
  st.markdown("""
12
  This application uses NASA's NeoWs (Near Earth Object Web Service) API to retrieve and visualize
13
  information about asteroids and other near-Earth objects.
14
  """)
15
 
16
- # API Configuration
17
- NASA_API_URL = "https://api.nasa.gov/neo/rest/v1/feed"
18
- import os # Ensure this is at the top
19
-
20
- API_KEY = os.getenv("NASA_API_KEY")
21
- if not API_KEY:
22
- st.error("🚨 NASA_API_KEY environment variable is not set.")
23
- st.stop()
24
-
25
-
26
  # Date selection
27
  st.sidebar.header("Search Parameters")
28
  today = datetime.now()
@@ -35,52 +26,33 @@ end_date = st.sidebar.date_input("End Date", default_end_date)
35
  # Validate date range
36
  date_diff = (end_date - start_date).days
37
  if date_diff > 7:
38
- st.warning("⚠️ NASA API limits date range to 7 days or less. Adjusting to a 7-day period.")
39
  end_date = start_date + timedelta(days=7)
40
 
41
- # Function to fetch data from NASA API
42
- def fetch_asteroid_data(start_date, end_date, api_key):
43
- params = {
44
- "start_date": start_date.strftime("%Y-%m-%d"),
45
- "end_date": end_date.strftime("%Y-%m-%d"),
46
- "api_key": api_key
47
- }
48
-
49
- with st.spinner("Fetching asteroid data from NASA..."):
50
- try:
51
- response = requests.get(NASA_API_URL, params=params)
52
- response.raise_for_status() # Raise an exception for HTTP errors
53
- return response.json()
54
- except requests.exceptions.RequestException as e:
55
- st.error(f"Error accessing NASA API: {e}")
56
- return None
57
-
58
  # Search button
59
  if st.sidebar.button("Search Asteroids"):
60
- # Fetch data
61
- data = fetch_asteroid_data(start_date, end_date, API_KEY)
62
-
63
  if data:
64
- # Store data in session state
65
  st.session_state.asteroid_data = data
66
  st.session_state.searched = True
67
  else:
68
- st.error("Failed to fetch asteroid data. Please check your API key and try again.")
69
 
70
  # Display results if search was performed
71
  if 'searched' in st.session_state and st.session_state.searched:
72
  data = st.session_state.asteroid_data
73
-
74
- # Extract asteroid count
75
  element_count = data.get('element_count', 0)
76
  st.success(f"Found {element_count} near-Earth objects between {start_date} and {end_date}")
77
-
78
- # Process and organize data
79
  neo_data = data.get('near_earth_objects', {})
80
-
81
  all_asteroids = []
 
82
  for date, asteroids in neo_data.items():
83
  for asteroid in asteroids:
 
 
 
84
  asteroid_info = {
85
  'id': asteroid['id'],
86
  'name': asteroid['name'],
@@ -93,53 +65,34 @@ if 'searched' in st.session_state and st.session_state.searched:
93
  'relative_velocity_kph': float(asteroid['close_approach_data'][0]['relative_velocity']['kilometers_per_hour'])
94
  }
95
  all_asteroids.append(asteroid_info)
96
-
97
- # Convert to DataFrame for easier manipulation
98
  df = pd.DataFrame(all_asteroids)
99
-
100
- # Add average diameter column
101
  df['avg_diameter_km'] = (df['diameter_min_km'] + df['diameter_max_km']) / 2
102
-
103
- # Display summary statistics
104
  st.header("Summary Statistics")
105
  col1, col2, col3 = st.columns(3)
106
-
107
  with col1:
108
  st.metric("Total Asteroids", len(df))
109
-
110
  with col2:
111
  hazardous_count = df['is_hazardous'].sum()
112
  st.metric("Potentially Hazardous", f"{hazardous_count} ({hazardous_count/len(df)*100:.1f}%)")
113
-
114
  with col3:
115
  st.metric("Avg. Size", f"{df['avg_diameter_km'].mean():.2f} km")
116
-
117
- # Visualizations
118
  st.header("Visualizations")
119
-
120
  viz_tab1, viz_tab2 = st.tabs(["Size Distribution", "Miss Distance"])
121
-
122
  with viz_tab1:
123
- # Size distribution chart
124
  fig1 = px.histogram(
125
- df,
126
- x="avg_diameter_km",
127
- color="is_hazardous",
128
  title="Size Distribution of Near-Earth Objects",
129
  labels={"avg_diameter_km": "Average Diameter (km)", "is_hazardous": "Potentially Hazardous"},
130
  color_discrete_map={True: "red", False: "green"}
131
  )
132
  st.plotly_chart(fig1, use_container_width=True)
133
-
134
  with viz_tab2:
135
- # Miss distance scatter plot
136
  fig2 = px.scatter(
137
- df,
138
- x="miss_distance_km",
139
- y="avg_diameter_km",
140
- color="is_hazardous",
141
- size="relative_velocity_kph",
142
- hover_name="name",
143
  title="Miss Distance vs. Size (with velocity)",
144
  labels={
145
  "miss_distance_km": "Miss Distance (km)",
@@ -151,40 +104,26 @@ if 'searched' in st.session_state and st.session_state.searched:
151
  )
152
  fig2.update_layout(xaxis_type="log")
153
  st.plotly_chart(fig2, use_container_width=True)
154
-
155
- # Detailed asteroid data
156
  st.header("Detailed Asteroid Data")
157
-
158
- # Filter options
159
  st.subheader("Filters")
160
  col1, col2 = st.columns(2)
161
-
162
  with col1:
163
  show_hazardous = st.checkbox("Show only hazardous asteroids", False)
164
-
165
  with col2:
166
  size_threshold = st.slider("Minimum size (km)", 0.0, max(df['avg_diameter_km']), 0.0, 0.01)
167
-
168
- # Apply filters
169
  filtered_df = df.copy()
170
  if show_hazardous:
171
  filtered_df = filtered_df[filtered_df['is_hazardous'] == True]
172
-
173
  filtered_df = filtered_df[filtered_df['avg_diameter_km'] >= size_threshold]
174
-
175
- # Sort options
176
- sort_by = st.selectbox(
177
- "Sort by",
178
- ["close_approach_date", "name", "avg_diameter_km", "miss_distance_km", "relative_velocity_kph"]
179
- )
180
-
181
  sort_order = st.radio("Sort order", ["Ascending", "Descending"], horizontal=True)
182
-
183
- # Apply sorting
184
  ascending = sort_order == "Ascending"
185
  filtered_df = filtered_df.sort_values(by=sort_by, ascending=ascending)
186
-
187
- # Display dataframe with key information
188
  display_df = filtered_df[[
189
  'name', 'close_approach_date', 'avg_diameter_km',
190
  'miss_distance_km', 'relative_velocity_kph', 'is_hazardous'
@@ -196,43 +135,30 @@ if 'searched' in st.session_state and st.session_state.searched:
196
  'relative_velocity_kph': 'Velocity (km/h)',
197
  'is_hazardous': 'Hazardous'
198
  })
199
-
200
  st.dataframe(display_df, use_container_width=True)
201
-
202
- # Asteroid details expander
203
  st.subheader("Individual Asteroid Details")
204
-
205
- # Allow user to select an asteroid for detailed view
206
  selected_asteroid = st.selectbox("Select an asteroid", filtered_df['name'].tolist())
207
-
208
  if selected_asteroid:
209
  asteroid_details = filtered_df[filtered_df['name'] == selected_asteroid].iloc[0]
210
-
211
- st.subheader(f"🌑 {selected_asteroid}")
212
-
213
  col1, col2 = st.columns(2)
214
-
215
  with col1:
216
  st.write("**ID:**", asteroid_details['id'])
217
  st.write("**Approach Date:**", asteroid_details['close_approach_date'])
218
- st.write("**Hazardous:**", "Yes ⚠️" if asteroid_details['is_hazardous'] else "No ")
219
-
220
  with col2:
221
  st.write("**Diameter Range:**", f"{asteroid_details['diameter_min_km']:.3f} - {asteroid_details['diameter_max_km']:.3f} km")
222
  st.write("**Miss Distance:**", f"{asteroid_details['miss_distance_km']:,.0f} km")
223
  st.write("**Relative Velocity:**", f"{asteroid_details['relative_velocity_kph']:,.0f} km/h")
224
-
225
- # Create a gauge-like visualization for the hazard level
226
  hazard_level = 0
227
  if asteroid_details['is_hazardous']:
228
- # Calculate hazard level based on size and miss distance
229
- size_factor = min(asteroid_details['avg_diameter_km'] / 0.5, 1) # Normalize by 0.5km
230
- distance_factor = min(1000000 / asteroid_details['miss_distance_km'], 1) # Normalize by 1M km
231
  hazard_level = (size_factor * 0.7 + distance_factor * 0.3) * 100
232
-
233
  st.progress(int(hazard_level), text=f"Relative Hazard Level: {hazard_level:.1f}%")
234
-
235
- # Add some context about the asteroid
236
  st.write("### Context")
237
  if hazard_level > 70:
238
  st.warning("This asteroid is classified as potentially hazardous and is relatively large and close.")
@@ -241,17 +167,14 @@ if 'searched' in st.session_state and st.session_state.searched:
241
  else:
242
  st.success("This asteroid is not considered hazardous and poses no risk to Earth.")
243
 
244
- # Add information about the NASA API
245
  st.sidebar.markdown("---")
246
  st.sidebar.markdown("""
247
  ### About NASA NeoWs API
248
- The [Near Earth Object Web Service](https://api.nasa.gov) is a RESTful web service for near earth asteroid information.
249
- This API provides data on asteroids based on their closest approach date to Earth.
250
-
251
- To get your own API key, visit [api.nasa.gov](https://api.nasa.gov).
252
  """)
253
 
254
- # Add deployment instructions
255
  st.sidebar.markdown("---")
256
  st.sidebar.markdown("""
257
  ### Deployment Instructions
@@ -262,6 +185,11 @@ st.sidebar.markdown("""
262
  requests
263
  pandas
264
  plotly
 
265
  ```
266
- 3. Upload to Hugging Face Spaces
267
- """)
 
 
 
 
 
1
+ # app.py
2
  import os
3
+ from datetime import datetime, timedelta
4
+
5
  import pandas as pd
6
  import plotly.express as px
7
+ import streamlit as st
8
+ from fetch import fetch_asteroid_data
9
 
10
  # App title and description
11
+ st.title("\U0001F320 NASA Near-Earth Objects Tracker")
12
  st.markdown("""
13
  This application uses NASA's NeoWs (Near Earth Object Web Service) API to retrieve and visualize
14
  information about asteroids and other near-Earth objects.
15
  """)
16
 
 
 
 
 
 
 
 
 
 
 
17
  # Date selection
18
  st.sidebar.header("Search Parameters")
19
  today = datetime.now()
 
26
  # Validate date range
27
  date_diff = (end_date - start_date).days
28
  if date_diff > 7:
29
+ st.warning("\u26a0\ufe0f NASA API limits date range to 7 days or less. Adjusting to a 7-day period.")
30
  end_date = start_date + timedelta(days=7)
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  # Search button
33
  if st.sidebar.button("Search Asteroids"):
34
+ data = fetch_asteroid_data(start_date, end_date)
35
+
 
36
  if data:
 
37
  st.session_state.asteroid_data = data
38
  st.session_state.searched = True
39
  else:
40
+ st.error("Failed to fetch asteroid data. Please check your environment setup.")
41
 
42
  # Display results if search was performed
43
  if 'searched' in st.session_state and st.session_state.searched:
44
  data = st.session_state.asteroid_data
 
 
45
  element_count = data.get('element_count', 0)
46
  st.success(f"Found {element_count} near-Earth objects between {start_date} and {end_date}")
47
+
 
48
  neo_data = data.get('near_earth_objects', {})
 
49
  all_asteroids = []
50
+
51
  for date, asteroids in neo_data.items():
52
  for asteroid in asteroids:
53
+ if not asteroid['close_approach_data']:
54
+ continue
55
+
56
  asteroid_info = {
57
  'id': asteroid['id'],
58
  'name': asteroid['name'],
 
65
  'relative_velocity_kph': float(asteroid['close_approach_data'][0]['relative_velocity']['kilometers_per_hour'])
66
  }
67
  all_asteroids.append(asteroid_info)
68
+
 
69
  df = pd.DataFrame(all_asteroids)
 
 
70
  df['avg_diameter_km'] = (df['diameter_min_km'] + df['diameter_max_km']) / 2
71
+
 
72
  st.header("Summary Statistics")
73
  col1, col2, col3 = st.columns(3)
 
74
  with col1:
75
  st.metric("Total Asteroids", len(df))
 
76
  with col2:
77
  hazardous_count = df['is_hazardous'].sum()
78
  st.metric("Potentially Hazardous", f"{hazardous_count} ({hazardous_count/len(df)*100:.1f}%)")
 
79
  with col3:
80
  st.metric("Avg. Size", f"{df['avg_diameter_km'].mean():.2f} km")
81
+
 
82
  st.header("Visualizations")
 
83
  viz_tab1, viz_tab2 = st.tabs(["Size Distribution", "Miss Distance"])
 
84
  with viz_tab1:
 
85
  fig1 = px.histogram(
86
+ df, x="avg_diameter_km", color="is_hazardous",
 
 
87
  title="Size Distribution of Near-Earth Objects",
88
  labels={"avg_diameter_km": "Average Diameter (km)", "is_hazardous": "Potentially Hazardous"},
89
  color_discrete_map={True: "red", False: "green"}
90
  )
91
  st.plotly_chart(fig1, use_container_width=True)
 
92
  with viz_tab2:
 
93
  fig2 = px.scatter(
94
+ df, x="miss_distance_km", y="avg_diameter_km", color="is_hazardous",
95
+ size="relative_velocity_kph", hover_name="name",
 
 
 
 
96
  title="Miss Distance vs. Size (with velocity)",
97
  labels={
98
  "miss_distance_km": "Miss Distance (km)",
 
104
  )
105
  fig2.update_layout(xaxis_type="log")
106
  st.plotly_chart(fig2, use_container_width=True)
107
+
 
108
  st.header("Detailed Asteroid Data")
 
 
109
  st.subheader("Filters")
110
  col1, col2 = st.columns(2)
 
111
  with col1:
112
  show_hazardous = st.checkbox("Show only hazardous asteroids", False)
 
113
  with col2:
114
  size_threshold = st.slider("Minimum size (km)", 0.0, max(df['avg_diameter_km']), 0.0, 0.01)
115
+
 
116
  filtered_df = df.copy()
117
  if show_hazardous:
118
  filtered_df = filtered_df[filtered_df['is_hazardous'] == True]
 
119
  filtered_df = filtered_df[filtered_df['avg_diameter_km'] >= size_threshold]
120
+
121
+ sort_by = st.selectbox("Sort by", [
122
+ "close_approach_date", "name", "avg_diameter_km", "miss_distance_km", "relative_velocity_kph"])
 
 
 
 
123
  sort_order = st.radio("Sort order", ["Ascending", "Descending"], horizontal=True)
 
 
124
  ascending = sort_order == "Ascending"
125
  filtered_df = filtered_df.sort_values(by=sort_by, ascending=ascending)
126
+
 
127
  display_df = filtered_df[[
128
  'name', 'close_approach_date', 'avg_diameter_km',
129
  'miss_distance_km', 'relative_velocity_kph', 'is_hazardous'
 
135
  'relative_velocity_kph': 'Velocity (km/h)',
136
  'is_hazardous': 'Hazardous'
137
  })
 
138
  st.dataframe(display_df, use_container_width=True)
139
+
 
140
  st.subheader("Individual Asteroid Details")
 
 
141
  selected_asteroid = st.selectbox("Select an asteroid", filtered_df['name'].tolist())
 
142
  if selected_asteroid:
143
  asteroid_details = filtered_df[filtered_df['name'] == selected_asteroid].iloc[0]
144
+ st.subheader(f"\U0001F311 {selected_asteroid}")
 
 
145
  col1, col2 = st.columns(2)
 
146
  with col1:
147
  st.write("**ID:**", asteroid_details['id'])
148
  st.write("**Approach Date:**", asteroid_details['close_approach_date'])
149
+ st.write("**Hazardous:**", "Yes \u26a0\ufe0f" if asteroid_details['is_hazardous'] else "No \u2713")
 
150
  with col2:
151
  st.write("**Diameter Range:**", f"{asteroid_details['diameter_min_km']:.3f} - {asteroid_details['diameter_max_km']:.3f} km")
152
  st.write("**Miss Distance:**", f"{asteroid_details['miss_distance_km']:,.0f} km")
153
  st.write("**Relative Velocity:**", f"{asteroid_details['relative_velocity_kph']:,.0f} km/h")
154
+
 
155
  hazard_level = 0
156
  if asteroid_details['is_hazardous']:
157
+ size_factor = min(asteroid_details['avg_diameter_km'] / 0.5, 1)
158
+ distance_factor = min(1000000 / asteroid_details['miss_distance_km'], 1)
 
159
  hazard_level = (size_factor * 0.7 + distance_factor * 0.3) * 100
 
160
  st.progress(int(hazard_level), text=f"Relative Hazard Level: {hazard_level:.1f}%")
161
+
 
162
  st.write("### Context")
163
  if hazard_level > 70:
164
  st.warning("This asteroid is classified as potentially hazardous and is relatively large and close.")
 
167
  else:
168
  st.success("This asteroid is not considered hazardous and poses no risk to Earth.")
169
 
170
+ # Sidebar info
171
  st.sidebar.markdown("---")
172
  st.sidebar.markdown("""
173
  ### About NASA NeoWs API
174
+ The [Near Earth Object Web Service](https://api.nasa.gov) provides asteroid data based on closest approach to Earth.
175
+ To get an API key, visit [api.nasa.gov](https://api.nasa.gov).
 
 
176
  """)
177
 
 
178
  st.sidebar.markdown("---")
179
  st.sidebar.markdown("""
180
  ### Deployment Instructions
 
185
  requests
186
  pandas
187
  plotly
188
+ python-dotenv
189
  ```
190
+ 3. Add `.env` file with:
191
+ ```
192
+ NASA_API_KEY=your_key_here
193
+ ```
194
+ 4. Deploy to Streamlit Cloud or similar
195
+ """)