Skip to content
2 changes: 2 additions & 0 deletions bolides/astro_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from astropy.coordinates import ICRS, SkyCoord
from astropy.time import Time
from scipy.special import comb
from pytz import timezone
utc = timezone('UTC')

def get_phase(datetime):
"""Get lunar phase (0.01=new moon just happened, 0.99=new moon about to happen)"""
Expand Down
52 changes: 51 additions & 1 deletion bolides/bdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,9 @@ def filter_shower(self, shower=None, years=None, padding=1, sdf=None, exclude=Fa
`~BolideDataFrame`
The filtered `~BolideDataFrame`
"""

if not pd.api.types.is_datetime64_any_dtype(self.datetime):
self.datetime = pd.to_datetime(self.datetime, errors='coerce')
if years is None:
years = list(range(min(self.datetime).year-1, max(self.datetime).year+1))
elif type(years) is int:
Expand All @@ -542,7 +545,7 @@ def filter_shower(self, shower=None, years=None, padding=1, sdf=None, exclude=Fa
from . import ShowerDataFrame
sdf = ShowerDataFrame()
self._showers = sdf

dates = sdf.get_dates(shower, years).datetime
date_padding = timedelta(days=padding)
date_ranges = [[d-date_padding, d+date_padding] for d in dates]
Expand All @@ -553,6 +556,53 @@ def filter_shower(self, shower=None, years=None, padding=1, sdf=None, exclude=Fa
else:
good_locs = counts == np.zeros(len(counts))
return self[good_locs]

def filter_out_all_showers(self, shower=None, exclude=True, sdf=None):
"""Filter out all bolides that are part of any meteor shower.

Parameters
----------
sdf: ShowerDataFrame
Optionally provide a ShowerDataFrame to use for filtering.
If not provided, the ShowerDataFrame is pulled from the IAU Meteor Data Center.
shower: str
The meteor shower to filter by. If None, all showers are used.
Can enter either IAU number, IAU 3-letter code, or full shower name.
Refer to the IAU Meteor Data center at https://www.ta3.sk/IAUC22DB/MDC2022/.
exclude: bool
Whether or not to exclude bolides around the given showers or keep only bolides in showers. Default is to exclude.

Returns
-------
`~BolideDataFrame`
The filtered `~BolideDataFrame`
"""
years = list(range(min(self.datetime).year-1, max(self.datetime).year+1))
if sdf is None:
if hasattr(self, '_showers'):
sdf = self._showers
else:
from . import ShowerDataFrame
sdf = ShowerDataFrame()
self._showers = sdf


start = sdf.get_start_dates(shower, years).datetime
end = sdf.get_end_dates(shower, years).datetime
date_ranges = [
[date_start, date_end]
for date_start, date_end in zip(start, end)
if not (pd.isna(date_start) or pd.isna(date_end))
]
print(date_ranges)
counts = np.sum(np.array([list(self.datetime.between(d[0], d[1])) for d in date_ranges]), axis=0)
if not exclude:

good_locs = counts != np.zeros(len(counts))
else:
# keep bolides that are not part of any shower
good_locs = counts == np.zeros(len(counts))
return self[good_locs]

def plot_detections(self, category=None, *args, **kwargs):
"""Plot detections of bolides.
Expand Down
14 changes: 7 additions & 7 deletions bolides/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def plot_scatter(
if s is not None and hasattr(s, '__getitem__'):
kwargs['s'] = s[idx]
ax.scatter(x[idx], y[idx], color=scalarMap.to_rgba(num), label=label, **kwargs)
plt.legend()
plt.legend(fontsize=20)

if coastlines:
ax.coastlines() # plot coastlines
Expand Down Expand Up @@ -343,10 +343,8 @@ def plot_density(
filled_c = ax.contourf(x, y, z*mask, levels=levels[1:],
transform=ccrs.PlateCarree(), **kwargs)

# make lines invisible
for c in filled_c.collections:
c.set_edgecolor('none')
c.set_linewidth(0.000000000001)
filled_c.set_edgecolor('none')
filled_c.set_linewidth(0.000000000001)

if coastlines:
ax.coastlines() # plot coastlines
Expand All @@ -356,8 +354,10 @@ def plot_density(
if boundary:
add_boundary(ax, boundary, boundary_style)

plt.colorbar(filled_c, alpha=kwargs['alpha'],
label='bolide density (km$^{-2}$)')
cbar = plt.colorbar(filled_c, alpha=kwargs['alpha'])

cbar.ax.tick_params(labelsize=15)
cbar.set_label(label='bolide density (km$^{-2}$)', size=20, weight='bold')

if title is not None:
plt.title(title)
Expand Down
175 changes: 167 additions & 8 deletions bolides/sdf.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pandas as pd
import requests
import numpy as np
import warnings
from . import ROOT_PATH


Expand All @@ -14,7 +15,7 @@ class ShowerDataFrame(pd.DataFrame):
Specifies the source for the initialized. Can be:

- ``'established', 'all', 'working'``: initialize from different sets
of data offered at the IAU Meteor Data Center, https://www.ta3.sk/IAUC22DB/MDC2007/
of data offered at the IAU Meteor Data Center, https://www.ta3.sk/IAUC22DB/MDC2022/
- ``'csv'``: initialize from a .csv file

file : str
Expand All @@ -32,7 +33,7 @@ def __init__(self, *args, **kwargs):
if source == 'csv':
df = pd.read_csv(file, index_col=0, keep_default_na=False, na_values='')
else:
url = 'https://www.ta3.sk/IAUC22DB/MDC2007/Etc/stream'+source+'data.txt'
url = 'https://www.ta3.sk/IAUC22DB/MDC2022/Etc/stream'+source+'data2022.txt'
r = requests.get(url)
start_line = 0
for num, line in enumerate(r.text.splitlines()):
Expand All @@ -41,12 +42,13 @@ def __init__(self, *args, **kwargs):
break
column_line = r.text.splitlines()[start_line-3]
import re
columns = re.split(r" {2,}", column_line)[1:-1]
columns = re.split(r" {2,}", column_line)[1:-3]
data_lines = r.text.splitlines()[start_line:]
data = '\n'.join(data_lines)
import io
csv_io = io.StringIO(data)
df = pd.read_csv(csv_io, sep="|", header=None)
df = df.iloc[:, :len(columns)]
df.columns = columns
for col in df.columns:
if df[col].dtype == 'O':
Expand Down Expand Up @@ -135,7 +137,7 @@ def plot_orbits(self, date='2000-01-01T12:00:00',
orb = Orbit.from_classical(Sun, a, ecc, inc, raan, argp, nu, plane=Planes.EARTH_ECLIPTIC)
except ValueError:
continue
plotter.plot(orb, label=row['shower name'])
plotter.plot(orb, label=row['shower name-designation'])

if use_3d:
fig = plotter._figure
Expand Down Expand Up @@ -203,6 +205,24 @@ def plot_orbits(self, date='2000-01-01T12:00:00',
return plotter

def get_dates(self, showers, years):
"""
Get the dates from solar longitude.

Parameters
----------
showers : str or list of str
The meteor shower(s) to get the dates for.
years : int or list of int
The year(s) to get the dates for.

Returns
-------
pd.DataFrame
A DataFrame with the shower Codes, names, IAUNos, and the corresponding dates
for the specified meteor showers and years.
"""
if showers is None:
showers = self['shower name-designation'].unique()
if type(years) is int:
years = [years]
if type(showers) in [str, int]:
Expand All @@ -214,28 +234,167 @@ def get_dates(self, showers, years):
elif len(showers[0]) == 3:
col = 'Code'
else:
col = 'shower name'
col = 'shower name-designation'
sdf = self[self[col].isin(showers)]
import warnings
if len(sdf) == 0:
warnings.warn('No showers with '+col+'in'+str(showers)+'.')

sdf = sdf[sdf.activity == 'annual']
sdf = sdf.dropna(subset=['LaSun'])
sdf = sdf.dropna(subset=['LoS'])
num = len(sdf)
sdf = pd.concat([sdf]*len(years), ignore_index=True)
years = np.repeat(years, num)

lons = sdf.LaSun
lons = sdf.LoS
from .astro_utils import sol_lon_to_datetime
dts = []
for lon, year in zip(lons, years):
dts.append(sol_lon_to_datetime(lon, year))
return pd.DataFrame({'Code': sdf.Code,
'shower name': sdf['shower name'],
'shower name': sdf['shower name-designation'],
'IAUNo': sdf.IAUNo,
'datetime': dts})

def get_start_dates(self, showers, years):
"""Get the start dates of the meteor showers from solar longitude.

Parameters
----------
showers : str or list of str
The meteor shower(s) to get the start dates for.
years : int or list of int
The year(s) to get the start dates for.

Returns
-------
pd.Series
A series with the start dates of the meteor showers.
"""
if showers is None:
showers = self['shower name-designation'].unique()
if type(years) is int:
years = [years]
if type(showers) in [str, int]:
showers = [showers]
showers = [str(s) for s in showers]
if showers[0].isdigit():
col = 'IAUNo'
showers = [int(s) for s in showers]
elif len(showers[0]) == 3:
col = 'Code'
else:
col = 'shower name-designation'
sdf = self[self[col].isin(showers)]
import warnings
if len(sdf) == 0:
warnings.warn('No showers with '+col+'in'+str(showers)+'.')

sdf = sdf[sdf.activity == 'annual']
sdf = sdf.dropna(subset=['LoSb'])
num = len(sdf)
sdf = pd.concat([sdf]*len(years), ignore_index=True)
years = np.repeat(years, num)

lons = sdf.LoSb
from .astro_utils import sol_lon_to_datetime
dts = []
for lon, year in zip(lons, years):
if lon=='' or lon is None or pd.isna(lon):
dts.append(np.nan)
else:
dts.append(sol_lon_to_datetime(float(lon), year))
return pd.DataFrame({'Code': sdf.Code,
'shower name': sdf['shower name-designation'],
'IAUNo': sdf.IAUNo,
'datetime': dts})

def get_end_dates(self, showers, years):
"""Get the end dates of the meteor showers from solar longitude.

Parameters
----------
showers : str or list of str
The meteor shower(s) to get the start dates for.
years : int or list of int
The year(s) to get the start dates for.

Returns
-------
pd.Series
A series with the start dates of the meteor showers.
"""
if showers is None:
showers = self['shower name-designation'].unique()
if type(years) is int:
years = [years]
if type(showers) in [str, int]:
showers = [showers]
showers = [str(s) for s in showers]
if showers[0].isdigit():
col = 'IAUNo'
showers = [int(s) for s in showers]
elif len(showers[0]) == 3:
col = 'Code'
else:
col = 'shower name-designation'
sdf = self[self[col].isin(showers)]
import warnings
if len(sdf) == 0:
warnings.warn('No showers with '+col+'in'+str(showers)+'.')

sdf = sdf[sdf.activity == 'annual']
sdf = sdf.dropna(subset=['LoSe'])
num = len(sdf)
sdf = pd.concat([sdf]*len(years), ignore_index=True)
years = np.repeat(years, num)

lons = pd.to_numeric(sdf.LoSe, errors='coerce')
from .astro_utils import sol_lon_to_datetime
dts = []
for lon, year in zip(lons, years):
if lon=='' or lon is None or pd.isna(lon):
dts.append(np.nan)
else:
dts.append(sol_lon_to_datetime(float(lon), year))
return pd.DataFrame({'Code': sdf.Code,
'shower name': sdf['shower name-designation'],
'IAUNo': sdf.IAUNo,
'datetime': dts})

def fastest_showers(self, n=5):
"""
Return the top n shower Codes by mean Vg.

Parameters
-----------
n : int
The number of top showers to return based on their mean Vg. Maximum is 122.

Returns
-------
pd.DataFrame
A DataFrame with the shower Codes and their mean Vg, sorted by mean Vg in descending order.
"""
# Ensure Vg is numeric, coerce errors to NaN
sdf = self.copy()
sdf['Vg'] = pd.to_numeric(sdf['Vg'], errors='coerce')
sdf_sorted = sdf.sort_values('Code')

mean_vgs = []
codes = []
for code, group in sdf_sorted.groupby('Code'):
codes.append(code)
mean_vgs.append(group['Vg'].mean())

result = pd.DataFrame({'Code': codes, 'mean_Vg': mean_vgs})

if len(result) < n:
warnings.warn('Maximum number of showers is ' + str(len(result)))

result = result.sort_values('mean_Vg', ascending=False).head(n).reset_index(drop=True)
return result

def __setattr__(self, attr, val):
from warnings import filterwarnings
filterwarnings("ignore", message="Pandas doesn't allow columns to be created via a new attribute name")
Expand Down
2 changes: 1 addition & 1 deletion notebooks/tutorial.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -4423,7 +4423,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.13.2"
}
},
"nbformat": 4,
Expand Down