from pathlib import Path
from urllib.request import urlopen
CDS_URL = "https://cdsarc.cds.unistra.fr/ftp/VI/42/data.dat"
OUT = Path("src/constellation_data.rs")
IAU_SPELLING = {
"Chamaleon": "Chamaeleon",
"Ophiucus": "Ophiuchus",
"Pisces Austrinus": "Piscis Austrinus",
}
def names_table():
import astropy.coordinates as ac
path = Path(str(ac.__file__)).parent / "data" / "constellation_names.dat"
rows = []
for line in path.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
abbr, full = line.split(maxsplit=1)
rows.append((abbr, IAU_SPELLING.get(full, full)))
if len(rows) != 88:
raise SystemExit(f"expected 88 constellation names, got {len(rows)}")
return rows
def zone_lines():
try:
text = urlopen(CDS_URL, timeout=10).read().decode("ascii")
source = CDS_URL
except Exception as exc: import astropy.coordinates as ac
path = Path(str(ac.__file__)).parent / "data" / "constellation_data_roman87.dat"
text = path.read_text(encoding="ascii")
source = f"astropy bundled copy ({exc.__class__.__name__} from CDS)"
lines = [
ln for ln in text.splitlines() if ln.strip() and not ln.lstrip().startswith("#")
]
if len(lines) != 357:
raise SystemExit(f"expected 357 zone records, got {len(lines)} ({source})")
print(f"source: {source}")
return lines
def main():
names = names_table()
by_lower = {abbr.lower(): abbr for abbr, _ in names}
records = []
for ln in zone_lines():
ra_lo, ra_hi, dec_lo, code = ln.split()
key = code.rstrip("0123456789").lower()
if key not in by_lower:
raise SystemExit(f"unknown constellation code {code!r}")
for tok, span in ((ra_lo, (0.0, 24.0)), (ra_hi, (0.0, 24.0)), (dec_lo, (-90.0, 90.0))):
v = float(tok)
if not span[0] <= v <= span[1]:
raise SystemExit(f"value {tok} out of range in record {ln!r}")
records.append((ra_lo, ra_hi, dec_lo, by_lower[key]))
last = records[-1]
if (float(last[0]), float(last[1]), float(last[2]), last[3]) != (0.0, 24.0, -90.0, "Oct"):
raise SystemExit(f"unexpected final (south-cap) record: {last}")
body = "\n".join(
f" ({float(lo)}, {float(hi)}, {float(dl)}, C::{abbr})," for lo, hi, dl, abbr in records
)
OUT.write_text(
f'''//! Roman (1987) precomputed constellation boundary zones — GENERATED FILE.
//!
//! Generated by `scripts/gen_constellation_table.py`; DO NOT EDIT by hand.
//! Source: ADC/CDS catalogue VI/42 (Roman 1987, PASP 99, 695), the IAU
//! (Delporte 1930) constellation boundaries precomputed as declination-zone
//! records at epoch B1875.0. Record order is the catalogue's: the first
//! matching record wins in the lookup walk (see `crate::constellation`).
use crate::constellation::Constellation as C;
/// `(ra_lo, ra_hi, dec_lo, constellation)` — RA in B1875 hours, Dec in B1875
/// degrees; a record matches when `dec >= dec_lo && ra_lo <= ra < ra_hi`.
pub(crate) const ZONES: [(f64, f64, f64, C); 357] = [
{body}
];
''',
encoding="utf-8",
)
print(f"wrote {OUT} ({len(records)} records)")
scratch = Path("target/constellation_enum_blocks.rs")
scratch.parent.mkdir(exist_ok=True)
variants = "\n".join(
f" /// {full} ({abbr}).\n {abbr}," for abbr, full in names
)
all_arr = "\n".join(f" Constellation::{abbr}," for abbr, _ in names)
abbrs = "\n".join(f' "{abbr}",' for abbr, _ in names)
fulls = "\n".join(f' "{full}",' for _, full in names)
scratch.write_text(
f"// variants\n{variants}\n// ALL\n{all_arr}\n// ABBR\n{abbrs}\n// NAMES\n{fulls}\n",
encoding="utf-8",
)
print(f"wrote {scratch}")
if __name__ == "__main__":
main()