# HyPlan

[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![Documentation](https://img.shields.io/badge/docs-GitHub_Pages-green.svg)](https://ryanpavlick.github.io/hyplan/)

An open-source Python library for planning airborne remote sensing campaigns.

HyPlan helps scientists and engineers design remote sensing flight missions. It handles flight line generation, sensor modeling, swath coverage, solar glint prediction, cloud analysis, terrain-aware calculations, and mission logistics including airport selection and aircraft performance.

```
 Study Area          Flight Lines      Sensor Swaths         Mission Plan
 ┌─────────┐        ┌─────────┐         ┌─────────┐          ┌─────────┐
 │ ▓▓▓▓▓▓▓ │        │ ──────► │         │▒──────►▒│          │ ✈ ─ ─ ► │
 │ ▓▓▓▓▓▓▓ │  ───►  │ ◄────── │  ───►   │▒◄──────▒│  ───►    │ ──────► │
 │ ▓▓▓▓▓▓▓ │        │ ──────► │         │▒──────►▒│          │ ◄────── │
 └─────────┘        └─────────┘         └─────────┘          │ ─ ─ ✈ ◄ │
  define area      generate lines     compute coverage       └─────────┘
                                                             plan & optimize
```

## Features

- **Flight planning** — Define flight lines, generate multi-line coverage patterns, and compute complete mission plans with takeoff, transit, data collection, and landing segments
- **Pattern workflows** — Build reusable racetrack, rosette, polygon, sawtooth, spiral, and glint-arc patterns as first-class objects that can be edited, serialized, and re-planned
- **Flight optimization** — Automatically order flight lines with multi-day scheduling, endurance constraints, and refueling stops
- **Wind-aware isochrones** — Compute reachability boundaries from a base airport given an aircraft, time budget, and wind field; supports refuel-extended reach with two-clock budget tracking, single-target spot checks, and multi-budget concentric contours; static (Cartopy) and interactive (Folium) plotters
- **Sensor modeling** — Pre-configured NASA instruments (AVIRIS-3, AVIRIS-5, HyTES, PRISM, MASTER, and more) with ground sample distance and swath calculations
- **Lidar & radar** — LVIS full-waveform lidar and UAVSAR L/P/Ka-band SAR sensor models
- **Solar glint prediction** — Predict glint angles across flight lines for water observation missions
- **Solar illumination** — Compute solar position and daily data-collection windows for any site and date
- **Terrain-aware analysis** — Download DEM data and compute where the sensor field of view intersects the ground
- **Cloud cover analysis** — Estimate clear-sky probability from ERA5 reanalysis via Open-Meteo (no auth) or MODIS imagery via Google Earth Engine
- **Wind correction** — Trochoidal Dubins arcs (Sachdev/Moon, 2023) bend ground tracks under wind; per-segment wind from MERRA-2 reanalysis, NOAA GFS forecast, or GMAO GEOS-FP analysis; also constant wind and still-air baselines
- **Atmosphere model** — ISA standard atmosphere with CAS/TAS/Mach airspeed conversions
- **Aircraft performance** — 22 pre-configured research aircraft with climb/cruise/descent profiles; 19 are data-calibrated from in-situ flight-state archives (NASA ASP IWG1, NOAA AOML/CSL ICARTT, CEDA FAAM/EUFAR/MASIN/ARSF, AERIS EUREC4A, PANGAEA, DLR HALO BAHAMAS) or ADS-B globe-history archives (airplanes.live).  Calibrated fleet covers NASA (ER-2, G-III, G-V, WB-57, C-130, P-3, B-200), NOAA (Twin Otter, WP-3D, G-IV), international platforms (NCAR HIAPER, FAAM BAe-146, SAFIRE ATR-42, BAS Twin Otter, NERC Do-228, AWI Polar 5/6 BT-67, DLR HALO), and ADS-B-derived King Air calibrations (A90 fleet aggregate, 350 / UWKA-2).
- **Airport logistics** — Search and filter airports by location, runway length, surface type, and country
- **Satellite coordination** — Predict satellite overpasses and compute ground-track swaths for 14+ satellites
- **Dubins path planning** — Minimum-radius turning trajectories between waypoints for realistic aircraft maneuvering
- **Flight patterns** — Generate racetrack, rosette, spiral, sawtooth, polygon, glint-arc, and coordinated dual-aircraft (five-point line) flight patterns for profiling, survey, and multi-platform missions
- **Campaign persistence** — Organize free-standing lines and reusable patterns with stable IDs, revision metadata, and plain-folder persistence suitable for interactive planning tools
- **Geospatial export** — Output to Excel, KML, GPX, ForeFlight CSV, Honeywell FMS, ER-2, ICARTT, and interactive Folium maps

---

## Installation

### Requirements

- Python 3.10+

### Option 1: pip

```bash
git clone https://github.com/ryanpavlick/hyplan
cd hyplan
pip install -e .
```

### Option 2: conda/mamba

```bash
mamba env create --name hyplan --file environment.yml
mamba activate hyplan
pip install -e .
```

### Optional dependencies

- **Google Earth Engine** (`earthengine-api`) — optional for `hyplan.clouds` (MODIS path); the Open-Meteo path requires no extra dependencies
- **Wind fields** (`xarray`, `netcdf4`, `earthaccess`, `pydap`, `cfgrib`) — install with `pip install hyplan[winds]` for MERRA-2, GFS, and GEOS-FP wind data
- **Planned-sortie ingest** (`openpyxl`, `pdfplumber`) — install with `pip install hyplan[planned]` to parse Green Card mission data cards (XLSX or PDF) and KML route files (used by the ER-2 calibration / planned-vs-flown workflow)

### API keys

Some modules require API keys or tokens. Create a `.env` file in the project root:

```bash
# NASA Earthdata — required for MERRA-2 wind data
# Get a token at https://urs.earthdata.nasa.gov/
EARTHDATA_TOKEN=your_token_here

# OpenAIP — required for airspace data
# Get a key at https://www.openaip.net/
OPENAIP_API_KEY=your_key_here
```

Google Earth Engine (required for `hyplan.clouds`) uses OAuth — run
`earthengine authenticate` once to store credentials locally.

---

## Documentation

Full API documentation is available at **[ryanpavlick.github.io/hyplan](https://ryanpavlick.github.io/hyplan/)**.

To build the documentation locally:

```bash
pip install sphinx myst-parser furo sphinx-autodoc-typehints
cd docs
make html
```

---

## Quick Start

### Define a flight line

```python
from hyplan import FlightLine, ureg

flight_line = FlightLine.start_length_azimuth(
    lat1=34.05, lon1=-118.25,
    length=ureg.Quantity(50, "km"),
    az=45.0,
    altitude_msl=ureg.Quantity(20000, "feet"),
    site_name="LA Northeast",
)
```

### Compute a flight plan

```python
from hyplan import KingAirB200, Airport, compute_flight_plan, plot_flight_plan

aircraft = KingAirB200()
departure = Airport("KSBA")
destination = Airport("KBUR")

plan = compute_flight_plan(aircraft, [flight_line], departure, destination)
plot_flight_plan(plan, departure, destination, [flight_line])
```

### Build a reusable pattern and store it in a campaign

```python
from hyplan import Campaign, NASA_GIII, racetrack, compute_flight_plan, ureg

campaign = Campaign(
    "LA Basin Demo",
    bounds=(-119.0, 33.5, -117.5, 34.5),
)

pattern = racetrack(
    center=(34.0, -118.2),
    heading=90.0,
    altitude=12_000 * ureg.meter,
    leg_length=80 * ureg.kilometer,
    n_legs=3,
    offset=8 * ureg.kilometer,
    name="Basin Wall",
)

pattern_id = campaign.add_pattern(pattern)
plan = compute_flight_plan(NASA_GIII(), [campaign.get_pattern(pattern_id)])
```

Patterns are first-class objects in HyPlan. They can be regenerated with
parameter overrides, edited line-by-line, saved inside a `Campaign`, and
expanded directly by `compute_flight_plan()`.

### Optimize flight line ordering

```python
from hyplan import greedy_optimize

result = greedy_optimize(
    aircraft=aircraft,
    flight_lines=[flight_line],
    airports=[departure, destination],
    takeoff_airport=departure,
    return_airport=destination,
    max_endurance=4.0,
    max_daily_flight_time=8.0,
    max_days=3,
)
print(f"Lines covered: {result['lines_covered']}, Days: {result['days_used']}")
```

### Predict solar glint

```python
from datetime import datetime, timezone
from hyplan import AVIRIS3
from hyplan.glint import compute_glint_vectorized

sensor = AVIRIS3()
obs_time = datetime(2025, 2, 17, 18, 0, tzinfo=timezone.utc)

gdf = compute_glint_vectorized(flight_line, sensor, obs_time)
gdf.to_file("glint_results.geojson", driver="GeoJSON")
```

---

## Modules

```
                        ┌────────────────────────┐
                        │    Flight Planning     │
                        ├────────────────────────┤
                        │  flight_line           │
                        │  pattern               │
                        │  flight_box            │
                        │  flight_plan           │
                        │  flight_optimizer      │
                        │  flight_patterns       │
                        │  waypoint  · campaign  │
                        └───────────┬────────────┘
                                    │
        ┌──────────────┬────────────┼────────────┬──────────────┐
        ▼              ▼            ▼            ▼              ▼
 ┌────────────┐ ┌────────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
 │Instruments │ │  Aircraft  │ │  Environ │ │Logistics │ │Utilities │
 ├────────────┤ ├────────────┤ ├──────────┤ ├──────────┤ ├──────────┤
 │instruments/│ │ aircraft   │ │ sun      │ │ airports │ │ geometry │
 │ line_scan  │ │ atmosphere │ │ glint    │ │ airspace │ │ units    │
 │ lvis       │ │ dubins3d   │ │ terrain  │ │satellites│ │ plotting │
 │ radar      │ │            │ │ clouds   │ │ exports  │ │          │
 │ frame_cam  │ │            │ │ winds    │ │          │ │ download │
 │swath       │ │            │ │          │ │          │ │exceptions│
 └────────────┘ └────────────┘ └──────────┘ └──────────┘ └──────────┘
```

| Module | Description |
|--------|-------------|
| | **Flight Planning** |
| `flight_line` | Create, modify, split, clip, and export individual flight lines |
| `pattern` | First-class reusable flight-pattern container with regeneration and serialization |
| `flight_box` | Generate parallel flight lines covering a geographic area |
| `flight_plan` | Compute complete mission plans with timing and altitude profiles |
| `flight_optimizer` | Graph-based flight line ordering with multi-day and refueling support |
| `planning.isochrone` | Wind-aware reachability boundaries — direct, refuel-extended, concentric multi-budget; single-target spot checks |
| `flight_patterns` | Flight pattern generators returning `Pattern` objects (racetrack, rosette, spiral, sawtooth, polygon, glint arc, coordinated line) |
| `waypoint` | Waypoint class for flight planning with altitude, heading, and speed |
| `campaign` | Campaign manager for organizing free-standing lines and patterns, caching reference data, tracking revisions, and persisting plans |
| | **Instruments** |
| `instruments` | All sensor models — line scanners (AVIRIS-3, AVIRIS-5, HyTES, PRISM, MASTER, etc.), LVIS lidar, UAVSAR SAR, and frame cameras |
| `swath` | Sensor swath coverage with terrain integration |
| | **Aircraft** |
| `aircraft` | Aircraft performance models (22 pre-configured research aircraft, 19 data-calibrated) |
| `atmosphere` | ISA standard atmosphere model, airspeed conversions (CAS/TAS/Mach) |
| `dubins3d` | 2D Dubins path planning [@dubins1957curves] with trochoidal wind support (Sachdev/Moon, 2023); used by the planner via `Aircraft._hybrid_path` (2D horizontal + integrated vertical profile). |
| | **Environment** |
| `sun` | Solar position and timing calculations |
| `glint` | Solar glint angle prediction for water observations |
| `terrain` | DEM data acquisition and ray-terrain intersection |
| `clouds` | Cloud cover analysis and clear-sky probability from ERA5 (Open-Meteo) or MODIS (GEE) |
| `winds` | Wind field models (MERRA-2, GFS, GEOS-FP, IWG1 trace, constant, still air); supplied to `compute_flight_plan` to bend ground tracks via trochoidal Dubins arcs |
| | **Logistics** |
| `airports` | Airport database with search, filtering, and runway data |
| `airspace` | Airspace data from OpenAIP with flight line conflict detection |
| `satellites` | Satellite overpass prediction and swath modeling |
| `exports` | Export flight plans to Excel, KML, GPX, ForeFlight, Honeywell FMS, ER-2, ICARTT |
| | **Utilities** |
| `geometry` | Geospatial utilities (haversine, coordinate transforms, polygons) |
| `units` | Unit conversions using Pint (meters, feet, knots, etc.) |
| `plotting` | Interactive Folium map generation and altitude profiles |
| `download` | File download utility with chunked transfer and timeout support |
| `exceptions` | HyPlan exception hierarchy for targeted error handling |

---

## Notebooks

The [`notebooks/`](notebooks/) directory contains Jupyter notebooks with interactive tutorials and visualizations covering every HyPlan module:

### Getting Started

| Notebook | Description |
|----------|-------------|
| [tutorial.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/tutorial.ipynb) | End-to-end workflow: sensor setup, flight box generation, solar checks, airport selection, optimization, flight planning, and map visualization |
| [validation.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/validation.ipynb) | Validates HyPlan calculations against reference values (Vincenty, NOAA solar, analytical swath/GSD) |
| [campaign_management.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/campaign_management.ipynb) | `Campaign` class for multi-flight, multi-day planning: study area, flight-line groups, save/reload across sessions |

### Flight Planning

| Notebook | Description |
|----------|-------------|
| [flight_line_operations.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/flight_line_operations.ipynb) | Creating, clipping, splitting, offsetting, rotating, and exporting flight lines |
| [flight_box_generation.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/flight_box_generation.ipynb) | Generating parallel flight lines over study areas with swath overlap control |
| [flight_plan_computation.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/flight_plan_computation.ipynb) | Segment-by-segment flight plans with altitude profiles and map visualization |
| [flight_optimizer_demo.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/flight_optimizer_demo.ipynb) | Greedy line ordering with endurance constraints, refueling, and multi-day scheduling |
| [dubins_path_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/dubins_path_planning.ipynb) | Dubins path basics: turn radius, speed/bank effects, and flight line integration |
| [airport_selection.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/airport_selection.ipynb) | Finding, filtering, and comparing airports by location, runway, and aircraft requirements |
| [flight_patterns.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/flight_patterns.ipynb) | Racetrack, rosette, spiral, sawtooth, polygon, and glint-arc flight patterns |
| [airspace_check.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/airspace_check.ipynb) | Detect conflicts between flight lines and FAA / OpenAIP airspace boundaries (restricted, prohibited, controlled) |
| [isochrone.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/isochrone.ipynb) | Wind-aware reachability boundaries: round-trip / one-way / return-safe modes, MERRA-2 winds, fleet comparison, refuel-extended reach, concentric multi-budget contours, single-target spot checks |

### Instruments & Sensors

| Notebook | Description |
|----------|-------------|
| [sensor_comparison.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/sensor_comparison.ipynb) | Comparing GSD, swath width, and critical speed across imaging spectrometers |
| [frame_camera_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/frame_camera_planning.ipynb) | Frame camera FOV, footprints, GSD, and along-track sampling |
| [lidar_lvis_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/lidar_lvis_planning.ipynb) | LVIS lens options, swath geometry, contiguous coverage, and coverage rates |
| [radar_sar_missions.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/radar_sar_missions.ipynb) | UAVSAR L/P/Ka-band swath geometry, resolution, and InSAR line spacing |
| [profiling_lidar_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/profiling_lidar_planning.ipynb) | NASA `ProfilingLidar` family (HSRL-2, HALO, CPL): footprint diameter, horizontal resolution, pulses-per-profile |
| [awp_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/awp_planning.ipynb) | NASA Langley Aerosol Wind Profiler: dual-LOS geometry, stable-segment flagging, terrain-aware profile placement |
| [stereo_oblique_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/stereo_oblique_planning.ipynb) | Stereo photogrammetry and oblique-camera mission design: convergence angle, GSD variation across the frame |

### Environment & Conditions

| Notebook | Description |
|----------|-------------|
| [solar_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/solar_planning.ipynb) | Solar azimuth/elevation, daily collection windows, seasonal and cross-site comparisons |
| [glint_analysis.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/glint_analysis.ipynb) | Glint angle prediction, heading optimization, and time-of-day effects for aquatic missions |
| [glint_arc_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/glint_arc_planning.ipynb) | GlintArc geometry for specular reflection flight paths over water |
| [terrain_aware_planning.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/terrain_aware_planning.ipynb) | DEM-based terrain profiles, AGL variation effects on GSD and swath |
| [cloud_analysis.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/cloud_analysis.ipynb) | MODIS cloud cover from Google Earth Engine, visit simulation, campaign duration planning |
| [cloud_analysis_gee.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/cloud_analysis_gee.ipynb) | Higher-resolution MODIS cloud analysis via Google Earth Engine (1 km), Terra vs Aqua morning/afternoon discrimination |
| [winds.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/winds.ipynb) | Wind field models, flight plan wind correction, MERRA-2 reanalysis demo, direction/speed sensitivity |
| [wind_effects.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/wind_effects.ipynb) | Quantitative wind effects on geometry: crab angle, swath rotation, trochoidal turn drift, along-track sampling |
| [phenology_analysis.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/phenology_analysis.ipynb) | MODIS NDVI/EVI/LAI/FPAR + phenological transition dates for selecting optimal collection windows |

### Aircraft & Satellites

| Notebook | Description |
|----------|-------------|
| [aircraft_performance.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/aircraft_performance.ipynb) | Fleet comparison, speed profiles, climb/descent performance, range/endurance, custom aircraft |
| [calibration/](notebooks/calibration/) | Per-aircraft calibration notebooks for 19 data-calibrated aircraft — NASA fleet (ER-2, G-III, G-V, WB-57, C-130H, P-3, B-200), NOAA fleet (Twin Otter, WP-3D, G-IV), international platforms (HIAPER, FAAM BAe-146, SAFIRE ATR-42, BAS Twin Otter, NERC Do-228, AWI BT-67, DLR HALO), and ADS-B-based King Air calibrations (A90 fleet aggregate, 350 / UWKA-2).  Per-altitude-bin medians, breakpoint selection, bank-angle analysis.  See [docs/calibration.md](docs/calibration.md) for the methodology overview. |
| [calibration/NASA_ER2/sortie_replay.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/calibration/NASA_ER2/sortie_replay.ipynb) | Replay individual ER-2 sorties through the planner; modeled-vs-flown breakdown with multi-sortie scan |
| [calibration/NASA_ER2/planned_vs_flown.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/calibration/NASA_ER2/planned_vs_flown.ipynb) | Compare planned (Green Card / KML) vs flown (IWG1) vs HyPlan-modeled timing for NM17 B / CO07v4 / CO06 |
| [satellite_coordination.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/satellite_coordination.ipynb) | Satellite ground tracks, overpass prediction, and multi-satellite search |

### Export & Integration

| Notebook | Description |
|----------|-------------|
| [export_formats.ipynb](https://nbviewer.org/github/ryanpavlick/hyplan/blob/main/notebooks/export_formats.ipynb) | Export flight plans to Excel, KML, GPX, ForeFlight, Honeywell FMS, ER-2, ICARTT, and text formats |

---

## Known Limitations

- **Flight optimizer** — Uses a greedy nearest-neighbor heuristic; does not guarantee globally optimal ordering; still experimental
- **Terrain module** — DEM downloads require internet access and may be slow for large areas
- **Google Earth Engine** — The `clouds` module's MODIS path requires a Google Earth Engine account; the Open-Meteo path (default) needs no account

---

## Contributing

Contributions are welcome! To get started:

1. Fork the repository and create a feature branch
2. Install in development mode: `pip install -e .`
3. Run the notebooks in `notebooks/` to verify your changes
4. Submit a pull request

Please open an [issue](https://github.com/ryanpavlick/hyplan/issues) for bug reports, feature requests, or questions.

---

## Citation

If you use HyPlan in your research, please cite it as:

```bibtex
@software{hyplan,
  author = {Pavlick, Ryan},
  title = {HyPlan: Planning Software for Airborne Remote Sensing Campaigns},
  url = {https://github.com/ryanpavlick/hyplan},
  license = {Apache-2.0},
  version = {1.6.0}
}
```

Machine-readable citation metadata is also available in [`CITATION.cff`](CITATION.cff).

## License

HyPlan is licensed under the Apache License, Version 2.0. See [`LICENSE`](LICENSE)
or [`LICENSE.md`](LICENSE.md) for details.

## Project Files

- [`LICENSE`](LICENSE) / [`LICENSE.md`](LICENSE.md) — project license and third-party notices
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — development and pull-request workflow
- [`CONTRIBUTORS.md`](CONTRIBUTORS.md) — acknowledged project contributors
- [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md) — community expectations for collaboration

## Bundled data and attribution

HyPlan ships the JPL DE421 planetary ephemeris (`hyplan/data/de421.bsp`)
so solar geometry calculations work fully offline. DE421 is produced by
NASA's Jet Propulsion Laboratory and is in the public domain:

> Folkner, W. M., Williams, J. G., & Boggs, D. H. (2009). *The Planetary
> and Lunar Ephemeris DE 421*. JPL Interoffice Memorandum 343R-08-003.
> <https://ssd.jpl.nasa.gov/planets/eph_export.html>

DE421 is read via the [Skyfield](https://rhodesmill.org/skyfield/)
library (Rhodes 2019, ASCL:1907.024). See [`LICENSE.md`](LICENSE.md) for
the full third-party data attribution.

## Contact

For inquiries or further information, please contact Ryan Pavlick (ryan.p.pavlick@nasa.gov).
