################################################################################
# starcat/ybsc.py
################################################################################
# The Bright Star Catalogue, 5th Revised Ed. (Preliminary Version)
# Hoffleit D., Warren Jr W.H.
# <Astronomical Data Center, NSSDC/ADC (1991)>
# =1964BS....C......0H
# From ftp://cdsarc.u-strasbg.fr/cats/V/50/1
# http://tdc-www.harvard.edu/catalogs/bsc5.html
from __future__ import annotations
import numpy as np
import os
from pathlib import Path
from typing import Any, Iterator, Optional, cast
from filecache import FCPath
from .starcatalog import (AS_TO_RAD,
YEAR_TO_SEC,
Star,
StarCatalog
)
[docs]
class YBSCStar(Star):
"""A holder for star attributes.
This class includes attributes unique to the YBSC catalog.
"""
YBSC_IR_NASA = 0
YBSC_IR_ENGLES = 1
YBSC_IR_UNCERTAIN = 2
YBSC_IR_STRINGS = ['NASA', 'ENGLES', 'UNCERTAIN']
YBSC_MULTIPLE_NONE = ' '
YBSC_MULTIPLE_ASTROMETRIC = 'A'
YBSC_MULTIPLE_DUPLICITY_OCCULTATION = 'D'
YBSC_MULTIPLE_INNES = 'I'
YBSC_MULTIPLE_ROSSITER = 'R'
YBSC_MULTIPLE_DUPLICITY_SPECKLE = 'S'
YBSC_MULTIPLE_WORLEY = 'W'
YBSC_VMAG_UNCERTAINTY_V = ' '
YBSC_VMAG_UNCERTAINTY_HR_REDUCED = 'R'
YBSC_VMAG_UNCERTAINTY_HR = 'H'
[docs]
def __init__(self) -> None:
# Initialize the standard fields
super().__init__()
# Initialize the YBSC-specific fields
self.name: Optional[str] = None
"""Bayer and/or Flamsteed name"""
self.durchmusterung_id: Optional[str] = None
"""Durchmusterung identification"""
self.draper_number: Optional[int] = None
"""Henry Draper Catalog number (out of 225300)"""
self.sao_number: Optional[int] = None
"""SAO Catalog number (out of 258997)"""
self.fk5_number: Optional[int] = None
"""FK5 star number"""
self.ir_source: Optional[bool] = None
"""True if infrared source"""
self.ir_source_ref: Optional[int] = None
"""Infrared source:
NASA, ENGLES, or UNCERTAIN"""
self.multiple_star_code: Optional[str] = None
"""Double or multiple star code:
'A' = Astrometric binary;
'D' = Duplicity discovered by occultation;
'I' = Innes, Southern Double Star Catalogue (1927);
'R' = Rossiter, Michigan Publ. 9, 1955;
'S' = Duplicity discovered by speckle interferometry;
'W' = Worley (1978) update of the IDS"""
self.aitken_designation: Optional[str] = None
"""Aitken's Double Star Catalog (ADS) designation"""
self.ads_components: Optional[str] = None
"""ADS number components"""
self.variable_star_id: Optional[str] = None
"""Variable star identification"""
self.galactic_longitude: Optional[float] = None
"""Galactic longitude (radians)"""
self.galactic_latitude: Optional[float] = None
"""Galactic latitude (radians)"""
self.vmag_code: Optional[str] = None
"""Visual magnitude code:
' ' = V on UBV Johnson system;
'R' = HR magnitudes reduced to the UBV system;
'H' = original HR magnitude"""
self.vmag_uncertainty_flag: Optional[str] = None
"""Uncertainty flag on visual magnitude"""
self.b_v: Optional[float] = None
"""B-V color in the UBV system"""
self.b_v_uncertainty_flag: Optional[str] = None
"""Uncertainty flag on B-V color"""
self.u_b: Optional[float] = None
"""U-B color in the UBV system"""
self.u_b_uncertainty_flag: Optional[str] = None
"""Uncertainty flag on U-B color"""
self.r_i: Optional[float] = None
"""R-I color in the system indicated by r_i_code"""
self.r_i_code: Optional[str] = None
"""Code for R-I system:
'C' = Cousin;
'E' = 'Eggen';
':' = Unknown;
'?' = Unknown;
'D' = Unknown"""
self.spectral_class_code: Optional[str] = None
"""Spectral class code:
'e', 'v', or 't'"""
self.parallax_type: Optional[str] = None
"""Parallax type:
'D' = Dyanmical, otherwise Trigonometric"""
self.parallax: Optional[float] = None
"""Parallax (arcsec); see parallax_type for measurement type"""
self.radial_velocity: Optional[float] = None
"""Radial velocity (km/s)"""
self.radial_velocity_comments: Optional[str] = None
"""Radial velocity comments (multiple possible):
'V' = Variable radial velocity;
'V?' = Suspected variable radial velocity;
'SB', 'SB1', 'SB2', 'SB3' = Spectroscopic binaries,
single/double/triple-lined spectra;
'O' = Orbital data available"""
self.rotational_velocity_limit: Optional[str] = None
"""Rotational velocity limit:
'<', '=', or '>'"""
self.rotational_velocity: Optional[float] = None
"""Rotational velocity [v sin i] (km/s)"""
self.rotational_velocity_uncertainty_flag: Optional[str] = None
"""Rotational velocity uncertainty and variability flag:
' ', ':', or 'v'"""
self.double_mag_diff: Optional[float] = None
"""Magnitude difference of double, or brightest multiple"""
self.double_mag_sep: Optional[float] = None
"""Separation of components in double_mag if occultation binary (radians)"""
self.double_mag_components: Optional[str] = None
"""Indentification of components in double_mag"""
self.multiple_num_components: Optional[int] = None
"""Number of components assigned to a multiple"""
[docs]
def __str__(self) -> str:
ret = Star.__str__(self) + '\n'
ret += f'Name "{self.name}"'
ret += f' | Durch "{self.durchmusterung_id}"'
ret += f' | Draper {self.draper_number}'
ret += f' | SAO {self.sao_number}'
ret += f' | FK5 {self.fk5_number}'
ret += '\n'
if self.ir_source_ref is not None:
ir_ref = YBSCStar.YBSC_IR_STRINGS[self.ir_source_ref]
else:
ir_ref = 'N/A'
ret += f'IR {self.ir_source:d} Ref {ir_ref}'
ret += f' | Multiple "{self.multiple_star_code}"'
ret += f' | Aitken {self.aitken_designation} '
ret += str(self.ads_components)
ret += f' | Variable "{self.variable_star_id}"'
ret += '\n'
ret += f'SCLASS Code {self.spectral_class_code}'
ret += ' | Galactic LON '
if self.galactic_longitude is None:
ret += 'N/A'
else:
ret += str(np.degrees(self.galactic_longitude))
ret += ' LAT '
if self.galactic_latitude is None:
ret += 'N/A'
else:
ret += str(np.degrees(self.galactic_latitude))
ret += '\n'
ret += f'B-V {self.b_v}'
ret += f' | U-B {self.u_b}'
ret += f' | R-I {self.r_i}'
ret += '\n'
ret += 'Parallax '
if self.parallax_type == 'D':
ret += 'DYN'
else:
ret += 'TRIG'
if self.parallax is None:
ret += ' N/A'
else:
ret += f' {self.parallax:.7f} arcsec'
if self.radial_velocity is None:
ret += ' | RadVel N/A'
else:
ret += f' | RadVel {self.radial_velocity} km/s'
ret += f' {self.radial_velocity_comments}'
ret += f' {self.rotational_velocity_limit}'
if self.rotational_velocity is None:
ret += ' | RotVel (v sin i) N/A'
else:
ret += f' | RotVel (v sin i) {self.rotational_velocity} km/s'
ret += f' {self.rotational_velocity_uncertainty_flag}'
ret += '\n'
if self.double_mag_diff is None:
ret += 'Double mag diff N/A'
else:
ret += f'Double mag diff {self.double_mag_diff:.2f}'
if self.double_mag_sep is None:
ret += ' Sep N/A'
else:
ret += f' Sep {self.double_mag_sep / AS_TO_RAD:.2f} arcsec'
ret += f' Components {self.double_mag_components}'
if self.multiple_num_components is None:
ret += ' # N/A'
else:
ret += f' # {self.multiple_num_components:d}'
return ret
# TODO
# star.cat_match = None
# star.num_img_total = None
# star.num_img_used = None
# star.num_cat_pm = None
# star.ra_mean_epoch = None
# star.dec_mean_epoch = None
# star.id_str = None
# star.id_str_ucac2 = None
# --------------------------------------------------------------------------------
# Bytes Format Units Label Explanations
# --------------------------------------------------------------------------------
# 1- 4 I4 --- HR [1/9110]+ Harvard Revised Number
# = Bright Star Number
# 5- 14 A10 --- Name Name, generally Bayer and/or Flamsteed name
# 15- 25 A11 --- DM Durchmusterung Identification (zone in
# bytes 17-19)
# 26- 31 I6 --- HD [1/225300]? Henry Draper Catalog Number
# 32- 37 I6 --- SAO [1/258997]? SAO Catalog Number
# 38- 41 I4 --- FK5 ? FK5 star Number
# 42 A1 --- IRflag [I] I if infrared source
# 43 A1 --- r_IRflag *[ ':] Coded reference for infrared source
# 44 A1 --- Multiple *[AWDIRS] Double or multiple-star code
# 45- 49 A5 --- ADS Aitken's Double Star Catalog (ADS) designation
# 50- 51 A2 --- ADScomp ADS number components
# 52- 60 A9 --- VarID Variable star identification
# 61- 62 I2 h RAh1900 ?Hours RA, equinox B1900, epoch 1900.0 (1)
# 63- 64 I2 min RAm1900 ?Minutes RA, equinox B1900, epoch 1900.0 (1)
# 65- 68 F4.1 s RAs1900 ?Seconds RA, equinox B1900, epoch 1900.0 (1)
# 69 A1 --- DE-1900 ?Sign Dec, equinox B1900, epoch 1900.0 (1)
# 70- 71 I2 deg DEd1900 ?Degrees Dec, equinox B1900, epoch 1900.0 (1)
# 72- 73 I2 arcmin DEm1900 ?Minutes Dec, equinox B1900, epoch 1900.0 (1)
# 74- 75 I2 arcsec DEs1900 ?Seconds Dec, equinox B1900, epoch 1900.0 (1)
# 76- 77 I2 h RAh ?Hours RA, equinox J2000, epoch 2000.0 (1)
# 78- 79 I2 min RAm ?Minutes RA, equinox J2000, epoch 2000.0 (1)
# 80- 83 F4.1 s RAs ?Seconds RA, equinox J2000, epoch 2000.0 (1)
# 84 A1 --- DE- ?Sign Dec, equinox J2000, epoch 2000.0 (1)
# 85- 86 I2 deg DEd ?Degrees Dec, equinox J2000, epoch 2000.0 (1)
# 87- 88 I2 arcmin DEm ?Minutes Dec, equinox J2000, epoch 2000.0 (1)
# 89- 90 I2 arcsec DEs ?Seconds Dec, equinox J2000, epoch 2000.0 (1)
# 91- 96 F6.2 deg GLON ?Galactic longitude (1)
# 97-102 F6.2 deg GLAT ?Galactic latitude (1)
# 103-107 F5.2 mag Vmag ?Visual magnitude (1)
# 108 A1 --- n_Vmag *[ HR] Visual magnitude code
# 109 A1 --- u_Vmag [ :?] Uncertainty flag on V
# 110-114 F5.2 mag B-V ? B-V color in the UBV system
# 115 A1 --- u_B-V [ :?] Uncertainty flag on B-V
# 116-120 F5.2 mag U-B ? U-B color in the UBV system
# 121 A1 --- u_U-B [ :?] Uncertainty flag on U-B
# 122-126 F5.2 mag R-I ? R-I in system specified by n_R-I
# 127 A1 --- n_R-I [CE:?D] Code for R-I system (Cousin, Eggen)
# 128-147 A20 --- SpType Spectral type
# 148 A1 --- n_SpType [evt] Spectral type code
# 149-154 F6.3 arcsec/yr pmRA *?Annual proper motion in RA J2000, FK5 system
# 155-160 F6.3 arcsec/yr pmDE ?Annual proper motion in Dec J2000, FK5 system
# 161 A1 --- n_Parallax [D] D indicates a dynamical parallax,
# otherwise a trigonometric parallax
# 162-166 F5.3 arcsec Parallax ? Trigonometric parallax (unless n_Parallax)
# 167-170 I4 km/s RadVel ? Heliocentric Radial Velocity
# 171-174 A4 --- n_RadVel *[V?SB123O ] Radial velocity comments
# 175-176 A2 --- l_RotVel [<=> ] Rotational velocity limit characters
# 177-179 I3 km/s RotVel ? Rotational velocity, v sin i
# 180 A1 --- u_RotVel [ :v] uncertainty and variability flag on
# RotVel
# 181-184 F4.1 mag Dmag ? Magnitude difference of double,
# or brightest multiple
# 185-190 F6.1 arcsec Sep ? Separation of components in Dmag
# if occultation binary.
# 191-194 A4 --- MultID Identifications of components in Dmag
# 195-196 I2 --- MultCnt ? Number of components assigned to a multiple
# 197 A1 --- NoteFlag [*] a star indicates that there is a note
# (see file notes)
# --------------------------------------------------------------------------------
# Note (1): These fields are all blanks for stars removed from
# the Bright Star Catalogue (see notes).
# Note on r_IRflag:
# Blank if from NASA merged Infrared Catalogue, Schmitz et al., 1978;
# ' if from Engles et al. 1982
# : if uncertain identification
# Note on Multiple:
# A = Astrometric binary
# D = Duplicity discovered by occultation;
# I = Innes, Southern Double Star Catalogue (1927)
# R = Rossiter, Michigan Publ. 9, 1955
# S = Duplicity discovered by speckle interferometry.
# W = Worley (1978) update of the IDS;
# Note on n_Vmag:
# blank = V on UBV Johnson system;
# R = HR magnitudes reduced to the UBV system;
# H = original HR magnitude.
# Note on pmRA:
# As usually assumed, the proper motion in RA is the projected
# motion (cos(DE).d(RA)/dt), i.e. the total proper motion is
# sqrt(pmRA^2^+pmDE^2^)
# Note on n_RadVel:
# V = variable radial velocity;
# V? = suspected variable radial velocity;
# SB, SB1, SB2, SB3 = spectroscopic binaries,
# single, double or triple lined spectra;
# O = orbital data available.
# --------------------------------------------------------------------------------
[docs]
class YBSCStarCatalog(StarCatalog):
[docs]
def __init__(self,
dir: Optional[str | Path | FCPath] = None) -> None:
"""Create a YBSCStarCatalog.
Parameters:
dir: The path to the star catalog directory (may be a URL). Within
this directory should be the file ``catalog``.
"""
super().__init__()
if dir is None:
self._dirname = FCPath(os.environ['YBSC_PATH'])
else:
self._dirname = FCPath(dir)
self._stars = []
with (self._dirname / 'catalog').open(mode='r') as fp:
while True:
record = fp.readline().rstrip()
if record == '':
break
record = record.ljust(197, ' ')
if record[102:107].strip() == '': # No VMAG
continue
star = self._record_to_star(record)
self._stars.append(star)
def _find_stars(self,
ra_min: float,
ra_max: float,
dec_min: float,
dec_max: float,
vmag_min: Optional[float] = None,
vmag_max: Optional[float] = None,
full_result: bool = True,
**kwargs: Any) -> Iterator[YBSCStar]:
# We do this here instead of as specific arguments because it works better
# with mypy
allow_double: bool = kwargs.pop('allow_double', False)
for star in self._stars:
if star.ra is None or star.dec is None:
continue
if not ra_min <= star.ra <= ra_max:
continue
if not dec_min <= star.dec <= dec_max:
continue
if star.vmag is not None:
if vmag_min and star.vmag < vmag_min:
continue
if vmag_max and star.vmag > vmag_max:
continue
if not allow_double and star.multiple_star_code != ' ':
continue
if self.debug_level:
print(star)
print('-' * 80)
yield star
@staticmethod
def _record_to_star(record: str) -> YBSCStar:
star = YBSCStar()
###################
# CATALOG NUMBERS #
###################
# 1- 4 I4 --- HR [1/9110]+ Harvard Revised Number
# = Bright Star Number
# 5- 14 A10 --- Name Name, generally Bayer and/or Flamsteed name
# 15- 25 A11 --- DM Durchmusterung Identification (zone in
# bytes 17-19)
# 26- 31 I6 --- HD [1/225300]? Henry Draper Catalog Number
# 32- 37 I6 --- SAO [1/258997]? SAO Catalog Number
# 38- 41 I4 --- FK5 ? FK5 star Number
star.unique_number = int(record[0:4].strip())
star.name = record[4:14].strip()
star.durchmusterung_id = record[14:25].strip()
if record[25:31].strip() != '':
star.draper_number = int(record[25:31].strip())
if record[31:37].strip() != '':
star.sao_number = int(record[31:37].strip())
if record[37:41].strip() != '':
star.fk5_number = int(record[37:41].strip())
################
# SOURCE FLAGS #
################
# 42 A1 --- IRflag [I] I if infrared source
# 43 A1 --- r_IRflag *[ ':] Coded reference for infrared source
# Note on r_IRflag:
# Blank if from NASA merged Infrared Catalogue, Schmitz et al., 1978;
# ' if from Engles et al. 1982
# : if uncertain identification
# 44 A1 --- Multiple *[AWDIRS] Double or multiple-star code
# Note on Multiple:
# A = Astrometric binary
# D = Duplicity discovered by occultation;
# I = Innes, Southern Double Star Catalogue (1927)
# R = Rossiter, Michigan Publ. 9, 1955
# S = Duplicity discovered by speckle interferometry.
# W = Worley (1978) update of the IDS;
# 45- 49 A5 --- ADS Aitken's Double Star Catalog (ADS) designation
# 50- 51 A2 --- ADScomp ADS number components
# 52- 60 A9 --- VarID Variable star identification
star.ir_source = (record[41] == 'I')
if record[42] == ' ':
star.ir_source_ref = YBSCStar.YBSC_IR_NASA
elif record[42] == '\'':
star.ir_source_ref = YBSCStar.YBSC_IR_ENGLES
elif record[42] == ':':
star.ir_source_ref = YBSCStar.YBSC_IR_UNCERTAIN
star.multiple_star_code = record[43]
if record[44:49].strip() != '':
star.aitken_designation = record[44:49].strip()
if record[49:51].strip() != '':
star.ads_components = record[49:51].strip()
if record[51:60].strip() != '':
star.variable_star_id = record[51:60].strip()
###########
# RA, DEC #
###########
# 76- 77 I2 h RAh ?Hours RA, equinox J2000, epoch 2000.0 (1)
# 78- 79 I2 min RAm ?Minutes RA, equinox J2000, epoch 2000.0 (1)
# 80- 83 F4.1 s RAs ?Seconds RA, equinox J2000, epoch 2000.0 (1)
# 84 A1 --- DE- ?Sign Dec, equinox J2000, epoch 2000.0 (1)
# 85- 86 I2 deg DEd ?Degrees Dec, equinox J2000, epoch 2000.0 (1)
# 87- 88 I2 arcmin DEm ?Minutes Dec, equinox J2000, epoch 2000.0 (1)
# 89- 90 I2 arcsec DEs ?Seconds Dec, equinox J2000, epoch 2000.0 (1)
ra_hr = float(record[75:77])
ra_min = float(record[77:79])
ra_sec = float(record[79:83])
dec_deg = float(record[83:86])
dec_min = float(record[86:88])
dec_sec = float(record[88:90])
sign = 1
if dec_deg < 0:
dec_deg = -dec_deg
sign = -1
star.ra = np.radians((ra_hr/24. + ra_min/24./60 + ra_sec/24./60/60)*360)
star.dec = np.radians(sign*(dec_deg + dec_min/60. + dec_sec/3600.))
########################
# GALACTIC COORDINATES #
########################
# 91- 96 F6.2 deg GLON ?Galactic longitude (1)
# 97-102 F6.2 deg GLAT ?Galactic latitude (1)
star.galactic_longitude = np.radians(float(record[90:96]))
star.galactic_latitude = np.radians(float(record[96:102]))
##############
# MAGNITUDES #
##############
# 103-107 F5.2 mag Vmag ?Visual magnitude (1)
# 108 A1 --- n_Vmag *[ HR] Visual magnitude code
# Note on n_Vmag:
# blank = V on UBV Johnson system;
# R = HR magnitudes reduced to the UBV system;
# H = original HR magnitude.
# 109 A1 --- u_Vmag [ :?] Uncertainty flag on V
# 110-114 F5.2 mag B-V ? B-V color in the UBV system
# 115 A1 --- u_B-V [ :?] Uncertainty flag on B-V
# 116-120 F5.2 mag U-B ? U-B color in the UBV system
# 121 A1 --- u_U-B [ :?] Uncertainty flag on U-B
# 122-126 F5.2 mag R-I ? R-I in system specified by n_R-I
# 127 A1 --- n_R-I [CE:?D] Code for R-I system (Cousin, Eggen)
star.vmag = float(record[102:107])
star.vmag_code = record[107]
star.vmag_uncertainty_flag = record[108]
if record[109:114].strip() != '':
star.b_v = float(record[109:114])
if record[115:120].strip() != '':
star.u_b = float(record[115:120])
if record[121:126].strip() != '':
star.r_i = float(record[121:126])
star.r_i_code = record[126]
##################
# SPECTRAL CLASS #
##################
# 128-147 A20 --- SpType Spectral type
# 148 A1 --- n_SpType [evt] Spectral type code
star.spectral_class = record[127:147].strip()
star.spectral_class_code = record[147]
#######################
# MOTION AND PARALLAX #
#######################
# 149-154 F6.3 arcsec/yr pmRA *?Annual proper motion in RA J2000, FK5 system
# Note on pmRA:
# As usually assumed, the proper motion in RA is the projected
# motion (cos(DE).d(RA)/dt), i.e. the total proper motion is
# sqrt(pmRA^2^+pmDE^2^)
# 155-160 F6.3 arcsec/yr pmDE ?Annual proper motion in Dec J2000, FK5 system
# 161 A1 --- n_Parallax [D] D indicates a dynamical parallax,
# otherwise a trigonometric parallax
# 162-166 F5.3 arcsec Parallax ? Trigonometric parallax (unless n_Parallax)
# 167-170 I4 km/s RadVel ? Heliocentric Radial Velocity
# 171-174 A4 --- n_RadVel *[V?SB123O ] Radial velocity comments
# Note on n_RadVel:
# V = variable radial velocity;
# V? = suspected variable radial velocity;
# SB, SB1, SB2, SB3 = spectroscopic binaries,
# single, double or triple lined spectra;
# O = orbital data available.
# 175-176 A2 --- l_RotVel [<=> ] Rotational velocity limit characters
# 177-179 I3 km/s RotVel ? Rotational velocity, v sin i
# 180 A1 --- u_RotVel [ :v] uncertainty and variability flag on
# RotVel
star.pm_rac = float(record[148:154]) * AS_TO_RAD * YEAR_TO_SEC
star.pm_ra = star.pm_rac / np.cos(cast(float, star.dec))
star.pm_dec = float(record[154:160]) * AS_TO_RAD * YEAR_TO_SEC
star.parallax_type = record[160]
if record[161:166].strip() != '':
star.parallax = float(record[161:166])
if record[166:170].strip() != '':
star.radial_velocity = float(record[166:170])
star.radial_velocity_comments = record[170:174].strip()
star.rotational_velocity_limit = record[174:176].strip()
if record[176:179].strip() != '':
star.rotational_velocity = float(record[176:179])
star.rotational_velocity_uncertainty_flag = record[179:180]
# 181-184 F4.1 mag Dmag ? Magnitude difference of double,
# or brightest multiple
# 185-190 F6.1 arcsec Sep ? Separation of components in Dmag
# if occultation binary.
# 191-194 A4 --- MultID Identifications of components in Dmag
# 195-196 I2 --- MultCnt ? Number of components assigned to a multiple
# 197 A1 --- NoteFlag [*] a star indicates that there is a note
# (see file notes)
if record[180:184].strip() != '':
star.double_mag_diff = float(record[180:184])
if record[184:190].strip() != '':
star.double_mag_sep = float(record[184:190]) * AS_TO_RAD
star.double_mag_components = record[190:194].strip()
if record[194:196].strip() != '':
star.multiple_num_components = int(record[194:196])
##################################################
# COMPUTE SPECTRAL CLASS AND SURFACE TEMPERATURE #
##################################################
sclass = star.spectral_class
if sclass[0] == 'g':
sclass = sclass[1:]
sclass = sclass[0:2].strip()
star.temperature = Star.temperature_from_sclass(sclass)
return star