Skip to content

Extended packed designation functionality #406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
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 @@
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'

Check warning on line 140 in sbpy/data/names.py

View check run for this annotation

Codecov / codecov/patch

sbpy/data/names.py#L140

Added line #L140 was not covered by tests
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 @@
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 @@
(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')