visit
[
{
"time21_5":"2016-09-06T14:18Z",
"latitude":-20.0,
"longitude":120.0,
"halfAngle":31.0,
"speed":674.0,
"type":"C",
"isMostAccurate":true,
"associatedCMEID":"2016-09-06T08:54:00-CME-001",
"note":"",
"catalog":"SWRC_CATALOG",
"link":"//kauai.ccmc.gsfc.nasa.gov/DONKI/view/CMEAnalysis/11233/-1"
},
{
"time21_5":"2016-09-15T04:24Z",
"latitude":-18.0,
"longitude":-122.0,
"halfAngle":43.0,
"speed":722.0,
"type":"C",
"isMostAccurate":true,
"associatedCMEID":"2016-09-14T23:36:00-CME-001",
"note":"Measured with swpc_cat using C3 and STA Cor2 imagery.",
"catalog":"SWRC_CATALOG",
"link":"//kauai.ccmc.gsfc.nasa.gov/DONKI/view/CMEAnalysis/11256/-1"
}
]
Check out the created in this tutorial or on GitHub.
"NeoWs (Near Earth Object Web Service) is a RESTful web service for near earth Asteroid information. With NeoWs a user can: search for Asteroids based on their closest approach date to Earth, lookup a specific Asteroid with its NASA JPL small body id, as well as browse the overall data-set."NeoWs data is also relatively accessible for someone like myself with no subject matter knowledge. Unlike some of the space weather APIs, which I find mostly unintelligible with my lack of space weather training, NeoWs returns data with self-explanatory keys like "estimated_diameter" and "miss_distance":
{
"links":{
"next":"//www.neowsapp.com/rest/v1/feed?start_date=2020-09-30&end_date=2020-09-30&detailed=false&api_key=DEMO_KEY",
"prev":"//www.neowsapp.com/rest/v1/feed?start_date=2020-09-28&end_date=2020-09-28&detailed=false&api_key=DEMO_KEY",
"self":"//www.neowsapp.com/rest/v1/feed?start_date=2020-09-29&end_date=2020-09-29&detailed=false&api_key=DEMO_KEY"
},
"element_count":12,
"near_earth_objects":{
"2020-09-29":[
{
"links":{
"self":"//www.neowsapp.com/rest/v1/neo/54054531?api_key=DEMO_KEY"
},
"id":"54054531",
"neo_reference_id":"54054531",
"name":"(2020 SN2)",
"nasa_jpl_url":"//ssd.jpl.nasa.gov/sbdb.cgi?sstr=54054531",
"absolute_magnitude_h":24.431,
"estimated_diameter":{
"kilometers":{
"estimated_diameter_min":0.0345425963,
"estimated_diameter_max":0.0772395934
},
"meters":{
"estimated_diameter_min":34.5425962673,
"estimated_diameter_max":77.239593373
},
"miles":{
"estimated_diameter_min":0.0214637676,
"estimated_diameter_max":0.0479944434
},
"feet":{
"estimated_diameter_min":113.3287315376,
"estimated_diameter_max":253.4107475218
}
},
"is_potentially_hazardous_asteroid":false,
"close_approach_data":[
{
"close_approach_date":"2020-09-29",
"close_approach_date_full":"2020-Sep-29 13:21",
"epoch_date_close_approach":00,
"relative_velocity":{
"kilometers_per_second":"8.7170431155",
"kilometers_per_hour":"31381.355215948",
"miles_per_hour":"19499.1537451539"
},
"miss_distance":{
"astronomical":"0.1257314283",
"lunar":"48.9095256087",
"kilometers":"18809153.865737721",
"miles":"11687466.2571577098"
},
"orbiting_body":"Earth"
}
],
"is_sentry_object":false
},
...
GET //api.nasa.gov/neo/rest/v1/feed?start_date=START_DATE&end_date=END_DATE&api_key=API_KEY
pip install requests
params = {'start_date' : today_str,
'end_date' : today_str,
'api_key' : self.api_key}
get_request = requests.get(url = self.api_endpoint, params = params)
json_resp = get_request.json()
json_resp
now contains a JSON blob from NeoWs like the one above.We only need a few items from
json_resp
to create our plot, so I extract that information before passing the list of asteroids back to the caller. In this case, we only want the asteroid's name, close approach time, miss distance, diameter, and it's JPL URL. We will append these values as a tuple to our list of asteroids.miss_miles = neo['close_approach_data'][0]['miss_distance']['miles']
diameter_miles = neo['estimated_diameter']['miles']['estimated_diameter_max']
url = neo['nasa_jpl_url']
asteroid_list.append((name, close_approach_epoch, miss_miles, diameter_miles, url))
pip install astropy
close_approach_epoch = datetime.datetime.strptime(neo['close_approach_data'][0]['close_approach_date_full'], '%Y-%b-%d %H:%M')
close_approach_epoch = Time(close_approach_epoch, scale='tdb')
close_approach_epoch = str(close_approach_epoch.tt)
pip install bokeh
def plot_asteroids(self, neos_list:list):
# We will label each circle on our plot with the NEO name and it's diameter
names = [(name + " {:.2f}".format(dia) + " M wide") for (name, miss_time, miss_dist, dia, url) in neos_list]
names.append('Moon') # We will add the moon to our plot to help give a sense of scale
# Drop the nanoseconds we don't need off the end of the timestamps
x = [datetime.datetime.strptime(miss_time[0:-7], '%Y-%m-%d %H:%M:%S') for (name, miss_time, miss_dist, dia, url) in neos_list]
x.append(datetime.datetime.strptime(self.date_str + ' 04:00', '%Y-%m-%d %H:%M')) # We'll put the moon at 4 hours
# Distance from Earth will go along the Y axis
y = [float(miss_dist) for (name, miss_time, miss_dist, dia, url) in neos_list]
y.append(238900) # The moon is 238,900 from Earth
# Let's represent each NEO's relative size with the diameter of each circle
# Since most NEOs are less than a mile wide, we will need to scale them by a factor of 1e7
# to see them on our plot
sizes = [float(dia) for (name, miss_time, miss_dist, dia, url) in neos_list]
radii = [size * 1e7 for size in sizes]
radii.append(1e6) # This is an arbitrary size, since the moon is actually much larger than any of our NEOs
# Users will be able to click on each circle to view the NASA's page for each NEO
urls = [url for (name, miss_time, miss_dist, dia, url) in neos_list]
urls.append('//moon.nasa.gov/') # Moon website!
# Let's make the NEOs coming close to Earth red and the
# ones farthest from Earth green
colors = []
min_dist = min(y)
max_dist = max(y)
range_dist = max_dist - min_dist
for (name, dist) in zip(names, y):
if name == 'Moon':
colors.append('#0EBFE9') # Let's make the moon blue so that
# it's clear it's different from the NEOs
elif dist == min_dist:
colors.append("#%02x%02x%02x" % (255, 0, 0))
elif dist == max_dist:
colors.append("#%02x%02x%02x" % (0, 255, 0))
else:
perc = (dist - min_dist)/range_dist
num = int(perc * 500)
if num > 255:
colors.append("#%02x%02x%02x" % (0, num-255, 0))
else:
colors.append("#%02x%02x%02x" % (255-num, 0, 0))
# Create the ColumnDataSource that we will use for plotting and creating labels
source = ColumnDataSource(data=dict(hours=x,
distance=y,
names=names,
radii=radii,
colors=colors,
url=urls))
The
colors
list provides the color for each of our circles. The asteroids with smaller miss distances are shades of red and the ones with further miss distances are shades of green. Note that this is all relative to each other; the closest asteroid in the list is set to red regardless of its absolute miss distance.Bokeh does provide for colorizing plots. I found that in my case the auto-magic behind the pallettes colorized my circles by radius rather than Y-axis value, so I constructed the colors list myself to achieve the effect I wanted.After constructing each list, I wrapped all the data in a . It's not required to do this; you can pass the data to Bokeh directly for plotting. However, the
ColumnDataSource
provides data mapping that drives a lot of Bokeh's features. In this case, the ColumnDataSource allows us to construct all the labels for our circles with a single call:labels = LabelSet(x='hours', y='distance', text='names', source=source, text_font_size='8pt')
source=source
refers to our ColumnDataSource variable source
defined above.It also allows us to set up a that will open each asteroid's JPL link when a user clicks on it:# Add TapTool call back that will open a new tab with our NEO URL on a click
taptool = p.select(type=TapTool)
taptool.callback = OpenURL(url="@url")
"@url"
refers to the "url" field in our ColumnDataSource.Bokeh allows us to add some on our chart. I used this feature to credit the source of our data and make an explanatory note about the moon:# Let's label each NEO to make our plot more meaningful
citation = Label(x=850, y=70, x_units='screen', y_units='screen',
text='Data From NASA NeoW API', render_mode='css',
border_line_color='black', border_line_alpha=0.6,
background_fill_color='white', background_fill_alpha=0.6)
# Let's make a note comparing the moon's real diameter to the largest NEO on this plot
max_size = max(sizes)
scale = " {:.2f}".format(2158.8/max_size) # Moon is 2,158.8 miles in diameter
moon_note = Label(x=150, y=30, x_units='screen', y_units='screen', text='Moon diameter not to scale; it is ' + scale + 'x larger than largest NEO on this plot.', render_mode='css',
border_line_color='black', border_line_alpha=0.6,
background_fill_color='white', background_fill_alpha=0.6, text_font_size="8pt")
# Output our plot to a static HTML file
output_file("neos_today.html", title="NEOs Demo", mode="cdn")
# Set up our plots
TOOLS = "pan,wheel_zoom,box_zoom,reset,box_select,lasso_select,tap"
p = figure(tools=TOOLS, x_axis_type="datetime", plot_width=1250, height=650, x_axis_label="Hours After Midnight",
y_axis_label="Miles From Earth", title=self.plot_title)
p.circle(x="hours", y="distance", source=source, radius="radii", fill_color="colors", fill_alpha=0.6, line_color=None)
labels = LabelSet(x='hours', y='distance', text='names', source=source, text_font_size='8pt')
p.add_layout(labels)
p.add_layout(citation)
p.add_layout(moon_note)
# Add TapTool call back that will open a new tab with our NEO URL on a click
taptool = p.select(type=TapTool)
taptool.callback = OpenURL(url="@url")
# Display the plot in a browser
show(p)
output_file
defines our static HTML output file. The show
call will display this file in a browser and can be omitted if you don't wish to display your plot immediately.TOOLS
refers to various interactive user tools Bokeh provides, such as zooming and panning tools. They appear to the left of my plot (the display location of tools if you wish) and allow users to explore the plot by adjusting it in various ways.