skyfielders / python-skyfield

Elegant astronomy for Python
MIT License
1.43k stars 213 forks source link

Names and New Cross Index #39

Closed dreamalligator closed 6 years ago

dreamalligator commented 9 years ago

The New Cross Index (IV/27A) could be used to populate the Star name. The particular instance I wanted to use this was to look up both the Bayer designation and visual magnitude using the Hipparcos number.

from skyfield.data import hipparcos
s = hipparcos.get('25930')

s.names is currently [('HIP', 25930)].

from skyfield.units import Angle

def cross_index(catalog, name):
    '''Uses the New Cross Index (`IV/27A <http://cdsarc.u-strasbg.fr/viz-bin/Cat?IV/27A>`_) to look up designations between catalogs. It also includes the visual magnitude, right ascension, magnitude and constellation abbreviation.

    Designations:

    Henry Draper Catalog Number ``HD`` (`III/135 <http://vizier.u-strasbg.fr/viz-bin/VizieR?-source=III/135>`_), 
    Durchmusterung Identification from HD Catalog ``DM`` (see `IV/27A note 1 <http://cdsarc.u-strasbg.fr/viz-bin/Cat?IV/27A#sRM3.1>`_), 
    General Catalogue of 33342 stars ``GC`` (`I/113 <http://vizier.u-strasbg.fr/viz-bin/VizieR?-source=I/113>`_), 
    Harvard Revised Number - BSC5 ``HR`` (`V/50 <http://vizier.u-strasbg.fr/viz-bin/VizieR?-source=V/50>`_), 
    Hipparcos Catalog ``HIP`` (`I/196 <http://vizier.u-strasbg.fr/viz-bin/VizieR?-source=I/196>`_)

    You can look solely for the Bayer ``Bayer`` *or* Flamsteed number ``Fl``, or return either with ``BFD``.

    `IV/27A note 2 <http://cdsarc.u-strasbg.fr/viz-bin/Cat?IV/27A#sRM3.2>`_ on HIP and CSI (`IV/9 <http://vizier.u-strasbg.fr/viz-bin/VizieR?-source=IV/9>`_) right ascensions and visual magnitude:

        Right ascensions, declinations and visual magnitudes for all stars were taken from the Hipparcos catalog and from the CSI for the stars that has no number in catalog Hipparcos.

    Here is an example of looking up a star in Orion's belt by using the Hipparcos number:

    ::

        sn_dict = cross_index('HIP','25930')
        print(sn_dict['Bayer'],sn_dict['Vmag'],sn_dict['Cst'])

    The greek alphabet dictionary was appreciatively taken from Pystaratlas' `catalogues.py <https://code.google.com/p/pystaratlas/source/browse/catalogues.py>`_.

    Excluding DE to Dec as a key abbreviation and the addition of epoch, the labels are the same as the New Cross Index.
    '''

    alphabet={'alf  ':'α','alf01':'α¹','alf02':'α²',\
          'bet  ':'β','bet01':'β¹','bet02':'β²','bet03':'β³',\
          'gam  ':'γ','gam01':'γ¹','gam02':'γ²','gam03':'γ³',\
          'del  ':'δ','del01':'δ¹','del02':'δ²','del03':'δ³',\
          'eps  ':'ε','eps01':'ε¹','eps02':'ε²','eps03':'ε³',\
          'zet  ':'ζ','zet01':'ζ¹','zet02':'ζ²','zet03':'ζ³',\
          'eta  ':'η','eta01':'η¹','eta02':'η²','eta03':'η³',\
          'the  ':'θ','the01':'θ¹','the02':'θ²','the03':'θ³',\
          'iot  ':'ι','iot01':'ι¹','iot02':'ι²',\
          'kap  ':'κ','kap01':'κ¹','kap02':'κ²',\
          'lam  ':'λ','lam01':'λ¹','lam02':'λ²','lam03':'λ³',\
          'mu.  ':'μ','mu.01':'μ¹','mu.02':'μ²','mu.03':'μ³',\
          'nu.  ':'ν','nu.01':'ν¹','nu.02':'ν²','nu.03':'ν³',\
          'ksi  ':'ξ','ksi01':'ξ¹','ksi02':'ξ²','ksi03':'ξ³',\
          'omi  ':'ο','omi01':'ο¹','omi02':'ο²','omi03':'ο³',\
          'pi.  ':'π','pi.01':'π¹','pi.02':'π²','pi.03':'π³','pi.04':'π⁴','pi.05':'π⁵','pi.06':'π⁶',\
          'rho  ':'ρ','rho01':'ρ¹','rho02':'ρ²','rho03':'ρ³',\
          'sig  ':'σ','sig01':'σ¹','sig02':'σ²','sig03':'σ³',\
          'tau  ':'τ','tau01':'τ¹','tau02':'τ²','tau03':'τ³','tau04':'τ⁴','tau05':'τ⁵','tau06':'τ⁶','tau07':'τ⁷','tau08':'τ⁸','tau09':'τ⁹',\
          'ups  ':'υ','ups01':'υ¹','ups02':'υ²','ups03':'υ³',\
          'phi  ':'φ','phi01':'φ¹','phi02':'φ²','phi03':'φ³','phi04':'φ⁴',\
          'chi  ':'χ','chi01':'χ¹','chi02':'χ²','chi03':'χ³',\
          'psi  ':'ψ','psi01':'ψ¹','psi02':'ψ²','psi03':'ψ³','psi04':'ψ⁴','psi05':'ψ⁵','psi06':'ψ⁶','psi07':'ψ⁷','psi08':'ψ⁸','psi09':'ψ⁹',\
          'ome  ':'ω','ome01':'ω¹','ome02':'ω²','ome03':'ω³'}

    cross_index_url = 'ftp://cdsarc.u-strasbg.fr/pub/cats/IV/27A/catalog.dat'
    star_dict = {}

    #TODO: if used in skyfield load/cache can be used instead of wget. https://github.com/brandon-rhodes/python-skyfield/blob/master/skyfield/data/hipparcos.py and https://github.com/brandon-rhodes/python-skyfield/blob/master/skyfield/io.py

    try:
        data = open('catalog.dat','r')
    except:
        import wget
        wget.download(cross_index_url)
        data = open('catalog.dat','r')

    for l in data.readlines():
        s = {}
        s['HD']   = l[0:6]
        s['DM']   = l[7:19]
        s['GC']   = l[20:25]
        s['HR']   = l[26:30]
        if(s['HR'] == '    '): s['HR'] = None
        s['HIP']  = l[31:37]
        if(s['HIP'] == '      '): s['HIP'] = None
        ra = float(l[38:40])+float(l[40:42])/60.+float(l[42:47])/3600.
        s['RA']   = Angle(degrees=float(ra))
        if(l[48]=='+'): 
            sign=1
        else:
            sign=-1
        dec = sign*float(l[49:51])+float(l[51:53])/60.+float(l[53:57])/3600.
        s['Dec']  = Angle(degrees=float(dec))
        s['Vmag'] = l[58:63]
        s['Fl']   = l[64:67]
        s['Bayer']= l[68:73]
        if(s['Bayer'] == '     '):
            s['Bayer'] = None
        else:
            try:
                s['Bayer'] = alphabet[s['Bayer']]
            except:
                pass
        if(s['Bayer']):
            s['BFD'] = s['Bayer']
        elif(s['Fl']):
            s['BFD'] = s['Fl']
        else:
            s['BFD'] = None
        s['Cst']  = l[74:77]
        s['epoch'] = 2000 # The New Cross Index (IV/27A) uses epoch 2000.
        # This function could be modified to look at a range of magnitudes, positions, or by constellation.
        if(s[catalog] is not None and s[catalog].lower().strip() == name.lower().strip()):
            return s
    return False

Dictionary of names returned that matches the Hipparcos number 25930

n = cross_index('HIP','25930')
print(n)
print(n['Bayer'])

And the Bayer designation '\xce\xb4' = δ.

{'HIP': ' 25930', 'DM': 'BD-00   983 ', 'Vmag': ' 2.25', 'HR': '1852', 'BFD': '\xce\xb4', 'epoch': 2000, 'GC': ' 6847', 'RA': <Angle 05deg 32' 00.4">, 'Cst': 'Ori', 'Bayer': '\xce\xb4', 'Dec': <Angle 00deg 17' 56.7">, 'Fl': ' 34', 'HD': ' 36486'}
δ
s.names = n
print(s.names)
print(s.names['Bayer'])
{'HIP': ' 25930', 'DM': 'BD-00   983 ', 'Vmag': ' 2.25', 'HR': '1852', 'BFD': '\xce\xb4', 'epoch': 2000, 'GC': ' 6847', 'RA': <Angle 05deg 32' 00.4">, 'Cst': 'Ori', 'Bayer': '\xce\xb4', 'Dec': <Angle 00deg 17' 56.7">, 'Fl': ' 34', 'HD': ' 36486'}
δ

My apologies for all the issues this week. I've just been integrating Skyfield into a project and it has been useful enough that I've read through the code a few times :).

What do you think of instead of setting names=[('HIP','25930')], the format could be a dictionary with the catalog abbreviation as the key? Or of course left as is. The function could possibly be used as a standalone method, or in a Star class method. If used internally with the Hipparcos functions, since position is already populated, the values of RA and Dec should be popped out so that there arent duplicates like in the above example.

If this fits in with what you want for Skyfield, let me know and I can make a pull request for it.

brandon-rhodes commented 9 years ago

Changing names to a dictionary might be a good idea! That would solve the problem of someone who needs to dive in and select one particular name, or test for its presence. I do think it would be an improvement, so let’s make that change as part of this issue.

I do not see a license associated with the PyStarAtlas project, so we had better steer clear of including any of their code in Skyfield lest we compromise our own license.

Adding a new module to load the cross index is a good idea, and it can live in the data directory next to hipparcos. Instead of reading through the whole cross index for one star, I am tempted to either read the whole thing into RAM as a dictionary (depending on how big it is), or else have a conversion routine that will re-save it as a live sqlite3 database with indexing turned on so that particular stars can be looked up immediately without the user having to re-read the whole file for each one. Does that make sense?

The code you have pasted in will save me the trouble of learning to parse the file — thanks! Would you like to submit it (minus the excerpt from PyStarAtlas) as a skyfield/data/new_cross.py file via a pull request so that you get credit for having put together this first version of the code?

dreamalligator commented 9 years ago

Okay, I made the pull request. Among the changes, names is now a dictionary. I named the file new_cross.py, but of course feel free to change the name of the function to something other than new_cross. I would have named it parse like in hipparcos.py but I didn't know if you wanted to make that a catalog convention or stay away from equivalent method names. I like your idea about loading the data into a database or RAM as a dictionary.

In the docs a note might be made on mixing catalogs. It is up to the programmer, but maybe suggest when setting my_star.names = new_cross('HIP',my_star.names['HIP']) when my_star = hipparcos.get('25930'). A quick my_star.names.pop('RA') and my_star.names.pop('Dec') would remove confusing the position data.

I didn't include the pystaratlas alphabet dictionary. Pystaratlas is GNU GPL v3. Would this function be an okay drop-in replacement? It handles any greek and number instead of hardcoding.

It follows the format found in the New Cross Index where two digits can be appended to the abbreviations.

Abbreviations for the greek letters (the same as in catalog Hipparcos):
    alpha = alf   beta= bet   gamma= gam   delta= del   epsilon= eps
    dzeta = zet   eta = eta   theta= the   iota = iot   kappa  = kap
    lambda= lam   mu  = mu.   nu   = nu.   xi   = ksi   omicron= omi
    pi    = pi.   rho = rho   sigma= sig   tau  = tau   upsilon= ups
    phi   = phi   chi = chi   psi  = psi   omega= ome
def greek(s):

    a = {'alf':u'α','bet':u'β','gam':u'γ','del':u'δ','eps':u'ε','zet':u'ζ','eta':u'η','the':u'θ','iot':u'ι','kap':u'κ','lam':u'λ','mu.':u'μ','nu.':u'ν','ksi':u'ξ','omi':u'ο','pi.':u'π','rho':u'ρ','sig':u'σ','tau':u'τ','ups':u'υ','phi':u'φ','chi':u'χ','psi':u'ψ','ome':u'ω'}
    n = u'⁰¹²³⁴⁵⁶⁷⁸⁹'

    try:
        letter = a[s[0:3]]
    except:
        return None
    try:
        letter = letter+n[int(s[3:5])]
    except:
        try:
            letter = letter+n[int(s[3])]+n[int(s[4])]
        except:
            pass

    #print(letter)
    return letter

nu03 = greek('nu.03')
pi = greek('pi.  ')
bet = greek('bet  ')
# made these ones up to test no breakage for nontypical bayer formatted strings
psi47 = greek('psi47')
valid0 = greek('eta 7')
valid1 = greek('kap9 ')
valid2 = greek('     ')
invalid0 = greek('    7')
invalid1 = greek('   00')
print(nu03,pi,bet,psi47,valid0,valid1,valid2,invalid0,invalid1)

Output:

ν³
π
β
ψ⁴⁷
η⁷
κ⁹
(u'\u03bd\xb3', u'\u03c0', u'\u03b2', u'\u03c8\u2074\u2077', u'\u03b7\u2077', u'\u03ba\u2079', None, None, None)

Tests and Usage

from skyfield.data import new_cross
mintaka_names = new_cross.new_cross('HIP','25930')
print(mintaka_names)

Output:

{'HIP': ' 25930', 'DM': 'BD-00   983 ', 'Vmag': ' 2.25', 'HR': '1852', 'BFD': 'del  ', 'GC': ' 6847', 'RA': <Angle 05deg 32' 00.4">, 'Cst': 'Ori', 'Bayer': 'del  ', 'Dec': <Angle 00deg 17' 56.7">, 'Fl': ' 34', 'HD': ' 36486'}

Test that name is now by default an empty dict {}.

from skyfield.starlib import Star
mystar = Star(ra_hours=5.5334446452722048, dec_degrees=-0.29909203888888819, ra_mas_per_year=1.67, dec_mas_per_year=0.56, parallax_mas=3.56)
mystar.names

I tested the modified hipparcos.get using my version of the load function https://github.com/brandon-rhodes/python-skyfield/pull/36.

from skyfield.data import hipparcos
star = hipparcos.get('25930')
print(star)
print(star.names['HIP'])

Output:

Star(ra_hours=5.5334446452722048, dec_degrees=-0.29909203888888819, ra_mas_per_year=1.67, dec_mas_per_year=0.56, parallax_mas=3.56, names={'HIP': 25930})
25930

This uses your Assay instead of Unittest right? After install what is the command to run these tests?

Thanks for letting me add this to the project :)

nachoplus commented 9 years ago

Hi.

I am Nacho Mas, the pystaratlas developer. Feel free to use anything of my code. I will be proud if could be usefull to improve your amazing work in any way.

Cheers

brandon-rhodes commented 9 years ago

Thank you, @nachoplus, for your permission! I will be sure to give credit if we re-use any code.

@digitalvapor — I have been busy adding Jovian moon support to Skyfield, but have kept star names in the back of my mind, and am trying to think of a way around a dict-per-star. I am thinking of people who might load up millions of stars to do detailed finder charts and things like that. Is that a silly case to think about? I mean, no one ever puts millions of stars on a single printed page — the number of objects shown on a page is always in, what, the hundreds at most?

Anyway, I want to make sure that loading up a huge star atlas and selecting stars by name, or of filtering for the stars that belong in a particular image, is effective, and I'll hopefully have more ideas about that by the time that I get done with the Jovian moons.

dreamalligator commented 9 years ago

@brandon-rhodes I don't think that is silly at all. One of my projects is using IPHAS with Skyfield. Although I definitely won't be naming these stars, I see the potential. If I were to name proper-named/Bayer-designated stars, then I'd have duplicate indexes. Please keep mulling that over :+1:

brandon-rhodes commented 6 years ago

I've finally come up with a way to handle big star catalogs, and have commented on the pull request about the approach. Since there's now a way forward I'm going to remove myself from this issue until we finish over on the pull request. Good luck, and thanks!

dreamalligator commented 6 years ago

thanks!