Metadata-Version: 2.4
Name: geomag-aurora
Version: 0.1.0
Summary: Aurora visibility thresholds and geomagnetic outlooks for any location, from NOAA SWPC public data
Keywords: aurora,northern lights,space weather,geomagnetic storm,kp index,swpc,noaa
Author: trice
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
Classifier: Topic :: Scientific/Engineering :: Physics
Requires-Dist: aacgmv2>=2.7.1
Requires-Dist: numpy>=2.4.6
Requires-Dist: pandas>=3.0.6
Requires-Dist: pyarrow>=26.0.0
Requires-Python: >=3.11
Project-URL: Homepage, https://github.com/rtphokie/geomag-aurora
Project-URL: Repository, https://github.com/rtphokie/geomag-aurora
Project-URL: Issues, https://github.com/rtphokie/geomag-aurora/issues
Description-Content-Type: text/markdown

# geomag-aurora

Aurora expectations and outlooks for any location, built on NOAA Space Weather Prediction
Center (SWPC) products as published. It covers geomagnetic factors only: there's no darkness,
Moon or weather, so read every answer as "if it's dark and clear."

- **`geomag-aurora threshold`**: what storm level (G-scale, Kp) a location needs to see aurora, how
  often that happens, and when it last did. Uses no forecast data; has a plain-language mode
  for non-specialists.
- **`geomag-aurora aurora`**: whether SWPC's current 3-day Kp forecast, daily storm probabilities and
  OVATION nowcast bring aurora into view at that location, and when.
- **`geomag-aurora build` / `verify`**: research datasets for Kp/Ap forecasting. These align the GFZ Kp
  record (target), NASA OMNI solar wind (drivers) and SWPC's archived daily forecasts
  (baseline), and score SWPC against simple reference forecasts.

> **Disclaimer:** This is an independent project. It is **not** a product of, affiliated with,
> or endorsed by NOAA, the NOAA Space Weather Prediction Center (SWPC), or the National Weather
> Service. It uses SWPC's publicly available data and products, as well as public data from GFZ
> Potsdam, NASA and OpenStreetMap. It may be wrong, out of date or unavailable when you need it.
> For official space weather forecasts, watches, warnings and alerts, go to
> [spaceweather.gov](https://www.spaceweather.gov).

See [RESOURCES.md](https://github.com/rtphokie/geomag-aurora/blob/main/RESOURCES.md) for the full survey of SWPC and related data sources, and
[References](#references) for where each data set and algorithm comes from.

## Install

Requires Python 3.11 or newer.

```sh
pip install geomag-aurora          # or: uv tool install geomag-aurora / pipx install geomag-aurora
geomag-aurora threshold "Seattle, WA" --plain
```

The examples below use `uv run geomag-aurora ...` from a source checkout. With the package
installed, run `geomag-aurora ...` directly. The Python package is `geomag_aurora`.

## Development

Requires [uv](https://docs.astral.sh/uv/). Python 3.11 is pinned in `.python-version` for
development.

```sh
git clone https://github.com/rtphokie/geomag-aurora && cd geomag-aurora
uv sync            # creates .venv and installs geomag-aurora plus dev dependencies (pytest, ruff)
```

Run all three checks before every commit (CI runs the same on Python 3.11–3.13):

```sh
uv run pytest                  # offline tests (no network), about 1 s
uv run ruff check .            # lint (ruff's defaults; `--fix` applies safe fixes)
uv run ruff format --check .   # formatting (`uv run ruff format .` to apply)
```

Ruff settings live in `pyproject.toml` under `[tool.ruff]`: line length 110, target Python 3.11.

### Releasing to PyPI

Releases are published by GitHub Actions (`.github/workflows/publish.yml`) using PyPI
[trusted publishing](https://docs.pypi.org/trusted-publishers/), so no API token is stored.

One-time setup on pypi.org: add a *pending* trusted publisher for project `geomag-aurora`
with owner `rtphokie`, repository `geomag-aurora`, workflow `publish.yml` and environment
`pypi`.

For each release:
1. Bump `version` in `pyproject.toml`, run the checks above, and commit.
2. Optionally check the build locally: `uv build && uvx twine check dist/*`.
3. Tag and create a GitHub release: `git tag v0.1.0 && git push --tags`, then
   `gh release create v0.1.0 --generate-notes`. Publishing the release triggers the
   workflow. It runs the checks, builds the sdist and wheel, and uploads them to PyPI.

## Locations

`threshold` and `aurora` take a location in any of these forms:

```sh
uv run geomag-aurora threshold "Fairbanks, AK"            # place name or street address
uv run geomag-aurora threshold "64.84, -147.72"           # coordinate text (no lookup)
uv run geomag-aurora threshold --lat 64.84 --lon -147.72  # explicit coordinates
```

Python functions take the same forms: `place="Fairbanks, AK"` or `lat, lon`.

Place names and addresses are resolved with [OpenStreetMap Nominatim](https://nominatim.org).
That sends the text you type to nominatim.openstreetmap.org; no other data is sent. The tool
takes the top-ranked match and prints it as `Location: ...`, so check it for ambiguous names
(e.g. "Portland" or "Springfield"). Add a state or country if it picked the wrong one.
Results are cached in `~/.cache/geomag-aurora/geocode.json` (respects `XDG_CACHE_HOME`), and
uncached lookups are limited to one per second, per Nominatim's usage policy.
Coordinate text such as `"35.78, -78.64"` is parsed locally and never sent anywhere.

Each location is converted to geomagnetic latitude using AACGM-v2, via `aacgmv2` and the
IGRF-14 field model, and that is compared against NOAA's G-scale latitudes. The centered-dipole
latitude is reported alongside. Southern-hemisphere sites use |AACGM latitude|.

Why AACGM rather than dipole:
- NOAA describes its latitudes with US place names, and at the US sites checked here the two
  agree within 0.3° (AACGM vs dipole: Raleigh 44.9° vs 44.9°, Fredericksburg 47.0° vs 47.3°,
  Seattle 52.9° vs 53.0°).
- Elsewhere they can differ a lot: Madrid is 43° dipole but 33° AACGM.
- AACGM follows the real field rather than a dipole approximation (Laundal & Richmond, 2017),
  so it is the system compared here.

```python
geomag_aurora.geocode(
    "Tromsø"
)  # Place(query='Tromsø', name='Tromsø, Troms, Norge', lat=69.65, lon=18.96, ...)
geomag_aurora.locate(
    47.6, -122.3
)  # Site(lat, lon, geomagnetic_lat=53.0, aacgm_lat=52.86, aacgm_lon=-58.95, ...)
```

## Aurora threshold: what it takes to see aurora here

`geomag-aurora threshold` answers "what storm level does this location need?" without any forecast
data. Use it to set expectations before a storm.

```sh
uv run geomag-aurora threshold "Seattle, WA"
uv run geomag-aurora threshold "Seattle, WA" --plain          # plain-language, for non-specialists
uv run geomag-aurora threshold "Raleigh, NC" --json
uv run geomag-aurora threshold "64.84, -147.72" --no-history   # skip the ~6 MB GFZ download
```

```
Location: Seattle, King County, Washington, United States
Site 47.60, -122.33: AACGM lat 52.9, dipole lat 53.0
At 52.9 deg AACGM, aurora typically needs a G3 storm (Kp 7) or stronger. NOAA: at G3, aurora
has been seen as low as Illinois and Oregon (typically 50 deg geomagnetic latitude).
History, GFZ Kp 1932-01-01 to 2026-10-11 (days with any 3-hour Kp at the level):
  Level    Kp  Last reached             Last 11 yr  Avg/11 yr  NOAA/cycle
  G3     6.67  2026-10-10 (provisional)         39         89         130
  G4     7.67  2026-01-19                       13         29          60
  G5     9.00  2024-05-11                        1          3           4
Days reaching G3 or stronger (Kp >= 6.67), last 11 years:
  2016: 0  2017: 3  2018: 1  2019: 0  2020: 0  2021: 2  2022: 0  2023: 5  2024: 13  2025: 8  2026 (to date): 7
  Most recent: 2026-10-10 (Kp 7.33, G3, provisional), 2026-07-04 (Kp 7.00, G3), 2026-03-22 (Kp 7.00, G3), ...
```

**Three versions of the message**, for different audiences:

| Field | Audience | Seattle |
|---|---|---|
| `visibility.message` | technical | "At 52.9 deg AACGM, aurora typically needs a G3 storm (Kp 7) or stronger. NOAA: at G3, aurora has been seen as low as Illinois and Oregon (typically 50 deg geomagnetic latitude)." |
| `visibility.simple_message` | technical, no magnetic coordinates | "Aurora here typically needs a G3 storm (Kp 7) or stronger. At G3, NOAA reports aurora seen as low as Illinois and Oregon." |
| `plain_message` | lay readers; adds frequency and the last occurrence | see below |

`message` and `simple_message` depend only on location. `plain_message` also uses the GFZ
history.

**Plain-language version.** `--plain`, `t.plain_message()`, and `plain_message` in the JSON
give expectation-setting text for non-specialists. It covers the NOAA storm level and Kp
needed, how often storms that strong happen, and when the last one was. It never mentions
magnetic coordinates:

> From Seattle, seeing aurora usually takes a strong geomagnetic storm: G3 or higher on
> NOAA's 5-level scale (Kp 7 or more). Storms that strong happen on about 8 days a year on
> average, most recently on October 10, 2026. When it happens, expect the aurora low on the
> northern horizon, best seen from somewhere dark, away from city lights.

> From Raleigh, seeing aurora usually takes an extreme geomagnetic storm: G5, the top of
> NOAA's 5-level scale (Kp 9). Storms that strong happen on only about 3 days in a typical
> 11-year solar cycle, most recently on May 11, 2024. Raleigh is right at the edge of where a
> severe storm (G4, Kp 8) can be enough, so watch for those too; storms that strong happen on
> about 3 days a year on average, most recently on January 19, 2026. ...

How the plain text is built:
- Frequencies are the GFZ long-term average of days at that level *or stronger*. They are
  given per year when there are 16 or more per 11-year cycle, otherwise per cycle.
- Without history (`--no-history` or GFZ unavailable), NOAA's published per-cycle figure is
  used instead.
- Storm names are NOAA's: Minor, Moderate, Strong, Severe, Extreme.
- The place name is the first part of what you typed ("Seattle, WA" gives "Seattle"), or
  "here" for coordinates.

```python
t = geomag_aurora.aurora_threshold(place="Seattle, WA")  # or aurora_threshold(47.6, -122.3)
t.visibility.category  # "noaa_scale" | "g2_or_lower" | "beyond_noaa_scale"
t.visibility.scale_needed  # "G3"
t.visibility.kp_needed  # "7" (NOAA's wording); t.visibility.kp_min == 6.67
t.visibility.message  # the technical sentence printed above
t.visibility.simple_message  # same, without magnetic coordinates
t.plain_message()  # the plain-language version
t.visibility_days  # days that reached the level needed here, by year and most recent
t.visibility.next_easier_level  # {"scale": "G2", ..., "short_by_deg": 2.1}
t.history  # per-level history, see below
geomag_aurora.visibility(52.9)  # the same lookup from an AACGM latitude, with no I/O
```

**Categories**
- `noaa_scale` (40–55° AACGM): NOAA's G3/G4/G5 latitudes apply directly.
- `g2_or_lower` (≥ 55°): G2 suffices, and weaker activity often does too. NOAA gives no
  latitude for G1.
- `beyond_noaa_scale` (< 40°): equatorward of NOAA's G5 latitude, so aurora here needs an
  exceptional storm.

`scale_needed` is `null` only in the last case, and `message` says why. Every category
reports `next_easier_level`: the weaker level whose latitude the site just misses, and by how
much. Within 1° (Raleigh is 0.1° short of G4's 45°), the message adds that the weaker level
may also be enough, because NOAA's latitudes are "typical" values, not hard edges.

**History.** NOAA publishes only averages ("X storms, Y days per 11-year cycle"), so
`threshold` also reads the GFZ Kp record (1932 to present) and reports, for each level that
reaches the site:
- `last_date`, `last_kp_max`, and `last_definitive` (GFZ's latest days are provisional and
  revised later)
- `recent_dates`: the last 5 days the level was reached
- `days_last_11_years`, `days_since_start` and `avg_days_per_11_years`
- `noaa_days_per_cycle`, NOAA's published figure, for comparison

A day counts when any of its eight 3-hour Kp values reaches the level's lowest Kp (G1 5- =
4.67, G2 5.67, G3 6.67, G4 7.67, G5 9o = 9.0; NOAA counts 9- as G4). For sites near a weaker
level or beyond the scale, that weaker level or G5 is also listed, marked `*`.

GFZ's day counts are consistently below NOAA's per-cycle figures: about 650 vs 900 G1 days
per 11 years, and 29 vs 60 for G4. NOAA doesn't state how its frequencies were computed, so
both are shown and labelled.

**Visibility days.** `visibility_days` covers the level needed at this site. Every day in
the GFZ record whose Kp reached that level or stronger is counted:
- `per_year`: days per calendar year, from 1932 to now. `partial_year` names the current,
  incomplete year.
- `recent`: the 10 most recent days, newest first, each with `kp_max` and the G level actually
  reached (`scale`), which may be above the level needed.
- `busiest_year`, and `years_with_none_last_11`.

When the site is within 1° of the next easier level, as Raleigh is for G4,
`visibility_days_near_level` gives the same data for that level. Both are `null` for sites
beyond NOAA's scale.

These are days when geomagnetic activity reached the level NOAA associates with aurora
coming into view at the location. They are not confirmed sightings: darkness, weather and
time of day still decide whether anyone saw it. For `g2_or_lower` sites, G2 is an upper
bound, and aurora is visible there on many more days.

The GFZ file is cached in `~/.cache/geomag-aurora/gfz/` and refreshed every 6 hours. If it can't be
downloaded, the lookup still succeeds with `history: null`.

## Aurora outlook

`geomag-aurora aurora` starts from the same threshold and adds SWPC's live forecasts: which
3-hour periods in the next 3 days reach the needed level, SWPC's daily storm probabilities,
and the OVATION nowcast near the site.

```sh
uv run geomag-aurora aurora "Seattle, WA"
uv run geomag-aurora aurora "Seattle, WA" --all     # plus every 3-hour period
uv run geomag-aurora aurora "Seattle, WA" --json    # everything, machine-readable
```

```
Location: Seattle, King County, Washington, United States
Site 47.60, -122.33: AACGM lat 52.9, dipole lat 53.0
At 52.9 deg AACGM, aurora typically needs a G3 storm (Kp 7) or stronger. NOAA: at G3, aurora
has been seen as low as Illinois and Oregon (typically 50 deg geomagnetic latitude).
SWPC forecast through Oct 15 00Z: no period reaches G3; max Kp 5.00.
SWPC daily storm probabilities, mid latitudes (Fredericksburg), issued Oct 11 2200Z:
  Oct 12  Ap 16  active 20%  minor 5%  major-severe 1%
  Oct 13  Ap 28  active 35%  minor 10%  major-severe 1%
  Oct 14  Ap 14  active 20%  minor 5%  major-severe 1%
OVATION nowcast valid 2026-10-12T15:18:00+00:00: 0% overhead, max 0% within 5 deg
```

```python
o = geomag_aurora.aurora_outlook(47.6, -122.3)  # or aurora_outlook(place="Seattle, WA")
o.place  # resolved Place (name, lat, lon) when given by name, else None
o.site  # geographic, dipole and AACGM coordinates
o.visibility  # same Visibility as aurora_threshold (category, message, ...)
o.scale_needed  # "G3"
o.periods  # SWPC 3-hourly Kp + noaa_scale + reaches_site + mlt
o.swpc_daily  # SWPC 1-3 day Ap and storm probabilities (both latitude bands)
o.ovation  # OVATION probability at the site and max within the radius
o.to_dict()  # JSON-ready
```

Where each attribute comes from (the first two rows also apply to `threshold`):

| Attribute | Source | How it's used |
|---|---|---|
| Geomagnetic latitude | AACGM-v2 (Shepherd, 2014) at 110 km, a conventional auroral altitude, via `aacgmv2` (Burrell, van der Meeren & Laundal); centered dipole from the IGRF (NOAA NCEI; Alken et al., 2021) | The AACGM latitude is compared against NOAA's thresholds |
| Scale needed | NOAA Space Weather Scales (SWPC): aurora "has been seen as low as" ~55° (G2), 50° (G3), 45° (G4), 40° (G5) geomagnetic latitude | Used as steps, no interpolation. NOAA gives no latitude for G1, so sites poleward of 55° get "G2 or lower". |
| 3-hourly Kp and G level | SWPC Planetary K-index and 3-Day Forecast products (`noaa-planetary-k-index-forecast.json`); Kp as defined by GFZ (Matzka et al., 2021) | Used as published; `reaches_site` = SWPC's G level ≥ the site's needed level |
| Magnetic local time | AACGM MLT (Shepherd, 2014; Laundal & Richmond, 2017) at the middle of each 3-hour period | Shown for timing; the oval extends furthest equatorward on the nightside, near magnetic midnight |
| Daily storm probabilities | SWPC Report and Forecast of Solar and Geophysical Activity (RSGA, `text/sgarf.txt`), issued 2200 UT | Shown as issued. The band (Fredericksburg mid / College high) closest to the site's AACGM latitude is highlighted. |
| Nowcast | SWPC Aurora 30-Minute Forecast: OVATION Prime (Newell et al., 2009; evaluated by Machol et al., 2012), driven by L1 solar wind | Probability in the site's 1° cell and the maximum within `--radius` degrees (default 5) |

Notes:
- The default 5° OVATION radius (~560 km) is this package's choice. SWPC notes aurora "can be
  observed from as much as 1000 km away when the aurora is bright and if conditions are right"
  (SWPC Aurora Viewline product), so raise `--radius` toward ~9° for a more generous search.
- SWPC's G-scale is defined in Kp, which is a planetary 3-hour index, so timing is only
  as fine as 3 hours.
- SWPC's daily probabilities are for northern stations.

## Command line

`uv sync` installs a `geomag-aurora` executable into `.venv/bin`. Run it with `uv run geomag-aurora ...`,
call `.venv/bin/geomag-aurora` directly, or use `python -m geomag_aurora`.

```sh
uv run geomag-aurora build --start 2015               # download + build data/processed/*.parquet
uv run geomag-aurora build --start 1996 --skip-omni   # SWPC/GFZ only, no 35 MB/yr OMNI download
uv run geomag-aurora verify --start 2023-01-01        # print scores, write verification_*.csv
uv run geomag-aurora --data-dir /elsewhere verify     # global option: cache/output location
uv run geomag-aurora -v build                         # log every file downloaded
```

| Command | Options |
|---|---|
| `build` | `--start YEAR` (default 2015; SWPC text archive begins 1996), `--end YEAR` (default: current year), `--skip-omni` |
| `verify` | `--start DATE`, `--end DATE` (target dates to score) |
| `aurora` | `PLACE` (name, address or "lat, lon") or `--lat`/`--lon`; `--radius DEG`, `--no-ovation`, `--all`, `--json` |
| `threshold` | `PLACE` or `--lat`/`--lon`; `--plain`, `--no-history`, `--json` |
| global | `--data-dir DIR` (default `./data`), `-v/--verbose` |

## Python API: datasets and verification

The aurora functions are covered above, under Aurora threshold and Aurora outlook.

```python
import geomag_aurora

ds = geomag_aurora.build_datasets(2015, 2026, data_dir="data")  # downloads, caches, writes parquet
ds = geomag_aurora.load_datasets("data")  # or read a previous build
ds.kp_3h, ds.daily, ds.swpc_forecasts  # pandas DataFrames

v = geomag_aurora.verify(ds.daily, ds.swpc_forecasts, start="2020-01-01")
v.ap  # MAE/RMSE/bias/r/skill by (lead, model)
v.storm_prob  # Brier score and skill of SWPC's storm probability by lead
print(v.report())
```

`geomag_aurora.assemble(kp_3h, daily, swpc_forecasts, solar_wind_3h)` joins tables you have already
loaded, with no I/O. The per-source loaders are `geomag_aurora.gfz.load`, `geomag_aurora.omni.load` plus
`geomag_aurora.omni.aggregate_3h`, and `geomag_aurora.swpc_rsga.load`.

## Outputs (`data/processed/`)

| File | Grain | Contents |
|---|---|---|
| `kp_3h.parquet` | 3-hour Kp bin (labelled by bin start, UTC) | GFZ `kp`, `ap`, `definitive`, plus OMNI features for the bin: V, n, T, Pdyn, B, By/Bz (GSM), Bz southward fraction, Newell coupling, E, `sw_coverage`. Also AE/AL/SYM-H, which are geomagnetic indices and leak the target if used for the same bin. |
| `daily.parquet` | UT day | GFZ `Ap`, `kp_max`, F10.7, SN, plus SWPC forecasts for that day at leads 1–3 (`swpc_ap_pred_l1`, `swpc_p_mid_minor_l2`, ...) |
| `swpc_forecasts.parquet` | (issue_date, lead) | Parsed SWPC/USAF RSGA: predicted Ap/Afr/F10.7, estimated Ap on the issue day, middle- and high-latitude activity probabilities, `issued_late` |
| `verification_ap.csv`, `verification_storm_prob.csv` | | Written by `geomag-aurora verify` |

Raw downloads are cached in `data/raw/`. The GFZ file, the last two OMNI years and the
current SWPC year are refreshed on later builds. Everything else is downloaded once.
A cached rebuild takes about 20 s.

## Sources

- **Target:** [GFZ Potsdam Kp/ap/Ap](https://kp.gfz.de) (Matzka et al., 2021; GFZ data
  publication doi:10.5880/Kp.0001). The latest days are nowcast values, flagged
  `definitive=False`, and are revised later.
- **Drivers:** [NASA OMNI 5-min](https://spdf.gsfc.nasa.gov/pub/data/omni/high_res_omni/)
  (King & Papitashvili, 2005), already shifted to the bow shock nose. It lags real time by
  about 3–4 weeks. The `newell_*` features use the coupling function
  dΦ/dt = V^(4/3) B_T^(2/3) sin^(8/3)(θ/2) of Newell et al. (2007).
- **Baseline:** SWPC daily RSGA forecasts (issued 2200 UT) from
  `ftp://ftp.swpc.noaa.gov/pub/warehouse`. The text archive is parseable from 1996; earlier
  years are scanned PDFs. SWPC does not archive its 3-hourly Kp forecast.

## Verification method

`geomag-aurora verify` scores SWPC's 1–3 day Ap forecasts against GFZ Ap alongside three references,
each of which uses only data available at SWPC's issue time:

- **persistence:** SWPC's estimated Ap for the issue day
- **recurrence:** Ap 27 days before the target day (one solar rotation)
- **climatology:** mean Ap over the 27 days before the issue day

It also computes the Brier (1950) skill score of SWPC's middle-latitude P(minor) + P(major-severe)
against the event "GFZ daily max Kp >= 5", with the in-sample event rate as the reference.
This event definition is approximate, because SWPC defines its middle-latitude categories
with Fredericksburg K. Storm-day metrics only score days that turned out stormy (Ap >= 30),
so every forecast looks biased low on them. Compare the methods to each other rather than
reading those numbers as absolute.

## References

### NOAA Space Weather Prediction Center
- NOAA SWPC. *NOAA Space Weather Scales*. https://www.spaceweather.gov/noaa-scales-explanation
  (G1–G5 Kp thresholds, the geomagnetic latitudes where aurora has been seen, and average
  frequency per 11-year cycle).
- NOAA SWPC. *Planetary K-index*. https://www.spaceweather.gov/products/planetary-k-index
- NOAA SWPC. *3-Day Forecast*. https://www.spaceweather.gov/products/3-day-forecast
- NOAA SWPC. *Report and Forecast of Solar and Geophysical Activity* (RSGA).
  https://www.spaceweather.gov/products/report-and-forecast-solar-and-geophysical-activity;
  archive at ftp://ftp.swpc.noaa.gov/pub/warehouse
- NOAA SWPC. *27-Day Outlook of 10.7 cm Radio Flux and Geomagnetic Indices*.
  https://www.spaceweather.gov/products/27-day-outlook-107-cm-radio-flux-and-geomagnetic-indices
- NOAA SWPC. *Aurora – 30 Minute Forecast* (OVATION Prime).
  https://www.spaceweather.gov/products/aurora-30-minute-forecast
- NOAA SWPC. *Aurora – Viewline for Tonight and Tomorrow Night (Experimental)*.
  https://www.spaceweather.gov/products/aurora-viewline-tonight-and-tomorrow-night-experimental
  (source of the "up to 1000 km" viewing-distance statement; SWPC removed the viewline
  itself from this product in May 2026).
- NOAA SWPC. *Data Access*. https://www.spaceweather.gov/content/data-access
  (services.swpc.noaa.gov JSON and text products).
- NOAA NCEI. *International Geomagnetic Reference Field*.
  https://www.ncei.noaa.gov/products/international-geomagnetic-reference-field

### Data sets
- Matzka, J., Stolle, C., Yamazaki, Y., Bronkalla, O., & Morschhauser, A. (2021). The
  geomagnetic Kp index and derived indices of geomagnetic activity. *Space Weather*, 19(5),
  e2020SW002641. https://doi.org/10.1029/2020SW002641
- Matzka, J., Bronkalla, O., Tornow, K., Elger, K., & Stolle, C. (2021). *Geomagnetic Kp
  index*. V. 1.0. GFZ Data Services. https://doi.org/10.5880/Kp.0001
- King, J. H., & Papitashvili, N. E. (2005). Solar wind spatial scales in and comparisons of
  hourly Wind and ACE plasma and magnetic field data. *Journal of Geophysical Research: Space
  Physics*, 110(A2). https://doi.org/10.1029/2004JA010649 (OMNI; high-resolution
  documentation at https://omniweb.gsfc.nasa.gov/html/HROdocum.html)

### Models and algorithms
- Shepherd, S. G. (2014). Altitude-adjusted corrected geomagnetic coordinates: Definition and
  functional approximations. *Journal of Geophysical Research: Space Physics*, 119(9),
  7501–7521. https://doi.org/10.1002/2014JA020264
- Laundal, K. M., & Richmond, A. D. (2017). Magnetic coordinate systems. *Space Science
  Reviews*, 206(1–4), 27–59. https://doi.org/10.1007/s11214-016-0275-y
- Alken, P., Thébault, E., Beggan, C. D., et al. (2021). International Geomagnetic Reference
  Field: the thirteenth generation. *Earth, Planets and Space*, 73, 49.
  https://doi.org/10.1186/s40623-020-01288-x (method; this package uses the IGRF-14
  coefficients shipped with `aacgmv2`, see the IGRF-14 special issue,
  https://doi.org/10.1186/s40623-026-02504-w).
- Newell, P. T., Sotirelis, T., Liou, K., Meng, C.-I., & Rich, F. J. (2007). A nearly universal
  solar wind-magnetosphere coupling function inferred from 10 magnetospheric state
  variables. *Journal of Geophysical Research: Space Physics*, 112(A1).
  https://doi.org/10.1029/2006JA012015
- Newell, P. T., Sotirelis, T., & Wing, S. (2009). Diffuse, monoenergetic, and broadband
  aurora: The global precipitation budget. *Journal of Geophysical Research: Space Physics*,
  114, A09207. https://doi.org/10.1029/2009JA014326
- Machol, J. L., Green, J. C., Redmon, R. J., Viereck, R. A., & Newell, P. T. (2012).
  Evaluation of OVATION Prime as a forecast model for visible aurorae. *Space Weather*,
  10(3). https://doi.org/10.1029/2011SW000746
- Brier, G. W. (1950). Verification of forecasts expressed in terms of probability. *Monthly
  Weather Review*, 78(1), 1–3.
  https://doi.org/10.1175/1520-0493(1950)078<0001:VOFEIT>2.0.CO;2

### Software and services
- Burrell, A. G., van der Meeren, C., & Laundal, K. M. *aacgmv2* (v2.7.1): Python library for
  AACGM-v2 magnetic coordinates. https://github.com/aburrell/aacgmv2, docs
  https://aacgmv2.readthedocs.io
- OpenStreetMap contributors. *Nominatim* geocoder; data © OpenStreetMap contributors, ODbL.
  https://nominatim.org; usage policy https://operations.osmfoundation.org/policies/nominatim/

## Layout

```
src/geomag_aurora/
  cli.py          geomag-aurora build / verify / aurora / threshold
  threshold.py    aurora_threshold, visibility, NOAA G-scale table, GFZ level history
  aurora.py       aurora_outlook, AuroraOutlook (SWPC forecasts + OVATION on top of threshold)
  geocoding.py    place name / address / "lat, lon" text -> coordinates (Nominatim, cached)
  site.py         observer location -> dipole / AACGM latitude, magnetic local time
  swpc_live.py    live SWPC Kp forecast, RSGA and OVATION fetchers
  build.py        build_datasets, load_datasets, assemble, Datasets
  verify.py       verify, Verification, reference forecasts and scores
  gfz.py          GFZ Kp file reader
  omni.py         OMNI 5-min fixed-width reader, 3-hour features, Newell coupling
  swpc_rsga.py    SWPC RSGA text parser and archive loader
  download.py     cached HTTPS/FTP downloads
  cache.py        cleanup of cached downloads and lookups (hidden `geomag-aurora cleanup`)
tests/            offline tests (network calls are faked); fixtures are real RSGA files from 1998 and 2025
  test_parsers.py         RSGA, OMNI and GFZ readers
  test_build_verify.py    dataset assembly and verification scores
  test_cli.py             build / verify commands
  test_aurora.py          site coordinates, outlook, OVATION lookup
  test_threshold.py       NOAA thresholds, messages, history, visibility days
  test_geocode.py         place-name lookup, caching, coordinate parsing
  test_io.py              cached downloads, FTP mirror, live SWPC fetchers
  test_cache.py           cleanup: dry run, what is kept, empty dirs, symlinks, hidden from --help
```

## Caches

| Location | Contents | Refresh |
|---|---|---|
| `data/raw/` (or `--data-dir`) | GFZ, OMNI and SWPC archive downloads for `build` | GFZ every 6 h; current/previous OMNI year daily; current SWPC year on each build |
| `~/.cache/geomag-aurora/geocode.json` | place-name lookups | never; delete to refresh |
| `~/.cache/geomag-aurora/gfz/` | GFZ Kp file for `threshold` history | every 6 h |

`~/.cache` honours `XDG_CACHE_HOME`. Live SWPC products for `aurora` are not cached.

### Cleaning up

`cleanup` is not listed in `geomag-aurora --help`:

```sh
uv run geomag-aurora cleanup --dry-run             # list what would be removed, delete nothing
uv run geomag-aurora cleanup                       # remove it
uv run geomag-aurora --data-dir /elsewhere cleanup # for a non-default data directory
```

It deletes every file under `data/raw/` (or `<data-dir>/raw/`) and `~/.cache/geomag-aurora/`, then
removes the directories left empty, including the data directory itself if nothing else is
in it. Built outputs in `data/processed/` are kept. Symlinks are removed as links and never
followed. Everything is downloaded again on next use, which is about 35 MB per OMNI year for
`build`. From Python: `geomag_aurora.cache.cleanup(data_dir="data", dry_run=True)` returns a report
listing the files and directories.
