MPC Check Near-Duplicates (CND) API¶
This tutorial provides information on how to use the Minor Planet Center's Check Near-Duplicates API.¶
The Minor Planet Center's Check Near-Duplicates (CND) service identifies published observations in the MPC database that can be considered near-duplicates of the observations you provide. This helps detect potential duplicate submissions before they enter the database.
This is useful when you want to:
- Check if observations have already been submitted and published by the MPC
- Verify that your observations are unique before submission
The CND API is a REST endpoint. You can send GET requests to:
https://data.minorplanetcenter.net/api/cnd
In the examples below we use Python code to query the API.
Further information and documentation can be found at:
Import Packages¶
Here we import the standard Python packages needed to call the API and interpret the returned data.
import requests
import json
API Parameters¶
The CND API accepts the following parameters:
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
obs |
List of strings | Yes | 80- or 160-character observation records in MPC format | None |
time_separation_s |
Float | No | Temporal threshold in seconds (0-60) | 60 |
angle_separation_arcsec |
Float | No | Spatial threshold in arcseconds (0-10) | 5 |
omit_separation |
Boolean | No | Exclude separation values from results | false |
What Counts as a Near-Duplicate?¶
An observation is considered a near-duplicate if:
- It is within
time_separation_sseconds of your observation time - It is within
angle_separation_arcsecarcseconds of your observation position - Both conditions must be met
Observation Format¶
Observations must be provided in the MPC 80-column format. Here's an example:
' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX F51'
The format includes:
- Columns 1-12: Packed designation (in this example the firrt 5 columns are blank)
- Column 13: Discovery asterisk (if applicable)
- Column 14: Note 1
- Column 15: Note 2 (C = CCD)
- Columns 16-32: Date and time of observation
- Columns 33-44: Right Ascension
- Columns 45-56: Declination
- Columns 57-65: Blank
- Columns 66-70: Magnitude
- Column 71: Band
- Columns 72-77: Blank
- Columns 78-80: Observatory code
NB: The above column enumeration assumes starting from 1, not 0.
Basic Query: Single Observation:¶
Exact Match to Database Observation¶
Here we check if a single observation has a near-duplicate in the MPC database.
In this example there is an exact database match.
N.B. 'exact' matches may show non-zero angular separation values. This is due to the numerical difference between the RA/dec values stored in the database and those given in the obs80 string.
# Example observation in MPC 80-column format
observation = "f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08"
response = requests.get(
"https://data.minorplanetcenter.net/api/cnd",
json={"obs": [observation]}
)
if response.ok:
print(json.dumps(response.json(), indent=4))
else:
print(f"Error: {response.status_code}")
print(response.content)
{
"request": {
"angle_separation_arcsec": 5,
"obs": [
"f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08"
],
"omit_separation": false,
"time_separation_s": 60
},
"results": {
"f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08": [
{
"angle_separation_arcsec": 0.021,
"obs80": "f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08",
"time_separation_s": 0.0
}
]
}
}
Near-Match to Database Observation¶
Here we check if a single observation has a near-duplicate in the MPC database.
In this example there is a near-match to the database (note that the final digit of the declination differs: 25.9 versus 25.7).
# Example observation in MPC 80-column format
observation = "f9671 C2020 02 21.46921410 05 10.27 +04 52 25.9 19.37oV T08"
response = requests.get(
"https://data.minorplanetcenter.net/api/cnd",
json={"obs": [observation]}
)
if response.ok:
print(json.dumps(response.json(), indent=4))
else:
print(f"Error: {response.status_code}")
print(response.content)
{
"request": {
"angle_separation_arcsec": 5,
"obs": [
"f9671 C2020 02 21.46921410 05 10.27 +04 52 25.9 19.37oV T08"
],
"omit_separation": false,
"time_separation_s": 60
},
"results": {
"f9671 C2020 02 21.46921410 05 10.27 +04 52 25.9 19.37oV T08": [
{
"angle_separation_arcsec": 0.22,
"obs80": "f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08",
"time_separation_s": 0.0
}
]
}
}
No Match to Database¶
Here we check if a single observation has a near-duplicate in the MPC database.
In this example there is a no match to the database.
# Example observation in MPC 80-column format
observation = " abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08"
response = requests.get(
"https://data.minorplanetcenter.net/api/cnd",
json={"obs": [observation]}
)
if response.ok:
print(json.dumps(response.json(), indent=4))
else:
print(f"Error: {response.status_code}")
print(response.content)
{
"request": {
"angle_separation_arcsec": 5,
"obs": [
" abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08"
],
"omit_separation": false,
"time_separation_s": 60
},
"results": {
" abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08": "No results returned which is strange; the search term should be in the database."
}
}
Helper Function¶
Here's a convenient helper function for checking near-duplicates.
def check_near_duplicates(observations, time_separation_s:float=60, angle_separation_arcsec:float=5, omit_separation:bool=False):
"""
Check if observations have near-duplicates in the MPC database.
Parameters
----------
observations : str or list
Single observation or list of observations in MPC 80-column format
time_separation_s : float
Time threshold in seconds (0-60)
angle_separation_arcsec : float
Angle threshold in arcseconds (0-10)
omit_separation : bool
Whether to omit the separation values
Returns
-------
dict
Dictionary mapping each observation to its near-duplicates
"""
if isinstance(observations, str):
observations = [observations]
response = requests.get(
"https://data.minorplanetcenter.net/api/cnd",
json={
"obs": observations,
"time_separation_s": time_separation_s,
"angle_separation_arcsec": angle_separation_arcsec,
"omit_separation":omit_separation
}
)
return response.json().get('results', {})
def count_duplicates(observations, **kwargs):
"""
Simple count of near-duplicates for each input observation
Returns
-------
dict
key:input observation
value: count of matches
"""
results = check_near_duplicates(observations, **kwargs)
return {k:len(v) if isinstance(v,list) else 0 for k,v in results.items() }
Demo helper function: check_near_duplicates¶
check_near_duplicates(" abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08")
{' abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08': 'No results returned which is strange; the search term should be in the database.'}
check_near_duplicates(" K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51")
{' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51': [{'angle_separation_arcsec': 2.404,
'obs80': 'h3461 C2023 05 16.43686615 56 36.976-23 12 43.05 21.50wX~6pX9F51',
'time_separation_s': 0.0},
{'angle_separation_arcsec': 0.005,
'obs80': ' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51',
'time_separation_s': 0.0}]}
Demo helper function: count_duplicates¶
N.B.(1): You can search multiple observations at once.
N.B.(2): Here the input obs80 data contains a two-line observation. As such, the returned "key" is 160 characters long for that observation.
observation = [
' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51',
' K10HB1E S2010 04 24.76254008 07 33.34 -16 07 52.4 W C51',
' K10HB1E s2010 04 24.7625401 - 3527.1820 + 5686.2015 - 1729.5218 C51'
]
count_duplicates(observation)
{' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51': 2,
' K10HB1E S2010 04 24.76254008 07 33.34 -16 07 52.4 W C51 K10HB1E s2010 04 24.7625401 - 3527.1820 + 5686.2015 - 1729.5218 C51': 2}
count_duplicates(" abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08")
{' abc123 C2020 03 23.46921410 05 13.27 +04 52 25.9 19.37oV T08': 0}
Interpreting Results¶
The response contains:
request: Echo of your query parametersresults: Dictionary where each key is one of your observations, mapped to a list of matching near-duplicates
Each match includes:
obs80: The near-duplicate observation record from the MPC databasetime_separation_s: Time difference in secondsangle_separation_arcsec: Angular separation in arcseconds
# Let's examine the results more carefully
# Check results for the observation we queried
for input_obs, res_list in check_near_duplicates("f9671 C2020 02 21.46921410 05 10.27 +04 52 25.9 19.37oV T08").items():
print(f"Input Obs: {input_obs}")
for item in res_list:
print(f" - Match: {item.get('obs80', 'N/A')}")
print(f" - Time separation: {item.get('time_separation_s', 'N/A')} seconds")
print(f" - Angle separation: {item.get('angle_separation_arcsec', 'N/A')} arcsec")
Input Obs: f9671 C2020 02 21.46921410 05 10.27 +04 52 25.9 19.37oV T08
- Match: f9671 C2020 02 21.46921410 05 10.27 +04 52 25.7 19.37oV~3n2UT08
- Time separation: 0.0 seconds
- Angle separation: 0.22 arcsec
Custom Thresholds¶
You can adjust the time and angle thresholds to make the matching stricter or more lenient.
# Using strict matching thresholds we find no matches ...
count_duplicates("f9671 C2020 02 21.46941410 05 10.37 +04 52 29.9 19.37oV T08",
**{'time_separation_s' : 10, 'angle_separation_arcsec': 1})
check_near_duplicates:time_separation_s=10,angle_separation_arcsec=1,response.json().get('results', {})={'f9671 C2020 02 21.46941410 05 10.37 +04 52 29.9 19.37oV T08': 'No results returned which is strange; the search term should be in the database.'}
{'f9671 C2020 02 21.46941410 05 10.37 +04 52 29.9 19.37oV T08': 0}
# Using more tolerant thresholds, for the same input observation we find a match
count_duplicates("f9671 C2020 02 21.46941410 05 10.37 +04 52 29.9 19.37oV T08",
**{'time_separation_s' : 30, 'angle_separation_arcsec': 5})
{'f9671 C2020 02 21.46941410 05 10.37 +04 52 29.9 19.37oV T08': 1}
Omitting Separation Values¶
If you only need to know whether duplicates exist (not the separation values), use omit_separation=true.
print("With separation values:")
print(check_near_duplicates(" K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51", **{"omit_separation": False}))
print("\nWithout separation values:")
print(check_near_duplicates(" K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51", **{"omit_separation": True}))
With separation values:
{' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51': [{'angle_separation_arcsec': 2.404, 'obs80': 'h3461 C2023 05 16.43686615 56 36.976-23 12 43.05 21.50wX~6pX9F51', 'time_separation_s': 0.0}, {'angle_separation_arcsec': 0.005, 'obs80': ' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51', 'time_separation_s': 0.0}]}
Without separation values:
{' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51': [{'obs80': 'h3461 C2023 05 16.43686615 56 36.976-23 12 43.05 21.50wX~6pX9F51'}, {'obs80': ' K10CM6D C2023 05 16.43686615 56 36.807-23 12 43.67 21.55wX~6o8oF51'}]}
Summary¶
The MPC Check Near-Duplicates API helps identify potential duplicate observations before submission.
Key points:
- Endpoint:
https://data.minorplanetcenter.net/api/cnd - Required parameter:
obs- list of 80-column observation records - Optional thresholds:
time_separation_s(0-60s, default 60) andangle_separation_arcsec(0-10", default 5) - Use case: Check observations before submitting to avoid duplicates
For questions or feedback, contact the MPC via the Jira Helpdesk.