A Python package for easy access to real-time space weather information from NOAA's Space Weather Prediction Center (SWPC). Originally developed to be used as part of a hobby project, but may prove useful for those who wish to have access to such information.
Hobby project to make a desk ornament that displays real time space weather information and warnings. A touch E-ink display is used alongside a Raspberry Pi Zero 2 W.
SolarNow provides a simple, intuitive interface to fetch and analyse space weather data including solar wind conditions, geomagnetic indices, solar active regions, sunspot numbers, GOES satellite measurements, and F10.7 radio flux. All data is retrieved directly from NOAA SWPC's public APIs and formatted into convenient pandas DataFrames.
Note that the NOAA can change the locations of data sources, or suspend certain services. It is possible that this package breaks in the future as a result of this. If anyone finds that this is the case, please raise an issue or pull request.
- Solar Wind Data: Magnetic field components (Bx, By, Bz, Bt) and plasma parameters (density, speed, temperature)
- Geomagnetic Indices: Dst and Kp indices for monitoring geomagnetic activity
- Active Regions: Current and tracked solar active region data
- Sunspot Numbers: SWPC observed sunspot numbers with date filtering
- GOES Satellite Data:
- Proton flux at 8 energy levels (≥1, 5, 10, 30, 50, 60, 100, 500 MeV)
- Electron flux (≥2 MeV)
- X-ray flux in two bands (0.05-0.4nm, 0.1-0.8nm)
- Customisable time resolution with automatic re-sampling
- GLE (ground level enhancement) proton event warning levels
- F10.7 Radio Flux: Solar radio flux at 10.7 cm with 90-day running mean
Install from source:
git clone https://github.com/NLarsen15/SolarNow.git
cd SolarNow
pip install -e .import SolarNow as sn
# Fetch solar wind data
solar_wind = sn.get_solar_wind_data()
sw_data = solar_wind.fetch_data()
print(sw_data.head())
# Fetch geomagnetic indices
geo_index = sn.get_geo_index_data()
geo_data = geo_index.fetch_data()
print(geo_data.head())
# Fetch GOES satellite data
goes = sn.get_goes_data()
proton_data = goes.fetch_proton_data(satellite='primary')
print(proton_data.head())Retrieve magnetic field and plasma measurements:
import SolarNow as sn
from datetime import datetime, timedelta
# Get solar wind data with custom resolution (default: 1min)
solar_wind = sn.get_solar_wind_data(resolution='5min')
# Fetch all available data (last ~24 hours)
all_data = solar_wind.fetch_data()
# Filter by date range
start = datetime.now() - timedelta(hours=5)
end = datetime.now()
filtered_data = solar_wind.fetch_data(start_date=start, end_date=end)
# Available columns:
# - date [UTC]: Timestamp
# - bx_gsm [nT], by_gsm [nT], bz_gsm [nT]: Magnetic field components
# - bt [nT]: Total magnetic field
# - density [cm^-3]: Plasma density
# - speed [km/s]: Solar wind speed
# - temperature [K]: Plasma temperature
print(filtered_data[['date [UTC]', 'speed [km/s]', 'bz_gsm [nT]']].head())Retrieve the Dst, Kp, and a indices for geomagnetic activity:
import SolarNow as sn
# Get geomagnetic index data with custom resolution
geo_index = sn.get_geo_index_data(resolution='1hour')
# Fetch data
geo_data = geo_index.fetch_data()
# Available columns:
# - date [UTC]: Timestamp
# - dst [nT]: Disturbance Storm Time index
# - kp: Planetary K-index
# - running a_index: Running A-index
print(geo_data.tail(24)) # Last 24 hours
# Filter for geomagnetic storms (Dst < -50 nT)
storm_periods = geo_data[geo_data['dst [nT]'] < -50]
print(f"Storm periods detected: {len(storm_periods)}")Access current and historical solar active region data:
import SolarNow as sn
# Get active region data
ar_data = sn.get_ar_data()
# Fetch currently visible active regions
current_regions = ar_data.fetch_current_regions()
print(f"Current active regions: {len(current_regions)}")
print(current_regions[['ID', 'Location', 'Lo', 'Area', 'Z', 'LL', 'NN', 'MagType']])
# Get list of all tracked regions
ar_list = ar_data.fetch_ar_list()
print(f"\nTracked regions: {len(ar_list)}")
# Fetch historical data for a specific region
region_number = 3901 # Example region number
region_history = ar_data.fetch_region_history(region_number, verbose=True)
if not region_history.empty:
print(f"\nRegion {region_number} history:")
print(region_history[['Date', 'Location', 'Area', 'Z']].head())Retrieve SWPC observed sunspot numbers:
import SolarNow as sn
from datetime import datetime
# Get sunspot number data
ssn_data = sn.get_ssn_data()
# Fetch all available data
all_ssn = ssn_data.fetch_ssn_data()
print(all_ssn.tail(10))
# Fetch data for a specific date
specific_date = datetime(2026, 5, 15)
ssn_on_date = ssn_data.fetch_ssn_by_date(specific_date)
print(f"\nSSN on {specific_date.date()}: {ssn_on_date['swpc sunspot number'].values}")
# Fetch data for a date range
start_date = datetime(2026, 5, 15)
end_date = datetime(2026, 5, 30)
ssn_range = ssn_data.fetch_ssn_in_date_range(start_date, end_date)
print(f"\nSSN data from {start_date.date()} to {end_date.date()}:")
print(ssn_range)Access proton, electron, and X-ray flux data from GOES satellites:
import SolarNow as sn
from datetime import datetime, timedelta
# Get GOES data with custom resolutions for each instrument
# X-ray base resolution: 1min, Proton/Electron base resolution: 5min
goes = sn.get_goes_data(
protonresolution='10min', # Resample to 10 minutes
electronresolution='5min', # Keep at base 5 minutes
xrayresolution='1min' # Keep at base 1 minute
)
# --- Proton Flux Data ---
# Fetch proton data from primary satellite
proton_primary = goes.fetch_proton_data(satellite='primary')
print("Proton flux data (primary satellite):")
print(proton_primary.head())
# Available proton energy levels:
# - flux >=1 MeV [cm^-2 s^-1 sr^-1]
# - flux >=5 MeV [cm^-2 s^-1 sr^-1]
# - flux >=10 MeV [cm^-2 s^-1 sr^-1]
# - flux >=30 MeV [cm^-2 s^-1 sr^-1]
# - flux >=50 MeV [cm^-2 s^-1 sr^-1]
# - flux >=60 MeV [cm^-2 s^-1 sr^-1]
# - flux >=100 MeV [cm^-2 s^-1 sr^-1]
# - flux >=500 MeV [cm^-2 s^-1 sr^-1]
# Filter by date range
start = datetime.now() - timedelta(days=1)
proton_filtered = goes.fetch_proton_data(satellite='primary', start_date=start)
print(f"\nProton flux (last 24 hours): {len(proton_filtered)} records")
# --- Electron Flux Data ---
electron_primary = goes.fetch_electron_data(satellite='primary')
print("\nElectron flux data (primary satellite):")
print(electron_primary[['date [UTC]', 'flux >=2 MeV [cm^-2 s^-1 sr^-1]']].head())
# --- X-ray Flux Data ---
xray_primary = goes.fetch_xray_data(satellite='primary')
print("\nX-ray flux data (primary satellite):")
print(xray_primary[['date [UTC]', 'flux 0.05-0.4nm [W/m^2]', 'flux 0.1-0.8nm [W/m^2]']].head())
# Available X-ray columns for each band (0.05-0.4nm and 0.1-0.8nm):
# - flux [W/m^2]: Corrected flux
# - observed_flux [W/m^2]: Raw observed flux
# - electron_correction: Correction factor
# - electron_contamination: Contamination level
# Compare primary and secondary satellites
proton_secondary = goes.fetch_proton_data(satellite='secondary')
print(f"\nPrimary satellite records: {len(proton_primary)}")
print(f"Secondary satellite records: {len(proton_secondary)}")
# --- GLE (Ground Level Enhancement) Warning ---
# Checks the >=50/100/500 MeV proton flux nearest the given time against the
# spaceweather.gov S1 threshold (10 pfu) to flag a possible proton event.
gle = goes.get_gle_warning(datetime.now(), satellite='primary')
print(f"\nGLE warning level: {gle['GLE warning level']}") # 0 (none) - 3 (>=500 MeV crossed)GOESData.get_gle_warning(time, satellite='primary') is a convenience helper that classifies solar energetic particle (proton) events by checking the GOES proton flux nearest the given time against the NOAA Space Weather Scale S1 "Minor" threshold of 10 pfu (particle flux units, cm^-2 s^-1 sr^-1) at three energy channels. It is a package-defined heuristic for spotting a potential Ground Level Enhancement (GLE) — a proton event energetic enough to be detected by instruments at Earth's surface — not an official NOAA classification. Dedicated GLE detection systems should be used for more reliable alerts.
| Level | Trigger | Meaning |
|---|---|---|
| 0 | No channel at/above 10 pfu | No indication of a proton event |
| 1 | ≥50 MeV flux ≥ 10 pfu | Energetic proton event in progress |
| 2 | ≥100 MeV flux ≥ 10 pfu | Higher-energy protons present; increased GLE likelihood |
| 3 | ≥500 MeV flux ≥ 10 pfu | Highest-energy protons present; strong GLE candidate |
The check only looks at the single flux reading nearest time, evaluated highest-energy-channel-first, and returns a dict with that reading and the resulting level:
import SolarNow as sn
from datetime import datetime
goes = sn.get_goes_data()
gle = goes.get_gle_warning(datetime.now(), satellite='primary')
print(gle)
# {
# 'date [UTC]': Timestamp(...),
# 'flux >=50 MeV [cm^-2 s^-1 sr^-1]': ...,
# 'flux >=100 MeV [cm^-2 s^-1 sr^-1]': ...,
# 'flux >=500 MeV [cm^-2 s^-1 sr^-1]': ...,
# 'GLE warning level': 0, # 0-3
# }
if gle['GLE warning level'] > 0:
print(f"Proton event detected — GLE warning level {gle['GLE warning level']}")Monitor 10.7 cm solar radio emissions:
import SolarNow as sn
from datetime import datetime, timedelta
# Get F10.7 flux data
f107 = sn.get_F107_data()
# Fetch all available data
f107_data = f107.fetch_data()
print("F10.7 flux data:")
print(f107_data.head())
# Available columns:
# - date [UTC]: Observation date
# - flux [sfu]: Solar radio flux at 10.7 cm (1 sfu = 10^-22 W/m^2/Hz)
# - 90 day mean [sfu]: 90-day running mean
# Filter by date range
start = datetime.now() - timedelta(days=30)
recent_f107 = f107.fetch_data(start_date=start)
print(f"\nF10.7 flux (last 30 days):")
print(recent_f107[['date [UTC]', 'flux [sfu]', '90 day mean [sfu]']].tail())
# Calculate average flux
avg_flux = recent_f107['flux [sfu]'].mean()
print(f"\nAverage F10.7 flux (last 30 days): {avg_flux:.2f} sfu")import SolarNow as sn
import pandas as pd
from datetime import datetime, timedelta
# Define time range
end_time = datetime.now()
start_time = end_time - timedelta(hours-10)
# Fetch multiple datasets
solar_wind = sn.get_solar_wind_data(resolution='5min')
geo_index = sn.get_geo_index_data(resolution='5min')
goes = sn.get_goes_data(xrayresolution='5min')
# Get filtered data
sw_data = solar_wind.fetch_data(start_date=start_time, end_date=end_time)
geo_data = geo_index.fetch_data(start_date=start_time, end_date=end_time)
xray_data = goes.fetch_xray_data(satellite='primary', start_date=start_time, end_date=end_time)
# Merge datasets on timestamp
merged = pd.merge(sw_data, geo_data, on='date [UTC]', how='outer')
merged = pd.merge(merged, xray_data[['date [UTC]', 'flux 0.1-0.8nm [W/m^2]']],
on='date [UTC]', how='outer')
print("Combined space weather data:")
print(merged[['date [UTC]', 'speed [km/s]', 'bz_gsm [nT]',
'dst [nT]', 'flux 0.1-0.8nm [W/m^2]']].head())import SolarNow as sn
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
# Fetch solar wind data for the last 24 hours (the full window NOAA's real-time feed retains)
solar_wind = sn.get_solar_wind_data(resolution='5min')
start = datetime.now() - timedelta(hours=24)
sw_data = solar_wind.fetch_data(start_date=start)
# Create plot
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 8), sharex=True)
# Plot solar wind speed
ax1.plot(sw_data['date [UTC]'], sw_data['speed [km/s]'], color='blue')
ax1.set_ylabel('Speed [km/s]')
ax1.set_title('Solar Wind Conditions (Last 24 Hours)')
ax1.grid(True, alpha=0.3)
# Plot Bz component
ax2.plot(sw_data['date [UTC]'], sw_data['bz_gsm [nT]'], color='red')
ax2.axhline(y=0, color='black', linestyle='--', alpha=0.5)
ax2.set_ylabel('Bz GSM [nT]')
ax2.set_xlabel('Date [UTC]')
ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()- Solar Wind: Last ~24 hours (NOAA's real-time RTSW feed; sourced from ACE, DSCOVR, or IMAP depending on which is currently active)
- Geomagnetic Indices: Last 7 days
- GOES Satellite Data: Last 7 days
- Active Regions: Current visible regions and recently tracked regions
- Sunspot Numbers: Historical data from solar cycle observations
- F10.7 Radio Flux: Historical data with daily observations
SolarNow supports automatic time-series resampling for real-time data:
- Solar Wind: Base resolution 1 minute, customizable via
resolutionparameter - Geomagnetic Indices: Base resolution 1 minute, customizable via
resolutionparameter - GOES Protons: Base resolution 5 minutes, customizable via
protonresolutionparameter - GOES Electrons: Base resolution 5 minutes, customizable via
electronresolutionparameter - GOES X-rays: Base resolution 1 minute, customizable via
xrayresolutionparameter
Resampling automatically uses:
- Backfill for upsampling (higher resolution than base)
- Mean for downsampling (lower resolution than base)
- Python ≥ 3.8
- pandas ≥ 1.3.0
- numpy ≥ 1.20.0
- requests ≥ 2.25.0
The test suite uses pytest and covers both mocked unit tests and live-API integration tests:
pip install -e .[test]
# Unit tests only (mocked, no network calls)
pytest -v -m "not integration"
# Integration tests (hit the live NOAA/GFZ APIs)
pytest -v -m "integration"Tests run automatically on push/PR via GitHub Actions across Python 3.10-3.12, plus a daily scheduled run to catch upstream API changes.
Data is sourced from NOAA's Space Weather Prediction Center (SWPC) and GFZ Potsdam's geomagnetic observatory:
- NOAA SWPC
- SWPC Data Products
- GFZ Potsdam Kp Index (nowcast Kp/ap data)
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
If you encounter any problems or have suggestions, please open an issue on GitHub.
Nicholas Larsen - nlarsen1505@gmail.com
- NOAA Space Weather Prediction Center for providing open access to space weather data
- The space physics community for their continued research and monitoring efforts