#!/usr/bin/env python3 """Rebuild scripts/data/bright_stars.json from the Yale Bright Star Catalogue. Source: Hoffleit & Warren, *The Bright Star Catalogue, 5th Revised Ed.* (NASA Astronomical Data Center, 1991), distributed by CDS as VizieR V/50. A US-government (NASA ADC) data product, redistributed by CDS without restriction; treated as public domain. No star-chart software data is used. Usage (the catalogue itself is not committed; the script checks its sha256): curl -o /tmp/bsc5_catalog.gz https://cdsarc.cds.unistra.fr/ftp/V/50/catalog.gz python3 scripts/build_bright_stars.py /tmp/bsc5_catalog.gz """ from __future__ import annotations import gzip import hashlib import json import sys from pathlib import Path SOURCE_URL = "https://cdsarc.cds.unistra.fr/ftp/V/50/catalog.gz" SOURCE_SHA256 = "3dc44b1e90be8fbe5bcc7656032560f51275f985c7e3f783c9028e1838ec7bed" MAX_MAGNITUDE = 4.0 OUT = Path(__file__).resolve().parent / "data" / "bright_stars.json" # Asterism polylines by HR number. Each inner list is one continuous stroke. LINES = [ {"id": "big_dipper", "strokes": [[5191, 5054, 4905, 4660, 4554, 4295, 4301, 4660]]}, {"id": "cassiopeia", "strokes": [[21, 168, 264, 403, 542]]}, { "id": "orion", "strokes": [ [2061, 1879, 1790], [2061, 1948, 1903, 1852, 1790], [1948, 2004], [1852, 1713], ], }, ] def _parse(raw: bytes) -> list[dict]: stars: list[dict] = [] for line in raw.decode("latin-1").splitlines(): if len(line) < 107 or not line[75:77].strip() or not line[102:107].strip(): continue # HR entries without a J2000 position or V magnitude (novae, removed objects) vmag = float(line[102:107]) if vmag > MAX_MAGNITUDE: continue hr = int(line[0:4]) ra = (int(line[75:77]) + int(line[77:79]) / 60 + float(line[79:83]) / 3600) * 15 dec = int(line[84:86]) + int(line[86:88]) / 60 + int(line[88:90]) / 3600 if line[83] == "-": dec = -dec stars.append({"hr": hr, "ra": round(ra, 5), "dec": round(dec, 5), "vmag": vmag}) return stars def main(path: str) -> None: packed = Path(path).read_bytes() digest = hashlib.sha256(packed).hexdigest() if digest != SOURCE_SHA256: raise SystemExit(f"sha256 mismatch: {digest}") stars = _parse(gzip.decompress(packed)) known = {star["hr"] for star in stars} for line in LINES: for stroke in line["strokes"]: missing = [hr for hr in stroke if hr not in known] if missing: raise SystemExit(f"{line['id']} needs HR {missing} brighter than {MAX_MAGNITUDE}") payload = { "source": "Yale Bright Star Catalogue, 5th Revised Ed. (Hoffleit & Warren 1991), VizieR V/50", "source_url": SOURCE_URL, "source_sha256": SOURCE_SHA256, "license": "Public domain (NASA Astronomical Data Center product, redistributed by CDS without restriction)", "selection": f"V magnitude <= {MAX_MAGNITUDE}; J2000.0 equinox and epoch; proper motion not applied", "fields": ["hr", "ra_deg_j2000", "dec_deg_j2000", "vmag"], "generator": "scripts/build_bright_stars.py", "lines": LINES, "stars": [[s["hr"], s["ra"], s["dec"], s["vmag"]] for s in stars], } OUT.write_text(json.dumps(payload, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8") print(f"{len(stars)} stars -> {OUT}") if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else "/tmp/bsc5_catalog.gz")