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
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
#!/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()
|