Skip to content

Commit

Permalink
Merge pull request #406 from NASA-Planetary-Science/388-adding-functi…
Browse files Browse the repository at this point in the history
…onality-to-unpack-mpcs-new-lsst-era-extended-packed-designation-format

Extended packed designation functionality
  • Loading branch information
hhsieh00 authored Sep 6, 2024
2 parents df7f292 + e8f5699 commit 813fe61
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 42 deletions.
14 changes: 14 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
0.6.0 (unreleased)
==================

New Features
------------

sbpy.names
^^^^^^^^^^
- Added functionality to `sbpy.Names.from_packed()` and
`sbpy.Names.to_packed()` to handle new extended provisional designations
to be implemented by the MPC in anticipation of higher asteroid discovery
rates in the LSST survey era [#406]


0.5.0 (2024-08-28)
==================

Expand Down
4 changes: 4 additions & 0 deletions docs/sbpy/data/names.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,13 @@ numbers and unpacked ones:

>>> Names.from_packed('J95A01A')
'1995 AA1'
>>> Names.from_packed('_RD0aEM')
'2027 DZ6190'
>>> Names.from_packed('G3693')
163693
>>> Names.to_packed('1995 AA1')
'J95A01A'
>>> Names.to_packed('2027 DZ6190')
'_RD0aEM'
>>> Names.to_packed('163693')
'G3693'
141 changes: 102 additions & 39 deletions sbpy/data/names.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import re
import math
from ..exceptions import SbpyException

__all__ = ['Names', 'TargetNameParseError', 'natural_sort_key']
Expand Down Expand Up @@ -68,6 +69,9 @@ class Names():
pkd = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
'abcdefghijklmnopqrstuvwxyz')

# packed numbers translation string with no I
pkd_noI = 'ABCDEFGHJKLMNOPQRSTUVWXYZ'

@staticmethod
def to_packed(s):
"""Convert designation or number to packed identifier.
Expand All @@ -94,14 +98,22 @@ def to_packed(s):
s = int(s)
if s < 100000:
return '{:05d}'.format(s)
elif s > 619999:
raise TargetNameParseError(
'{} cannot be turned into a packed number'.format(s)
)
else:
elif (s > 99999 and s < 620000):
mod = (s % 10000)
return '{}{:04d}'.format(
Names.pkd[int((s - mod) / 10000)], mod)
elif (s > 619999 and s < 15396336):
s = s - 620000
d = ['0', '0', '0', '0']
for idx in reversed(range(0, 4)):
d[idx] = Names.pkd[math.floor(s % 62)]
s //= 62
return ('~'+''.join(d))
else:
raise TargetNameParseError(
'{} cannot be turned into a packed number'.format(s)
)

elif s.endswith('P-L'):
return 'PLS{}'.format(s[:4])
elif s[-3:] in ['T-1', 'T-2', 'T-3']:
Expand All @@ -123,42 +135,61 @@ def to_packed(s):
frag = '0'
num = s[6:]

if num == '':
num = '00'
elif len(num) == 1:
num = '0' + num
elif len(num) > 2:
try:
try:
if num == '':
num = '00'
elif len(num) == 1:
num = '0' + num
elif len(num) > 2:
num = Names.pkd[int(num[:-1])]+num[-1]
except (IndexError, ValueError):
raise TargetNameParseError(
('{} cannot be turned into a '
'packed designation').format(s))
return '{}{}{}{}{}'.format(
Names.pkd[int(float(s[:2]))],
s[2:4],
s[5],
num,
frag.lower()
)
return '{}{}{}{}{}'.format(
Names.pkd[int(float(s[:2]))],
s[2:4],
s[5],
num,
frag.lower()
)
except (IndexError, ValueError):
raise TargetNameParseError(
('{} cannot be turned into a '
'packed designation').format(s))
else:
yr = s.strip()[:4]
yr = Names.pkd[int(yr[:2])] + yr[2:]
let = s.strip()[4:7].strip()
num = s.strip()[7:].strip()
if num == '':
num = '00'
elif len(num) == 1:
num = '0' + num
elif len(num) > 2:
try:
num = Names.pkd[int(num[:-1])]+num[-1]
except (IndexError, ValueError):
raise TargetNameParseError(
('{} cannot be turned into a '
'packed designation').format(s))
return (yr + let[0] + num + let[1])

try:
yr = s.strip()[:4]
yr = Names.pkd[int(yr[:2])] + yr[2:]
let = s.strip()[4:7].strip()
num = s.strip()[7:].strip()

if num == '':
return (yr + let[0] + '00' + let[1])
elif len(num) == 1:
return (yr + let[0] + '0' + num + let[1])
elif len(num) > 1:
obj_num = int(num)*25 + Names.pkd_noI.find(let[1]) + 1
# use original packed desigs for first 15500 objs per month
if obj_num < 15501:
num = Names.pkd[int(num[:-1])]+num[-1]
return (yr + let[0] + num + let[1])
# use extended packed desigs for >15500 objs per month
elif obj_num < 14791837:
obj_num = obj_num - 15501
year = Names.pkd[int(yr[1:])]
month = let[0]
d = ['0', '0', '0', '0']
for idx in reversed(range(0, 4)):
d[idx] = Names.pkd[math.floor(obj_num % 62)]
obj_num //= 62
return ('_'+Names.pkd[int(yr[1:])]+let[0]+''.join(d))
# if more than maximum of 14,791,836 objects per half-month
# accommodated by the extended provisional designation scheme
else:
raise TargetNameParseError(
('{} cannot be turned into a '
'packed number or designation').format(s))
except (IndexError, ValueError):
raise TargetNameParseError(
('{} cannot be turned into a '
'packed number or designation').format(s))
else:
raise TargetNameParseError(
('{} cannot be turned into a '
Expand Down Expand Up @@ -189,6 +220,17 @@ def from_packed(p):
return int(p)
elif p[0].isalpha() and p[1:].isdigit():
return int(str(Names.pkd.find(p[0])) + p[1:])
elif p[0] == '~' and p[1:].isalnum():
if len(p) == 5:
obj_num = 620000 + Names.pkd.find(p[1])*(62**3) \
+ Names.pkd.find(p[2])*(62**2) \
+ Names.pkd.find(p[3])*(62) \
+ Names.pkd.find(p[4])
return int(obj_num)
else:
raise TargetNameParseError(
('{} cannot be turned into an '
'unpacked designation').format(p))

# old designation style, e.g.: 1989AB
if (len(p.strip()) < 7 and p[:4].isdigit() and p[4:6].isalpha()):
Expand Down Expand Up @@ -216,6 +258,27 @@ def from_packed(p):
(str(Names.pkd.find(p[4])) + p[5]).lstrip('0'),
'-{}'.format(p[6].upper()) if p[6].islower() else ''
)
# MPC extended packed provisional designation
elif p[0] == '_':
if (
(p[1].isalpha() and p[1].isupper())
and re.search("[A-H,J-Y]", p[2])
and p[3:].isalnum()
and len(p) == 7
):
obj_num = 15501 + Names.pkd.find(p[3])*(62**3) \
+ Names.pkd.find(p[4])*(62**2) \
+ Names.pkd.find(p[5])*(62) \
+ Names.pkd.find(p[6])
return '20{} {}{}{}'.format(
str(Names.pkd.find(p[1])),
p[2],
Names.pkd_noI[((obj_num-1) % 25)],
math.floor((obj_num-1)/25))
else:
raise TargetNameParseError(
('{} cannot be turned into an '
'unpacked designation').format(p))
else:
# nothing to do
return p
Expand Down
79 changes: 76 additions & 3 deletions sbpy/data/tests/test_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ def test_from_packed():
Test values from https://www.minorplanetcenter.net/iau/info/PackedDes.html
Test values for extended permanent designations from
https://www.minorplanetcenter.net/iau/info/PackedDes.html
Test values for extended provisional designations from
https://minorplanetcenter.net/mpcops/documentation/provisional-designation-definition/
"""

# minor planets
Expand Down Expand Up @@ -129,6 +135,22 @@ def test_from_packed():
assert Names.from_packed('K33L89c') == '2033 L89-C'
assert Names.from_packed('K88AA30') == '2088 A103'

# extended permanent designations
assert Names.from_packed('~0000') == 620000
assert Names.from_packed('~000z') == 620061
assert Names.from_packed('~AZaz') == 3140113
assert Names.from_packed('~zzzz') == 15396335

# extended provisional designations
assert Names.from_packed('_QC0000') == '2026 CA620'
assert Names.from_packed('_QC0aEM') == '2026 CZ6190'
assert Names.from_packed('_QCzzzz') == '2026 CL591673'
assert Names.from_packed('_PD0000') == '2025 DA620'
assert Names.from_packed('_QD000N') == '2026 DY620'
assert Names.from_packed('_RD0aEM') == '2027 DZ6190'
assert Names.from_packed('_SEZZZZ') == '2028 EA339749'
assert Names.from_packed('_TFzzzz') == '2029 FL591673'

# a few other tests
assert Names.from_packed('50000') == 50000
assert Names.from_packed('A0345') == 100345
Expand All @@ -142,6 +164,12 @@ def test_to_packed():
Test values from https://www.minorplanetcenter.net/iau/info/PackedDes.html
Test values for extended permanent designations from
https://www.minorplanetcenter.net/iau/info/PackedDes.html
Test values for extended provisional designations from
https://minorplanetcenter.net/mpcops/documentation/provisional-designation-definition/
"""

# minor planets
Expand Down Expand Up @@ -169,6 +197,22 @@ def test_to_packed():
assert Names.to_packed('2033 L89-C') == 'K33L89c'
assert Names.to_packed('2088 A103') == 'K88AA30'

# extended permanent designations
assert Names.to_packed('620000') == '~0000'
assert Names.to_packed('620061') == '~000z'
assert Names.to_packed('3140113') == '~AZaz'
assert Names.to_packed('15396335') == '~zzzz'

# extended provisional designations
assert Names.to_packed('2026 CA620') == '_QC0000'
assert Names.to_packed('2026 CZ6190') == '_QC0aEM'
assert Names.to_packed('2026 CL591673') == '_QCzzzz'
assert Names.to_packed('2025 DA620') == '_PD0000'
assert Names.to_packed('2026 DY620') == '_QD000N'
assert Names.to_packed('2027 DZ6190') == '_RD0aEM'
assert Names.to_packed('2028 EA339749') == '_SEZZZZ'
assert Names.to_packed('2029 FL591673') == '_TFzzzz'

# a few other tests
assert Names.to_packed('50000') == '50000'
assert Names.to_packed('100345') == 'A0345'
Expand Down Expand Up @@ -223,11 +267,40 @@ def test_parse_asteroid():


def test_break_packed():
with pytest.raises(TargetNameParseError):
Names.to_packed('620000')

with pytest.raises(TargetNameParseError):
Names.to_packed('2015 this will not work')

with pytest.raises(TargetNameParseError):
Names.to_packed('thiswillnotwork')


def test_raises_error():
with pytest.raises(TargetNameParseError):
Names.to_packed('2011 AA123456789')

with pytest.raises(TargetNameParseError):
Names.to_packed('2011 A123456789')

with pytest.raises(TargetNameParseError):
Names.to_packed('2026 CL591674')

with pytest.raises(TargetNameParseError):
Names.to_packed('1989 A')

with pytest.raises(TargetNameParseError):
Names.to_packed('15396336')

with pytest.raises(TargetNameParseError):
Names.from_packed('~555555')

with pytest.raises(TargetNameParseError):
Names.from_packed('_QCzzzz0')

with pytest.raises(TargetNameParseError):
Names.from_packed('_Qczzzz')

with pytest.raises(TargetNameParseError):
Names.from_packed('_QCzz_z')

with pytest.raises(TargetNameParseError):
Names.from_packed('_qCzzzz')

0 comments on commit 813fe61

Please sign in to comment.