Skip to content

Commit 813fe61

Browse files
authored
Merge pull request #406 from NASA-Planetary-Science/388-adding-functionality-to-unpack-mpcs-new-lsst-era-extended-packed-designation-format
Extended packed designation functionality
2 parents df7f292 + e8f5699 commit 813fe61

File tree

4 files changed

+196
-42
lines changed

4 files changed

+196
-42
lines changed

CHANGES.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
0.6.0 (unreleased)
2+
==================
3+
4+
New Features
5+
------------
6+
7+
sbpy.names
8+
^^^^^^^^^^
9+
- Added functionality to `sbpy.Names.from_packed()` and
10+
`sbpy.Names.to_packed()` to handle new extended provisional designations
11+
to be implemented by the MPC in anticipation of higher asteroid discovery
12+
rates in the LSST survey era [#406]
13+
14+
115
0.5.0 (2024-08-28)
216
==================
317

docs/sbpy/data/names.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,13 @@ numbers and unpacked ones:
112112

113113
>>> Names.from_packed('J95A01A')
114114
'1995 AA1'
115+
>>> Names.from_packed('_RD0aEM')
116+
'2027 DZ6190'
115117
>>> Names.from_packed('G3693')
116118
163693
117119
>>> Names.to_packed('1995 AA1')
118120
'J95A01A'
121+
>>> Names.to_packed('2027 DZ6190')
122+
'_RD0aEM'
119123
>>> Names.to_packed('163693')
120124
'G3693'

sbpy/data/names.py

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"""
1212

1313
import re
14+
import math
1415
from ..exceptions import SbpyException
1516

1617
__all__ = ['Names', 'TargetNameParseError', 'natural_sort_key']
@@ -68,6 +69,9 @@ class Names():
6869
pkd = ('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
6970
'abcdefghijklmnopqrstuvwxyz')
7071

72+
# packed numbers translation string with no I
73+
pkd_noI = 'ABCDEFGHJKLMNOPQRSTUVWXYZ'
74+
7175
@staticmethod
7276
def to_packed(s):
7377
"""Convert designation or number to packed identifier.
@@ -94,14 +98,22 @@ def to_packed(s):
9498
s = int(s)
9599
if s < 100000:
96100
return '{:05d}'.format(s)
97-
elif s > 619999:
98-
raise TargetNameParseError(
99-
'{} cannot be turned into a packed number'.format(s)
100-
)
101-
else:
101+
elif (s > 99999 and s < 620000):
102102
mod = (s % 10000)
103103
return '{}{:04d}'.format(
104104
Names.pkd[int((s - mod) / 10000)], mod)
105+
elif (s > 619999 and s < 15396336):
106+
s = s - 620000
107+
d = ['0', '0', '0', '0']
108+
for idx in reversed(range(0, 4)):
109+
d[idx] = Names.pkd[math.floor(s % 62)]
110+
s //= 62
111+
return ('~'+''.join(d))
112+
else:
113+
raise TargetNameParseError(
114+
'{} cannot be turned into a packed number'.format(s)
115+
)
116+
105117
elif s.endswith('P-L'):
106118
return 'PLS{}'.format(s[:4])
107119
elif s[-3:] in ['T-1', 'T-2', 'T-3']:
@@ -123,42 +135,61 @@ def to_packed(s):
123135
frag = '0'
124136
num = s[6:]
125137

126-
if num == '':
127-
num = '00'
128-
elif len(num) == 1:
129-
num = '0' + num
130-
elif len(num) > 2:
131-
try:
138+
try:
139+
if num == '':
140+
num = '00'
141+
elif len(num) == 1:
142+
num = '0' + num
143+
elif len(num) > 2:
132144
num = Names.pkd[int(num[:-1])]+num[-1]
133-
except (IndexError, ValueError):
134-
raise TargetNameParseError(
135-
('{} cannot be turned into a '
136-
'packed designation').format(s))
137-
return '{}{}{}{}{}'.format(
138-
Names.pkd[int(float(s[:2]))],
139-
s[2:4],
140-
s[5],
141-
num,
142-
frag.lower()
143-
)
145+
return '{}{}{}{}{}'.format(
146+
Names.pkd[int(float(s[:2]))],
147+
s[2:4],
148+
s[5],
149+
num,
150+
frag.lower()
151+
)
152+
except (IndexError, ValueError):
153+
raise TargetNameParseError(
154+
('{} cannot be turned into a '
155+
'packed designation').format(s))
144156
else:
145-
yr = s.strip()[:4]
146-
yr = Names.pkd[int(yr[:2])] + yr[2:]
147-
let = s.strip()[4:7].strip()
148-
num = s.strip()[7:].strip()
149-
if num == '':
150-
num = '00'
151-
elif len(num) == 1:
152-
num = '0' + num
153-
elif len(num) > 2:
154-
try:
155-
num = Names.pkd[int(num[:-1])]+num[-1]
156-
except (IndexError, ValueError):
157-
raise TargetNameParseError(
158-
('{} cannot be turned into a '
159-
'packed designation').format(s))
160-
return (yr + let[0] + num + let[1])
161-
157+
try:
158+
yr = s.strip()[:4]
159+
yr = Names.pkd[int(yr[:2])] + yr[2:]
160+
let = s.strip()[4:7].strip()
161+
num = s.strip()[7:].strip()
162+
163+
if num == '':
164+
return (yr + let[0] + '00' + let[1])
165+
elif len(num) == 1:
166+
return (yr + let[0] + '0' + num + let[1])
167+
elif len(num) > 1:
168+
obj_num = int(num)*25 + Names.pkd_noI.find(let[1]) + 1
169+
# use original packed desigs for first 15500 objs per month
170+
if obj_num < 15501:
171+
num = Names.pkd[int(num[:-1])]+num[-1]
172+
return (yr + let[0] + num + let[1])
173+
# use extended packed desigs for >15500 objs per month
174+
elif obj_num < 14791837:
175+
obj_num = obj_num - 15501
176+
year = Names.pkd[int(yr[1:])]
177+
month = let[0]
178+
d = ['0', '0', '0', '0']
179+
for idx in reversed(range(0, 4)):
180+
d[idx] = Names.pkd[math.floor(obj_num % 62)]
181+
obj_num //= 62
182+
return ('_'+Names.pkd[int(yr[1:])]+let[0]+''.join(d))
183+
# if more than maximum of 14,791,836 objects per half-month
184+
# accommodated by the extended provisional designation scheme
185+
else:
186+
raise TargetNameParseError(
187+
('{} cannot be turned into a '
188+
'packed number or designation').format(s))
189+
except (IndexError, ValueError):
190+
raise TargetNameParseError(
191+
('{} cannot be turned into a '
192+
'packed number or designation').format(s))
162193
else:
163194
raise TargetNameParseError(
164195
('{} cannot be turned into a '
@@ -189,6 +220,17 @@ def from_packed(p):
189220
return int(p)
190221
elif p[0].isalpha() and p[1:].isdigit():
191222
return int(str(Names.pkd.find(p[0])) + p[1:])
223+
elif p[0] == '~' and p[1:].isalnum():
224+
if len(p) == 5:
225+
obj_num = 620000 + Names.pkd.find(p[1])*(62**3) \
226+
+ Names.pkd.find(p[2])*(62**2) \
227+
+ Names.pkd.find(p[3])*(62) \
228+
+ Names.pkd.find(p[4])
229+
return int(obj_num)
230+
else:
231+
raise TargetNameParseError(
232+
('{} cannot be turned into an '
233+
'unpacked designation').format(p))
192234

193235
# old designation style, e.g.: 1989AB
194236
if (len(p.strip()) < 7 and p[:4].isdigit() and p[4:6].isalpha()):
@@ -216,6 +258,27 @@ def from_packed(p):
216258
(str(Names.pkd.find(p[4])) + p[5]).lstrip('0'),
217259
'-{}'.format(p[6].upper()) if p[6].islower() else ''
218260
)
261+
# MPC extended packed provisional designation
262+
elif p[0] == '_':
263+
if (
264+
(p[1].isalpha() and p[1].isupper())
265+
and re.search("[A-H,J-Y]", p[2])
266+
and p[3:].isalnum()
267+
and len(p) == 7
268+
):
269+
obj_num = 15501 + Names.pkd.find(p[3])*(62**3) \
270+
+ Names.pkd.find(p[4])*(62**2) \
271+
+ Names.pkd.find(p[5])*(62) \
272+
+ Names.pkd.find(p[6])
273+
return '20{} {}{}{}'.format(
274+
str(Names.pkd.find(p[1])),
275+
p[2],
276+
Names.pkd_noI[((obj_num-1) % 25)],
277+
math.floor((obj_num-1)/25))
278+
else:
279+
raise TargetNameParseError(
280+
('{} cannot be turned into an '
281+
'unpacked designation').format(p))
219282
else:
220283
# nothing to do
221284
return p

sbpy/data/tests/test_names.py

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ def test_from_packed():
102102
103103
Test values from https://www.minorplanetcenter.net/iau/info/PackedDes.html
104104
105+
Test values for extended permanent designations from
106+
https://www.minorplanetcenter.net/iau/info/PackedDes.html
107+
108+
Test values for extended provisional designations from
109+
https://minorplanetcenter.net/mpcops/documentation/provisional-designation-definition/
110+
105111
"""
106112

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

138+
# extended permanent designations
139+
assert Names.from_packed('~0000') == 620000
140+
assert Names.from_packed('~000z') == 620061
141+
assert Names.from_packed('~AZaz') == 3140113
142+
assert Names.from_packed('~zzzz') == 15396335
143+
144+
# extended provisional designations
145+
assert Names.from_packed('_QC0000') == '2026 CA620'
146+
assert Names.from_packed('_QC0aEM') == '2026 CZ6190'
147+
assert Names.from_packed('_QCzzzz') == '2026 CL591673'
148+
assert Names.from_packed('_PD0000') == '2025 DA620'
149+
assert Names.from_packed('_QD000N') == '2026 DY620'
150+
assert Names.from_packed('_RD0aEM') == '2027 DZ6190'
151+
assert Names.from_packed('_SEZZZZ') == '2028 EA339749'
152+
assert Names.from_packed('_TFzzzz') == '2029 FL591673'
153+
132154
# a few other tests
133155
assert Names.from_packed('50000') == 50000
134156
assert Names.from_packed('A0345') == 100345
@@ -142,6 +164,12 @@ def test_to_packed():
142164
143165
Test values from https://www.minorplanetcenter.net/iau/info/PackedDes.html
144166
167+
Test values for extended permanent designations from
168+
https://www.minorplanetcenter.net/iau/info/PackedDes.html
169+
170+
Test values for extended provisional designations from
171+
https://minorplanetcenter.net/mpcops/documentation/provisional-designation-definition/
172+
145173
"""
146174

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

200+
# extended permanent designations
201+
assert Names.to_packed('620000') == '~0000'
202+
assert Names.to_packed('620061') == '~000z'
203+
assert Names.to_packed('3140113') == '~AZaz'
204+
assert Names.to_packed('15396335') == '~zzzz'
205+
206+
# extended provisional designations
207+
assert Names.to_packed('2026 CA620') == '_QC0000'
208+
assert Names.to_packed('2026 CZ6190') == '_QC0aEM'
209+
assert Names.to_packed('2026 CL591673') == '_QCzzzz'
210+
assert Names.to_packed('2025 DA620') == '_PD0000'
211+
assert Names.to_packed('2026 DY620') == '_QD000N'
212+
assert Names.to_packed('2027 DZ6190') == '_RD0aEM'
213+
assert Names.to_packed('2028 EA339749') == '_SEZZZZ'
214+
assert Names.to_packed('2029 FL591673') == '_TFzzzz'
215+
172216
# a few other tests
173217
assert Names.to_packed('50000') == '50000'
174218
assert Names.to_packed('100345') == 'A0345'
@@ -223,11 +267,40 @@ def test_parse_asteroid():
223267

224268

225269
def test_break_packed():
226-
with pytest.raises(TargetNameParseError):
227-
Names.to_packed('620000')
228-
229270
with pytest.raises(TargetNameParseError):
230271
Names.to_packed('2015 this will not work')
231272

232273
with pytest.raises(TargetNameParseError):
233274
Names.to_packed('thiswillnotwork')
275+
276+
277+
def test_raises_error():
278+
with pytest.raises(TargetNameParseError):
279+
Names.to_packed('2011 AA123456789')
280+
281+
with pytest.raises(TargetNameParseError):
282+
Names.to_packed('2011 A123456789')
283+
284+
with pytest.raises(TargetNameParseError):
285+
Names.to_packed('2026 CL591674')
286+
287+
with pytest.raises(TargetNameParseError):
288+
Names.to_packed('1989 A')
289+
290+
with pytest.raises(TargetNameParseError):
291+
Names.to_packed('15396336')
292+
293+
with pytest.raises(TargetNameParseError):
294+
Names.from_packed('~555555')
295+
296+
with pytest.raises(TargetNameParseError):
297+
Names.from_packed('_QCzzzz0')
298+
299+
with pytest.raises(TargetNameParseError):
300+
Names.from_packed('_Qczzzz')
301+
302+
with pytest.raises(TargetNameParseError):
303+
Names.from_packed('_QCzz_z')
304+
305+
with pytest.raises(TargetNameParseError):
306+
Names.from_packed('_qCzzzz')

0 commit comments

Comments
 (0)