Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -6,287 +6,283 @@ import pandas as pd
|
|
6 |
import traceback
|
7 |
import plotly.express as px
|
8 |
import plotly.graph_objects as go
|
|
|
|
|
|
|
|
|
|
|
9 |
|
10 |
class NasaSsdCneosApi:
|
11 |
def __init__(self):
|
12 |
self.fireball_url = "https://ssd-api.jpl.nasa.gov/fireball.api"
|
13 |
self.ca_url = "https://ssd-api.jpl.nasa.gov/cad.api"
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
18 |
try:
|
19 |
-
|
20 |
-
if
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
response.raise_for_status()
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
return None
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
params = {'limit': limit, 'dist-max': dist_max, 'date-min': date_min,
|
38 |
-
'date-max': date_max, 'h-min': h_min, 'h-max': h_max,
|
39 |
-
'v-inf-min': v_inf_min, 'v-inf-max': v_inf_max, 'sort': 'date'}
|
40 |
-
params = {k: v for k, v in params.items() if v is not None}
|
41 |
-
|
42 |
-
response = requests.get(self.ca_url, params=params)
|
43 |
-
response.raise_for_status()
|
44 |
-
return response.json()
|
45 |
-
except Exception as e:
|
46 |
-
print("Close Approaches API Error:", e)
|
47 |
-
traceback.print_exc()
|
48 |
return None
|
49 |
-
|
50 |
-
def get_nea_data(self, des=None, spk_id=None, h_max=None):
|
51 |
-
try:
|
52 |
-
# Build query parameter for NEAs - select asteroid with NEA flag
|
53 |
-
query_params = {
|
54 |
-
'sb-nea': 'true' # Filter for Near-Earth Asteroids
|
55 |
-
}
|
56 |
|
57 |
-
if des:
|
58 |
-
query_params['sb-spk'] = des
|
59 |
-
if spk_id:
|
60 |
-
query_params['sb-spkid'] = spk_id
|
61 |
-
if h_max:
|
62 |
-
query_params['sb-h-max'] = h_max
|
63 |
-
|
64 |
-
# Add fields to return
|
65 |
-
query_params['fields'] = 'spkid,full_name,pdes,neo,H,G,diameter,extent,albedo,rot_per,GM,BV,UB,IR,spec_B,spec_T,H_sigma,diameter_sigma,orbit_id,epoch,epoch_mjd,epoch_cal,a,e,i,om,w,ma,ad,n,tp,tp_cal,per,per_y,q,moid,moid_ld,moid_jup'
|
66 |
-
query_params['limit'] = 100 # Set a reasonable limit
|
67 |
-
|
68 |
-
response = requests.get(self.nea_url, params=query_params)
|
69 |
-
response.raise_for_status()
|
70 |
-
return response.json()
|
71 |
except Exception as e:
|
72 |
-
|
73 |
traceback.print_exc()
|
74 |
return None
|
75 |
|
76 |
-
def
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
|
|
|
|
|
|
|
|
81 |
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
|
90 |
def format_response(self, data, format_type):
|
|
|
91 |
try:
|
92 |
if not data:
|
|
|
93 |
return None
|
94 |
|
95 |
-
|
|
|
96 |
rows = data.get('data')
|
97 |
|
98 |
if not fields or not rows:
|
|
|
|
|
99 |
return None
|
100 |
|
|
|
101 |
df = pd.DataFrame([dict(zip(fields, row)) for row in rows])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
|
|
103 |
if format_type == 'fireballs':
|
104 |
-
|
105 |
-
|
106 |
-
'
|
107 |
-
'
|
|
|
|
|
|
|
|
|
108 |
'vel': 'Velocity (km/s)'
|
109 |
-
}
|
|
|
|
|
|
|
110 |
|
111 |
elif format_type == 'close_approaches':
|
112 |
-
|
113 |
-
'des': 'Object',
|
114 |
-
'
|
115 |
-
'
|
|
|
|
|
|
|
|
|
116 |
'h': 'H (mag)'
|
117 |
-
})
|
118 |
-
|
119 |
-
elif format_type == 'nea':
|
120 |
-
name_columns = {
|
121 |
-
'full_name': 'Full Name', 'pdes': 'Designation',
|
122 |
-
'H': 'Absolute Magnitude (mag)', 'diameter': 'Diameter (km)',
|
123 |
-
'q': 'Perihelion (au)', 'ad': 'Aphelion (au)',
|
124 |
-
'i': 'Inclination (deg)', 'e': 'Eccentricity',
|
125 |
-
'moid': 'MOID (au)', 'moid_ld': 'MOID (LD)'
|
126 |
}
|
127 |
-
|
128 |
-
|
129 |
-
return df.rename(columns=valid_columns)
|
130 |
-
|
131 |
-
elif format_type == 'scout':
|
132 |
-
# Handle Scout API response - column names may vary
|
133 |
-
# Adjust these column mappings based on actual response structure
|
134 |
-
if 'score' in df.columns:
|
135 |
-
df = df.rename(columns={
|
136 |
-
'object': 'Object', 'score': 'Rating',
|
137 |
-
'diameter': 'Diameter (m)', 'ca_dist': 'Close Approach',
|
138 |
-
'nobs': 'Observations'
|
139 |
-
})
|
140 |
-
return df
|
141 |
|
142 |
return df
|
|
|
143 |
except Exception as e:
|
144 |
-
|
145 |
traceback.print_exc()
|
146 |
return None
|
147 |
|
148 |
|
149 |
-
# Gradio Interface Functions
|
150 |
|
151 |
def fetch_fireballs(limit, date_min, energy_min):
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
date_min = date_min if date_min else None
|
156 |
-
energy_min = float(energy_min) if energy_min else None
|
157 |
-
|
158 |
-
data = api.get_fireballs(
|
159 |
-
limit=int(limit),
|
160 |
-
date_min=date_min,
|
161 |
-
energy_min=energy_min
|
162 |
-
)
|
163 |
-
|
164 |
-
df = api.format_response(data, 'fireballs')
|
165 |
-
if df is None or df.empty:
|
166 |
-
return "No data available", None
|
167 |
-
|
168 |
-
# Create world map of fireballs
|
169 |
-
if 'Latitude' in df.columns and 'Longitude' in df.columns:
|
170 |
-
fig = px.scatter_geo(df,
|
171 |
-
lat='Latitude',
|
172 |
-
lon='Longitude',
|
173 |
-
size='Energy (kt)',
|
174 |
-
hover_name='Date/Time',
|
175 |
-
projection='natural earth',
|
176 |
-
title='Fireball Events')
|
177 |
|
178 |
-
|
179 |
-
|
180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
181 |
|
182 |
def fetch_close_approaches(limit, dist_max, date_min, date_max, h_min, h_max, v_inf_min, v_inf_max):
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
dist_max = float(dist_max) if dist_max else None
|
187 |
-
date_min = date_min if date_min else None
|
188 |
-
date_max = date_max if date_max else None
|
189 |
-
h_min = float(h_min) if h_min else None
|
190 |
-
h_max = float(h_max) if h_max else None
|
191 |
-
v_inf_min = float(v_inf_min) if v_inf_min else None
|
192 |
-
v_inf_max = float(v_inf_max) if v_inf_max else None
|
193 |
-
|
194 |
-
data = api.get_close_approaches(
|
195 |
-
limit=int(limit),
|
196 |
-
dist_max=dist_max,
|
197 |
-
date_min=date_min,
|
198 |
-
date_max=date_max,
|
199 |
-
h_min=h_min,
|
200 |
-
h_max=h_max,
|
201 |
-
v_inf_min=v_inf_min,
|
202 |
-
v_inf_max=v_inf_max
|
203 |
-
)
|
204 |
-
|
205 |
-
df = api.format_response(data, 'close_approaches')
|
206 |
-
if df is None or df.empty:
|
207 |
-
return "No data available", None
|
208 |
-
|
209 |
-
# Create scatter plot of distance vs velocity
|
210 |
-
fig = px.scatter(df,
|
211 |
-
x='Nominal Distance (au)',
|
212 |
-
y='Velocity (km/s)',
|
213 |
-
hover_name='Object',
|
214 |
-
size='H (mag)',
|
215 |
-
color='H (mag)',
|
216 |
-
title='Close Approaches - Distance vs Velocity')
|
217 |
-
|
218 |
-
return df, fig
|
219 |
-
|
220 |
-
def fetch_nea_data(des, spk_id, h_max):
|
221 |
-
api = NasaSsdCneosApi()
|
222 |
-
|
223 |
-
# Convert empty strings to None
|
224 |
-
des = des if des else None
|
225 |
-
spk_id = spk_id if spk_id else None
|
226 |
-
h_max = float(h_max) if h_max else None
|
227 |
-
|
228 |
-
data = api.get_nea_data(
|
229 |
-
des=des,
|
230 |
-
spk_id=spk_id,
|
231 |
-
h_max=h_max
|
232 |
-
)
|
233 |
-
|
234 |
-
df = api.format_response(data, 'nea')
|
235 |
-
if df is None or df.empty:
|
236 |
-
return "No data available", None
|
237 |
-
|
238 |
-
# Create a scatter plot of perihelion vs aphelion colored by inclination
|
239 |
-
if not df.empty and 'Perihelion (au)' in df.columns and 'Aphelion (au)' in df.columns:
|
240 |
-
fig = px.scatter(df,
|
241 |
-
x='Perihelion (au)',
|
242 |
-
y='Aphelion (au)',
|
243 |
-
hover_name='Designation' if 'Designation' in df.columns else None,
|
244 |
-
color='Inclination (deg)' if 'Inclination (deg)' in df.columns else None,
|
245 |
-
size='Diameter (km)' if 'Diameter (km)' in df.columns else None,
|
246 |
-
title='NEA Orbital Parameters')
|
247 |
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
nea_comet=nea_comet
|
258 |
-
)
|
259 |
-
|
260 |
-
df = api.format_response(data, 'scout')
|
261 |
-
if df is None or df.empty:
|
262 |
-
return "No data available", None
|
263 |
-
|
264 |
-
# Create a scatter plot based on available columns
|
265 |
-
if not df.empty:
|
266 |
-
# Use columns that are available in the dataframe
|
267 |
-
x_col = 'Diameter (m)' if 'Diameter (m)' in df.columns else df.columns[0]
|
268 |
-
y_col = 'Close Approach' if 'Close Approach' in df.columns else df.columns[1]
|
269 |
-
hover_col = 'Object' if 'Object' in df.columns else None
|
270 |
-
color_col = 'Rating' if 'Rating' in df.columns else None
|
271 |
-
size_col = 'Observations' if 'Observations' in df.columns else None
|
272 |
|
273 |
-
|
274 |
-
|
275 |
-
y=y_col,
|
276 |
-
hover_name=hover_col,
|
277 |
-
color=color_col,
|
278 |
-
size=size_col,
|
279 |
-
title='Scout Objects')
|
280 |
|
281 |
-
|
282 |
-
|
283 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
284 |
|
285 |
# Create Gradio interface
|
286 |
with gr.Blocks(title="NASA SSD/CNEOS API Explorer") as demo:
|
287 |
gr.Markdown("# NASA SSD/CNEOS API Explorer")
|
288 |
gr.Markdown("Access data from NASA's Center for Near Earth Object Studies")
|
289 |
|
|
|
|
|
|
|
290 |
with gr.Tab("Fireballs"):
|
291 |
gr.Markdown("### Fireball Events")
|
292 |
gr.Markdown("Get information about recent fireball events detected by sensors.")
|
@@ -300,7 +296,9 @@ with gr.Blocks(title="NASA SSD/CNEOS API Explorer") as demo:
|
|
300 |
fireball_results = gr.DataFrame(label="Fireball Results")
|
301 |
fireball_map = gr.Plot(label="Fireball Map")
|
302 |
|
303 |
-
fireball_submit.click(fetch_fireballs,
|
|
|
|
|
304 |
|
305 |
with gr.Tab("Close Approaches"):
|
306 |
gr.Markdown("### Close Approaches")
|
@@ -324,42 +322,13 @@ with gr.Blocks(title="NASA SSD/CNEOS API Explorer") as demo:
|
|
324 |
inputs=[ca_limit, ca_dist_max, ca_date_min, ca_date_max, ca_h_min, ca_h_max, ca_v_min, ca_v_max],
|
325 |
outputs=[ca_results, ca_plot])
|
326 |
|
327 |
-
with gr.Tab("NEA Data"):
|
328 |
-
gr.Markdown("### Near-Earth Asteroid Data")
|
329 |
-
gr.Markdown("Get information about specific near-Earth asteroids.")
|
330 |
-
with gr.Row():
|
331 |
-
with gr.Column():
|
332 |
-
nea_des = gr.Textbox(label="Designation", placeholder="e.g. 2020 SW")
|
333 |
-
nea_spk = gr.Textbox(label="SPK-ID", placeholder="e.g. 54101815")
|
334 |
-
nea_h_max = gr.Textbox(label="Maximum H (mag)", placeholder="e.g. 25")
|
335 |
-
nea_submit = gr.Button("Fetch NEA Data")
|
336 |
-
with gr.Column():
|
337 |
-
nea_results = gr.DataFrame(label="NEA Results")
|
338 |
-
nea_plot = gr.Plot(label="NEA Orbital Parameters")
|
339 |
-
|
340 |
-
nea_submit.click(fetch_nea_data, inputs=[nea_des, nea_spk, nea_h_max], outputs=[nea_results, nea_plot])
|
341 |
-
|
342 |
-
with gr.Tab("Scout Data"):
|
343 |
-
gr.Markdown("### Scout System Data")
|
344 |
-
gr.Markdown("Get information about newly discovered objects from NASA's Scout system.")
|
345 |
-
with gr.Row():
|
346 |
-
with gr.Column():
|
347 |
-
scout_limit = gr.Slider(minimum=1, maximum=100, value=10, step=1, label="Limit")
|
348 |
-
scout_type = gr.Radio(["NEA", "comet"], label="Object Type", value="NEA")
|
349 |
-
scout_submit = gr.Button("Fetch Scout Data")
|
350 |
-
with gr.Column():
|
351 |
-
scout_results = gr.DataFrame(label="Scout Results")
|
352 |
-
scout_plot = gr.Plot(label="Scout Objects Plot")
|
353 |
-
|
354 |
-
scout_submit.click(fetch_scout_data, inputs=[scout_limit, scout_type], outputs=[scout_results, scout_plot])
|
355 |
-
|
356 |
gr.Markdown("### About")
|
357 |
gr.Markdown("""
|
358 |
This application provides access to NASA's Solar System Dynamics (SSD) and Center for Near Earth Object Studies (CNEOS) API.
|
359 |
|
360 |
Data is retrieved in real-time from NASA's servers. All data is courtesy of NASA/JPL-Caltech.
|
361 |
|
362 |
-
Created
|
363 |
""")
|
364 |
|
365 |
# Create requirements.txt file
|
|
|
6 |
import traceback
|
7 |
import plotly.express as px
|
8 |
import plotly.graph_objects as go
|
9 |
+
import logging
|
10 |
+
|
11 |
+
# Set up logging
|
12 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
13 |
+
logger = logging.getLogger(__name__)
|
14 |
|
15 |
class NasaSsdCneosApi:
|
16 |
def __init__(self):
|
17 |
self.fireball_url = "https://ssd-api.jpl.nasa.gov/fireball.api"
|
18 |
self.ca_url = "https://ssd-api.jpl.nasa.gov/cad.api"
|
19 |
+
|
20 |
+
# For debugging - print response details if True
|
21 |
+
self.debug_mode = True
|
22 |
+
|
23 |
+
def _make_api_request(self, url, params, name="API"):
|
24 |
+
"""Generic API request handler with error handling and debugging"""
|
25 |
try:
|
26 |
+
# Clean up None values and empty strings
|
27 |
+
clean_params = {k: v for k, v in params.items() if v is not None and v != ""}
|
28 |
+
|
29 |
+
# Log the request in debug mode
|
30 |
+
if self.debug_mode:
|
31 |
+
logger.info(f"{name} Request - URL: {url}")
|
32 |
+
logger.info(f"{name} Request - Params: {clean_params}")
|
33 |
+
|
34 |
+
# Make the request
|
35 |
+
response = requests.get(url, params=clean_params)
|
36 |
+
|
37 |
+
# Log the response status and content in debug mode
|
38 |
+
if self.debug_mode:
|
39 |
+
logger.info(f"{name} Response - Status: {response.status_code}")
|
40 |
+
logger.info(f"{name} Response - Content Preview: {response.text[:500]}...")
|
41 |
+
|
42 |
+
# Check for HTTP errors
|
43 |
response.raise_for_status()
|
44 |
+
|
45 |
+
# Parse JSON response
|
46 |
+
data = response.json()
|
47 |
+
|
48 |
+
# Check for API-specific error messages
|
49 |
+
if isinstance(data, dict) and "error" in data:
|
50 |
+
logger.error(f"{name} API Error: {data['error']}")
|
51 |
+
return None
|
52 |
+
|
53 |
+
return data
|
54 |
+
|
55 |
+
except requests.exceptions.HTTPError as http_err:
|
56 |
+
logger.error(f"{name} HTTP Error: {http_err}")
|
57 |
+
if self.debug_mode and hasattr(http_err, 'response'):
|
58 |
+
logger.error(f"Response content: {http_err.response.text}")
|
59 |
return None
|
60 |
+
|
61 |
+
except json.JSONDecodeError as json_err:
|
62 |
+
logger.error(f"{name} JSON Decode Error: {json_err}")
|
63 |
+
if self.debug_mode and 'response' in locals():
|
64 |
+
logger.error(f"Raw response: {response.text}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
67 |
except Exception as e:
|
68 |
+
logger.error(f"{name} General Error: {e}")
|
69 |
traceback.print_exc()
|
70 |
return None
|
71 |
|
72 |
+
def get_fireballs(self, limit=10, date_min=None, energy_min=None):
|
73 |
+
"""Get fireball events from NASA CNEOS API"""
|
74 |
+
params = {'limit': limit}
|
75 |
+
if date_min:
|
76 |
+
params['date-min'] = date_min
|
77 |
+
if energy_min:
|
78 |
+
params['energy-min'] = energy_min
|
79 |
+
|
80 |
+
return self._make_api_request(self.fireball_url, params, "Fireball API")
|
81 |
|
82 |
+
def get_close_approaches(self, dist_max=None, date_min=None, date_max=None,
|
83 |
+
h_min=None, h_max=None, v_inf_min=None, v_inf_max=None,
|
84 |
+
limit=10):
|
85 |
+
"""Get close approach data from NASA CNEOS API"""
|
86 |
+
params = {
|
87 |
+
'limit': limit,
|
88 |
+
'dist-max': dist_max,
|
89 |
+
'date-min': date_min,
|
90 |
+
'date-max': date_max,
|
91 |
+
'h-min': h_min,
|
92 |
+
'h-max': h_max,
|
93 |
+
'v-inf-min': v_inf_min,
|
94 |
+
'v-inf-max': v_inf_max,
|
95 |
+
'sort': 'date'
|
96 |
+
}
|
97 |
+
|
98 |
+
return self._make_api_request(self.ca_url, params, "Close Approaches API")
|
99 |
|
100 |
def format_response(self, data, format_type):
|
101 |
+
"""Format JSON response from API into a pandas DataFrame"""
|
102 |
try:
|
103 |
if not data:
|
104 |
+
logger.warning(f"No data received for {format_type} format")
|
105 |
return None
|
106 |
|
107 |
+
# Some API responses use 'signature' field instead of 'fields'
|
108 |
+
fields = data.get('fields', data.get('signature'))
|
109 |
rows = data.get('data')
|
110 |
|
111 |
if not fields or not rows:
|
112 |
+
logger.warning(f"Missing fields or data rows for {format_type} format")
|
113 |
+
logger.debug(f"Data structure: {data.keys()}")
|
114 |
return None
|
115 |
|
116 |
+
# Create DataFrame from the API response
|
117 |
df = pd.DataFrame([dict(zip(fields, row)) for row in rows])
|
118 |
+
|
119 |
+
if df.empty:
|
120 |
+
logger.warning(f"Empty DataFrame created for {format_type}")
|
121 |
+
return None
|
122 |
+
|
123 |
+
# Log available columns for debugging
|
124 |
+
if self.debug_mode:
|
125 |
+
logger.info(f"Available columns in {format_type} response: {df.columns.tolist()}")
|
126 |
|
127 |
+
# Format based on data type
|
128 |
if format_type == 'fireballs':
|
129 |
+
# Only rename columns that exist in the DataFrame
|
130 |
+
rename_map = {
|
131 |
+
'date': 'Date/Time',
|
132 |
+
'energy': 'Energy (kt)',
|
133 |
+
'impact-e': 'Impact Energy (10^10 J)',
|
134 |
+
'lat': 'Latitude',
|
135 |
+
'lon': 'Longitude',
|
136 |
+
'alt': 'Altitude (km)',
|
137 |
'vel': 'Velocity (km/s)'
|
138 |
+
}
|
139 |
+
# Filter rename map to only include columns that exist
|
140 |
+
valid_rename = {k: v for k, v in rename_map.items() if k in df.columns}
|
141 |
+
return df.rename(columns=valid_rename)
|
142 |
|
143 |
elif format_type == 'close_approaches':
|
144 |
+
rename_map = {
|
145 |
+
'des': 'Object',
|
146 |
+
'orbit_id': 'Orbit ID',
|
147 |
+
'cd': 'Time (TDB)',
|
148 |
+
'dist': 'Nominal Distance (au)',
|
149 |
+
'dist_min': 'Minimum Distance (au)',
|
150 |
+
'dist_max': 'Maximum Distance (au)',
|
151 |
+
'v_rel': 'Velocity (km/s)',
|
152 |
'h': 'H (mag)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
}
|
154 |
+
valid_rename = {k: v for k, v in rename_map.items() if k in df.columns}
|
155 |
+
return df.rename(columns=valid_rename)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
return df
|
158 |
+
|
159 |
except Exception as e:
|
160 |
+
logger.error(f"Data formatting error for {format_type}: {e}")
|
161 |
traceback.print_exc()
|
162 |
return None
|
163 |
|
164 |
|
165 |
+
# Gradio Interface Functions with better error handling
|
166 |
|
167 |
def fetch_fireballs(limit, date_min, energy_min):
|
168 |
+
"""Fetch fireball data for Gradio interface"""
|
169 |
+
try:
|
170 |
+
api = NasaSsdCneosApi()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
171 |
|
172 |
+
# Process inputs
|
173 |
+
date_min = date_min.strip() if date_min else None
|
174 |
+
try:
|
175 |
+
energy_min = float(energy_min) if energy_min else None
|
176 |
+
except ValueError:
|
177 |
+
return f"Error: Invalid energy value '{energy_min}'. Please enter a valid number.", None
|
178 |
+
|
179 |
+
data = api.get_fireballs(
|
180 |
+
limit=int(limit),
|
181 |
+
date_min=date_min,
|
182 |
+
energy_min=energy_min
|
183 |
+
)
|
184 |
+
|
185 |
+
if not data:
|
186 |
+
return "No data returned from API. There might be an issue with the connection or parameters.", None
|
187 |
+
|
188 |
+
df = api.format_response(data, 'fireballs')
|
189 |
+
if df is None or df.empty:
|
190 |
+
return "No fireball data available for the specified parameters.", None
|
191 |
+
|
192 |
+
# Create world map of fireballs
|
193 |
+
if 'Latitude' in df.columns and 'Longitude' in df.columns:
|
194 |
+
try:
|
195 |
+
# Create size column if Energy (kt) is not available
|
196 |
+
size_col = 'Energy (kt)' if 'Energy (kt)' in df.columns else None
|
197 |
+
|
198 |
+
fig = px.scatter_geo(df,
|
199 |
+
lat='Latitude',
|
200 |
+
lon='Longitude',
|
201 |
+
size=size_col,
|
202 |
+
hover_name='Date/Time' if 'Date/Time' in df.columns else None,
|
203 |
+
projection='natural earth',
|
204 |
+
title='Fireball Events')
|
205 |
+
|
206 |
+
return df, fig
|
207 |
+
except Exception as plot_err:
|
208 |
+
logger.error(f"Error creating fireball plot: {plot_err}")
|
209 |
+
return df, None
|
210 |
+
|
211 |
+
return df, None
|
212 |
+
except Exception as e:
|
213 |
+
logger.error(f"Error in fetch_fireballs: {e}")
|
214 |
+
traceback.print_exc()
|
215 |
+
return f"An error occurred: {str(e)}", None
|
216 |
|
217 |
def fetch_close_approaches(limit, dist_max, date_min, date_max, h_min, h_max, v_inf_min, v_inf_max):
|
218 |
+
"""Fetch close approach data for Gradio interface"""
|
219 |
+
try:
|
220 |
+
api = NasaSsdCneosApi()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
|
222 |
+
# Process inputs with error handling
|
223 |
+
try:
|
224 |
+
dist_max = float(dist_max) if dist_max else None
|
225 |
+
h_min = float(h_min) if h_min else None
|
226 |
+
h_max = float(h_max) if h_max else None
|
227 |
+
v_inf_min = float(v_inf_min) if v_inf_min else None
|
228 |
+
v_inf_max = float(v_inf_max) if v_inf_max else None
|
229 |
+
except ValueError as ve:
|
230 |
+
return f"Error: Invalid numeric input - {str(ve)}", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
231 |
|
232 |
+
date_min = date_min.strip() if date_min else None
|
233 |
+
date_max = date_max.strip() if date_max else None
|
|
|
|
|
|
|
|
|
|
|
234 |
|
235 |
+
data = api.get_close_approaches(
|
236 |
+
limit=int(limit),
|
237 |
+
dist_max=dist_max,
|
238 |
+
date_min=date_min,
|
239 |
+
date_max=date_max,
|
240 |
+
h_min=h_min,
|
241 |
+
h_max=h_max,
|
242 |
+
v_inf_min=v_inf_min,
|
243 |
+
v_inf_max=v_inf_max
|
244 |
+
)
|
245 |
+
|
246 |
+
if not data:
|
247 |
+
return "No data returned from API. There might be an issue with the connection or parameters.", None
|
248 |
+
|
249 |
+
df = api.format_response(data, 'close_approaches')
|
250 |
+
if df is None or df.empty:
|
251 |
+
return "No close approach data available for the specified parameters.", None
|
252 |
+
|
253 |
+
# Create scatter plot
|
254 |
+
try:
|
255 |
+
x_col = 'Nominal Distance (au)' if 'Nominal Distance (au)' in df.columns else df.columns[0]
|
256 |
+
y_col = 'Velocity (km/s)' if 'Velocity (km/s)' in df.columns else df.columns[1]
|
257 |
+
hover_col = 'Object' if 'Object' in df.columns else None
|
258 |
+
size_col = 'H (mag)' if 'H (mag)' in df.columns else None
|
259 |
+
color_col = 'H (mag)' if 'H (mag)' in df.columns else None
|
260 |
+
|
261 |
+
fig = px.scatter(df,
|
262 |
+
x=x_col,
|
263 |
+
y=y_col,
|
264 |
+
hover_name=hover_col,
|
265 |
+
size=size_col,
|
266 |
+
color=color_col,
|
267 |
+
title='Close Approaches - Distance vs Velocity')
|
268 |
+
|
269 |
+
return df, fig
|
270 |
+
except Exception as plot_err:
|
271 |
+
logger.error(f"Error creating close approach plot: {plot_err}")
|
272 |
+
return df, None
|
273 |
+
except Exception as e:
|
274 |
+
logger.error(f"Error in fetch_close_approaches: {e}")
|
275 |
+
traceback.print_exc()
|
276 |
+
return f"An error occurred: {str(e)}", None
|
277 |
|
278 |
# Create Gradio interface
|
279 |
with gr.Blocks(title="NASA SSD/CNEOS API Explorer") as demo:
|
280 |
gr.Markdown("# NASA SSD/CNEOS API Explorer")
|
281 |
gr.Markdown("Access data from NASA's Center for Near Earth Object Studies")
|
282 |
|
283 |
+
# Error display area
|
284 |
+
error_box = gr.Textbox(label="Status", visible=True)
|
285 |
+
|
286 |
with gr.Tab("Fireballs"):
|
287 |
gr.Markdown("### Fireball Events")
|
288 |
gr.Markdown("Get information about recent fireball events detected by sensors.")
|
|
|
296 |
fireball_results = gr.DataFrame(label="Fireball Results")
|
297 |
fireball_map = gr.Plot(label="Fireball Map")
|
298 |
|
299 |
+
fireball_submit.click(fetch_fireballs,
|
300 |
+
inputs=[fireball_limit, fireball_date, fireball_energy],
|
301 |
+
outputs=[fireball_results, fireball_map])
|
302 |
|
303 |
with gr.Tab("Close Approaches"):
|
304 |
gr.Markdown("### Close Approaches")
|
|
|
322 |
inputs=[ca_limit, ca_dist_max, ca_date_min, ca_date_max, ca_h_min, ca_h_max, ca_v_min, ca_v_max],
|
323 |
outputs=[ca_results, ca_plot])
|
324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
gr.Markdown("### About")
|
326 |
gr.Markdown("""
|
327 |
This application provides access to NASA's Solar System Dynamics (SSD) and Center for Near Earth Object Studies (CNEOS) API.
|
328 |
|
329 |
Data is retrieved in real-time from NASA's servers. All data is courtesy of NASA/JPL-Caltech.
|
330 |
|
331 |
+
Created using Gradio and Hugging Face Spaces.
|
332 |
""")
|
333 |
|
334 |
# Create requirements.txt file
|