Source code for adam_core.utils.mpc

import numpy.typing as npt
from astropy.time import Time

BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
BASE62_MAP = {BASE62[i]: i for i in range(len(BASE62))}


def _unpack_mpc_date(epoch_pf: str) -> Time:
    # Taken from Lynne Jones' SSO TOOLS.
    # See https://minorplanetcenter.net/iau/info/PackedDates.html
    # for MPC documentation on packed dates.
    # Examples:
    #    1998 Jan. 18.73     = J981I73
    #    2001 Oct. 22.138303 = K01AM138303
    epoch_pf = str(epoch_pf)
    year = int(epoch_pf[0], base=32) * 100 + int(epoch_pf[1:3])
    month = int(epoch_pf[3], base=32)
    day = int(epoch_pf[4], base=32)
    isot_string = "{:d}-{:02d}-{:02d}".format(year, month, day)

    if len(epoch_pf) > 5:
        fractional_day = float("." + epoch_pf[5:])
        hours = int((24 * fractional_day))
        minutes = int(60 * ((24 * fractional_day) - hours))
        seconds = 3600 * (24 * fractional_day - hours - minutes / 60)
        isot_string += "T{:02d}:{:02d}:{:09.6f}".format(hours, minutes, seconds)

    return Time(isot_string, format="isot", scale="tt")


[docs] def convert_mpc_packed_dates(pf_tt: npt.ArrayLike) -> Time: """ Convert MPC packed form dates (in the TT time scale) to MJDs in TT. See: https://minorplanetcenter.net/iau/info/PackedDates.html for details on the packed date format. Parameters ---------- pf_tt : `~numpy.ndarray` (N) MPC-style packed form epochs in the TT time scale. Returns ------- mjd_tt : `~astropy.time.core.Time` (N) Epochs in TT MJDs. """ isot_tt = [] for epoch in pf_tt: isot_tt.append(_unpack_mpc_date(epoch)) return Time(isot_tt)
[docs] def pack_numbered_designation(designation: str) -> str: """ Pack a numbered MPC designation. Examples of numbered designations: Numbered Packed 3202 03202 50000 50000 100345 A0345 360017 a0017 203289 K3289 620000 ~0000 620061 ~000z 3140113 ~AZaz 15396335 ~zzzz Parameters ---------- designation : str MPC numbered designation. Returns ------- designation_pf : str MPC packed numbered designation. Raises ------ ValueError : If the numbered designation cannot be packed. If the numbered designation is larger than 15396335. """ number = int(designation) if number > 15396335: raise ValueError( "Numbered designation is too large. Maximum supported is 15396335." ) if number <= 99999: return "{:05}".format(number) elif (number >= 100000) and (number <= 619999): bigpart, remainder = divmod(number, 10000) return f"{BASE62[bigpart]}{remainder:04}" else: x = number - 620000 number_pf = [] while x: number_pf.append(BASE62[int(x % 62)]) x //= 62 number_pf.reverse() return "~{}".format("".join(number_pf).zfill(4))
[docs] def pack_provisional_designation(designation: str) -> str: """ Pack a provisional MPC designation. Examples of provisional designations: Provisional Packed 1995 XA J95X00A 1995 XL1 J95X01L 1995 FB13 J95F13B 1998 SQ108 J98SA8Q 1998 SV127 J98SC7V 1998 SS162 J98SG2S 2099 AZ193 K99AJ3Z 2008 AA360 K08Aa0A 2007 TA418 K07Tf8A Parameters ---------- designation : str MPC provisional designation. Returns ------- designation_pf : str MPC packed provisional designation. Raises ------ ValueError : If the provisional designation cannot be packed. The provisional designations is not at least 6 characters long. The first 4 characters of the provisional designation are not a year. The 5th character of the provisional designation is not a space. The provisional designation contains a hyphen. The half-month letter is I or Z. """ if len(designation) < 6: raise ValueError( "Provisional designations should be at least 6 characters long." ) if not designation[:3].isdecimal(): raise ValueError( "Expected the first 4 characters of the provisional designation to be a year." ) if designation[4] != " ": raise ValueError( "Expected the 5th character of the provisional designation to be a space." ) if "-" in designation: raise ValueError("Provisional designations cannot contain a hyphen.") year = BASE62[int(designation[0:2])] + designation[2:4] letter1 = designation[5] letter2 = designation[6] cycle = designation[7:] if letter1 in {"I", "Z"}: raise ValueError("Half-month letters cannot be I or Z.") if letter1.isdecimal() or letter2.isdecimal(): raise ValueError("Invalid provisional designation.") cycle_pf = "00" if len(cycle) > 0: cycle_int = int(cycle) if cycle_int <= 99: cycle_pf = str(cycle_int).zfill(2) else: cycle_pf = BASE62[cycle_int // 10] + str(cycle_int % 10) designation_pf = "{}{}{}{}".format(year, letter1, cycle_pf, letter2) return designation_pf
[docs] def pack_survey_designation(designation: str) -> str: """ Pack a survey MPC designation. Examples of survey designations: Survey Packed 2040 P-L PLS2040 3138 T-1 T1S3138 1010 T-2 T2S1010 4101 T-3 T3S4101 Parameters ---------- designation : str MPC survey designation. Returns ------- designation_pf : str MPC packed survey designation. Raises ------ ValueError : If the survey designation cannot be packed. The survey designation does not start with P-L, T-1, T-2, or T-3. """ number = designation[0:4] survey = designation[5:] if survey == "P-L": survey_pf = "PLS" elif survey[0:2] == "T-" and survey[2] in {"1", "2", "3"}: survey_pf = "T{}S".format(survey[2]) else: raise ValueError("Survey designations must start with P-L, T-1, T-2, T-3.") designation_pf = "{}{}".format(survey_pf, number.zfill(4)) return designation_pf
[docs] def pack_mpc_designation(designation: str) -> str: """ Pack a unpacked MPC designation. For example, provisional designation 1998 SS162 will be packed to J98SG2S. Permanent designation 323 will be packed to 00323. TODO: add support for comet and natural satellite designations Parameters ---------- designation : str MPC unpacked designation. Returns ------- designation_pf : str MPC packed form designation. Raises ------ ValueError : If designation cannot be packed. """ # Lets see if its a numbered object try: return pack_numbered_designation(designation) except ValueError: pass # If its not numbered, maybe its a provisional designation try: return pack_provisional_designation(designation) except ValueError: pass # If its a survey designation, deal with it try: return pack_survey_designation(designation) except ValueError: pass err = ( "Unpacked designation '{}' could not be packed.\n" "It could not be recognized as any of the following:\n" " - a numbered object (e.g. '3202', '203289', '3140113')\n" " - a provisional designation (e.g. '1998 SV127', '2008 AA360')\n" " - a survey designation (e.g. '2040 P-L', '3138 T-1')" ) raise ValueError(err.format(designation))
[docs] def unpack_numbered_designation(designation_pf: str) -> str: """ Unpack a numbered MPC designation. Examples of numbered designations: Numbered Unpacked 03202 3202 50000 50000 A0345 100345 a0017 360017 K3289 203289 ~0000 620000 ~000z 620061 ~AZaz 3140113 ~zzzz 15396335 Parameters ---------- designation_pf : str MPC packed numbered designation. Returns ------- designation : str MPC unpacked numbered designation. Raises ------ ValueError : If the numbered designation cannot be unpacked. The packed numbered designation is not at least 4 characters long. """ number = None # Numbered objects (1 - 99999) if designation_pf.isdecimal(): number = int(designation_pf) # Numbered objects (620000+) elif designation_pf[0] == "~": number = 620000 number_pf = designation_pf[1:] for i, c in enumerate(number_pf): power = len(number_pf) - (i + 1) number += BASE62_MAP[c] * (62**power) # Numbered objects (100000 - 619999) else: number = BASE62_MAP[designation_pf[0]] * 10000 + int(designation_pf[1:]) if number is None: raise ValueError("Packed numbered designation could not be unpacked.") else: designation = str(number) return designation
[docs] def unpack_provisional_designation(designation_pf: str) -> str: """ Unpack a provisional MPC designation. Examples of provisional designations: Provisional Unpacked J95X00A 1995 XA J95X01L 1995 XL1 J95F13B 1995 FB13 J98SA8Q 1998 SQ108 J98SC7V 1998 SV127 J98SG2S 1998 SS162 K99AJ3Z 2099 AZ193 K08Aa0A 2008 AA360 K07Tf8A 2007 TA418 Parameters ---------- designation_pf : str MPC packed provisional designation. Returns ------- designation : str MPC unpacked provisional designation. Raises ------ ValueError : If the provisional designation cannot be unpacked. The packed provisional designation is not 7 characters long. The packed provisional designation does not have a year. """ if len(designation_pf) != 7: raise ValueError("Provisional designation must be 7 characters long.") if not designation_pf[1].isdecimal() or not designation_pf[2].isdecimal(): raise ValueError("Provisional designation must have a year.") year = str(BASE62_MAP[designation_pf[0]] * 100 + int(designation_pf[1:3])) letter1 = designation_pf[3] letter2 = designation_pf[6] if letter1.isdecimal() or letter2.isdecimal(): raise ValueError() cycle1 = designation_pf[4] cycle2 = designation_pf[5] number = int(BASE62_MAP[cycle1]) * 10 + BASE62_MAP[cycle2] if number == 0: number_str = "" else: number_str = str(number) designation = "{} {}{}{}".format(year, letter1, letter2, number_str) return designation
[docs] def unpack_survey_designation(designation_pf: str) -> str: """ Unpack a survey MPC designation. Examples of survey designations: Survey Packed PLS2040 2040 P-L T1S3138 3138 T-1 T2S1010 1010 T-2 T3S4101 4101 T-3 Parameters ---------- designation_pf : str MPC packed survey designation. Returns ------- designation : str MPC unpacked survey designation. Raises ------ ValueError : If the survey designation cannot be unpacked. The packed survey designation does not start with PLS, T1S, T2S, or T3S. """ number = int(designation_pf[3:8]) survey_pf = designation_pf[0:3] if survey_pf not in {"PLS", "T1S", "T2S", "T3S"}: raise ValueError( "Packed survey designation must start with PLS, T1S, T2S, or T3S." ) if survey_pf == "PLS": survey = "P-L" if survey_pf[0] == "T" and survey_pf[2] == "S": survey = "T-{}".format(survey_pf[1]) designation = "{} {}".format(number, survey) return designation
[docs] def unpack_mpc_designation(designation_pf: str) -> str: """ Unpack a packed MPC designation. For example, provisional designation J98SG2S will be unpacked to 1998 SS162. Permanent designation 00323 will be unpacked to 323. TODO: add support for comet and natural satellite designations Parameters ---------- designation_pf : str MPC packed form designation. Returns ------- designation : str MPC unpacked designation. Raises ------ ValueError : If designation_pf cannot be unpacked. """ # Lets see if its a numbered object try: return unpack_numbered_designation(designation_pf) except ValueError: pass # Lets see if its a provisional designation try: return unpack_provisional_designation(designation_pf) except ValueError: pass # Lets see if its a survey designation try: return unpack_survey_designation(designation_pf) except ValueError: pass # At this point we haven't had any success so lets raise an error err = ( "Packed form designation '{}' could not be unpacked.\n" "It could not be recognized as any of the following:\n" " - a numbered object (e.g. '03202', 'K3289', '~AZaz')\n" " - a provisional designation (e.g. 'J98SC7V', 'K08Aa0A')\n" " - a survey designation (e.g. 'PLS2040', 'T1S3138')" ) raise ValueError(err.format(designation_pf))