skeledance/tools/build_catalog.py
vini 998de4a945 feat: catálogo de estrelas embutido (Yale BSC, V<=5.5, 2887 estrelas)
tools/build_catalog.py baixa o bsc5-short.json, converte RA/Dec/V/K e
emite starcatalog.js compacto (65KB) com paleta de cores por
temperatura (corpo negro dessaturado).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015HtPHsTnDcLVSCiF5FGHZj
2026-07-19 10:52:05 -03:00

117 lines
3.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Gera plugin/contents/ui/starcatalog.js a partir do Yale Bright Star Catalog.
Baixa bsc5-short.json (RA/Dec/V/K), filtra V <= MAG_LIMIT, converte a
temperatura de cor em RGB (aprox. corpo negro, quantizada numa paleta pequena)
e emite um JS compacto:
const STAR_PALETTE = [[r,g,b], ...];
const STAR_DATA = [[raDeg, decDeg, vmag, colorIdx], ...]; // ordenado por mag
Uso: python3 tools/build_catalog.py
"""
import json
import math
import re
import urllib.request
from pathlib import Path
URL = "https://raw.githubusercontent.com/brettonw/YaleBrightStarCatalog/master/bsc5-short.json"
OUT = Path(__file__).resolve().parent.parent / "plugin/contents/ui/starcatalog.js"
MAG_LIMIT = 5.5
# Limites (K) e centro representativo de cada faixa de temperatura
TEMP_BINS = [
(0, 3500, 3000), # M — alaranjado-avermelhado
(3500, 4500, 4000), # K — laranja
(4500, 5500, 5000), # G — amarelo-branco
(5500, 6500, 6000), # F — branco-amarelado
(6500, 8000, 7200), # A — branco
(8000, 11000, 9500), # B tardio — branco-azulado
(11000, 16000, 13000), # B — azul-branco
(16000, 10**9, 20000), # O/B quente — azul
]
SATURATION = 0.6 # estrelas reais parecem quase brancas; 1.0 = cor plena
def blackbody_rgb(kelvin: float) -> tuple[int, int, int]:
"""Aproximação de Tanner Helland (válida ~1000K-40000K)."""
t = kelvin / 100.0
if t <= 66:
r = 255.0
g = 99.4708025861 * math.log(t) - 161.1195681661
b = 0.0 if t <= 19 else 138.5177312231 * math.log(t - 10) - 305.0447927307
else:
r = 329.698727446 * (t - 60) ** -0.1332047592
g = 288.1221695283 * (t - 60) ** -0.0755148492
b = 255.0
def clamp(x):
return max(0, min(255, round(x)))
return clamp(r), clamp(g), clamp(b)
def desaturate(rgb, amount):
r, g, b = rgb
lum = 0.2126 * r + 0.7152 * g + 0.0722 * b
mix = lambda c: round(lum + (c - lum) * amount)
return mix(r), mix(g), mix(b)
def parse_ra(s: str) -> float:
m = re.match(r"(\d+)h\s*(\d+)m\s*([\d.]+)s", s)
h, mi, se = float(m.group(1)), float(m.group(2)), float(m.group(3))
return (h + mi / 60 + se / 3600) * 15.0
def parse_dec(s: str) -> float:
m = re.match(r"([+-])(\d+)°\s*(\d+)\s*([\d.]+)″", s)
sign = -1.0 if m.group(1) == "-" else 1.0
d, mi, se = float(m.group(2)), float(m.group(3)), float(m.group(4))
return sign * (d + mi / 60 + se / 3600)
def temp_index(kelvin: float) -> int:
for i, (lo, hi, _) in enumerate(TEMP_BINS):
if lo <= kelvin < hi:
return i
return len(TEMP_BINS) - 1
def main():
raw = json.loads(urllib.request.urlopen(URL, timeout=30).read())
stars = []
for entry in raw:
if "V" not in entry or "RA" not in entry or "Dec" not in entry:
continue
vmag = float(entry["V"])
if vmag > MAG_LIMIT:
continue
kelvin = float(entry.get("K", 6000))
stars.append((
round(parse_ra(entry["RA"]), 2),
round(parse_dec(entry["Dec"]), 2),
round(vmag, 2),
temp_index(kelvin),
))
stars.sort(key=lambda s: s[2]) # mais brilhantes primeiro
palette = [desaturate(blackbody_rgb(center), SATURATION)
for _, _, center in TEMP_BINS]
rows = ",\n".join(
f"[{ra},{dec},{mag},{ci}]" for ra, dec, mag, ci in stars
)
OUT.write_text(
"/* Gerado por tools/build_catalog.py — NÃO editar à mão.\n"
f" Yale Bright Star Catalog, V <= {MAG_LIMIT}, {len(stars)} estrelas.\n"
" Formato: [raGraus, decGraus, magV, idxPaleta] ordenado por magnitude. */\n"
f"const STAR_PALETTE = {json.dumps([list(p) for p in palette])};\n"
f"const STAR_DATA = [\n{rows}\n];\n"
)
print(f"{len(stars)} estrelas -> {OUT} ({OUT.stat().st_size / 1024:.0f} KB)")
if __name__ == "__main__":
main()