#!/usr/bin/env python3 """ mo_mpc.py -- "was there a known minor planet in the frame?" Purpose ------- The NGC 5128 (Centaurus A) LRGB session of 2026-07-21 was searched for moving objects (see _mo_dets.npz / _mo_tracklets.npy). Before any detection can be called a *new* object, we must rule out that it is simply a catalogued minor planet that happened to drift through the field. This script asks the authoritative services what known small bodies were inside the field of view at the epoch of the exposures. Field / epoch ------------- Field centre : RA 13 25 27.37, Dec -43 01 10.9 (J2000) = 201.36404 deg, -43.01969 deg Field size : 42.9' x 28.6' (half-diagonal ~25.8') Search radius: 30 arcmin (0.5 deg) -- comfortably encloses the whole frame Epoch : 2026-07-21 09:22 UTC (= 2026 07 21.39), mid-luminance-sequence (luminance ran 08:56-10:00 UTC, session ended ~11:16 UTC) Observatory : Q62, iTelescope / Siding Spring Observatory Services queried ---------------- 1. JPL Small-Body Identification API https://ssd-api.jpl.nasa.gov/sb_ident.api Primary source. Well documented, accepts arbitrary (including future) epochs because it propagates orbits rather than serving a precomputed ephemeris. IMPORTANT UNITS GOTCHA: despite some documentation wording, fov-ra-hwidth and fov-dec-hwidth are interpreted in DEGREES, not arcminutes. Passing "30" yields a 30-degree half-width box (the API echoes the box it used in ["observer"]["fov_offset"], which is worth checking every run). Queried twice: sb-kind=a (asteroids) and sb-kind=c (comets). 2. MPC "MPChecker" / minor planet checker https://minorplanetcenter.net/cgi-bin/mpcheck.cgi (classic HTTP POST form) https://www.minorplanetcenter.net/cgi-bin/checkmp.cgi Cross-check. Kept in the script even if it fails, so the log records whether the MPC endpoint was reachable and what it said. Output ------ Raw responses (JSON + HTML) and a human-readable summary are appended to intermediates/_mpc_query.txt Nothing is fabricated: if a service errors or refuses the epoch, the error text itself is written to the log. """ import json import math import sys import datetime import requests import layout # ---------------------------------------------------------------- field/epoch RA_SEX = "13-25-27.37" DEC_SEX = "M43-01-10.9" # JPL uses a leading "M" for a negative Dec RA_DEG = 201.36404 DEC_DEG = -43.01969 OBS_TIME = "2026-07-21_09:22:00" MPC_CODE = "Q62" # iTelescope, Siding Spring RADIUS_ARCMIN = 30.0 HWIDTH_DEG = RADIUS_ARCMIN / 60.0 # = 0.5 deg; API wants DEGREES here VMAG_LIM = 22.0 OUT = layout.path("_mpc_query.txt") log_lines = [] def log(s=""): print(s) log_lines.append(str(s)) # ------------------------------------------------------------------ JPL query def jpl(sb_kind): """Query ssd-api.jpl.nasa.gov/sb_ident.api for one small-body class.""" params = { "sb-kind": sb_kind, # 'a' = asteroid, 'c' = comet "mpc-code": MPC_CODE, "obs-time": OBS_TIME, "fov-ra-center": RA_SEX, "fov-dec-center": DEC_SEX, "fov-ra-hwidth": f"{HWIDTH_DEG}", "fov-dec-hwidth": f"{HWIDTH_DEG}", "mag-required": "true", "two-pass": "true", "vmag-lim": f"{VMAG_LIM}", } r = requests.get("https://ssd-api.jpl.nasa.gov/sb_ident.api", params=params, timeout=180) log(f"--- JPL sb_ident sb-kind={sb_kind} -> HTTP {r.status_code}") log(f" URL: {r.url}") if r.status_code != 200: log(" RAW: " + r.text[:2000]) return None d = r.json() log(" observer : " + json.dumps(d.get("observer", {}))) log(" n_first_pass : " + str(d.get("n_first_pass"))) log(" n_second_pass: " + str(d.get("n_second_pass"))) return d def show(d, label): """Print the refined (second-pass) hits, falling back to first pass.""" if d is None: return rows = d.get("data_second_pass") or [] fields = d.get("fields_second") or [] if not rows: rows = d.get("data_first_pass") or [] fields = d.get("fields_first") or [] log(f" [{label}] no second-pass data; showing first pass") log(f" fields: {fields}") if not rows: log(f" [{label}] NO OBJECTS in field.") return for row in rows: log(" " + " | ".join(str(x) for x in row)) # ------------------------------------------------------------------ MPC query def mpchecker(): """Classic MPChecker POST form. Recorded even if it fails.""" data = { "year": "2026", "month": "07", "day": "21.39", "which": "pos", "ra": "13 25 27.37", "decl": "-43 01 10.9", "TextArea": "", "radius": str(int(RADIUS_ARCMIN)), "limit": f"{VMAG_LIM}", "oc": MPC_CODE, "sort": "d", "mot": "h", "tmot": "s", "pdes": "u", "needed": "f", "ps": "n", "type": "p", } for url in ("https://minorplanetcenter.net/cgi-bin/mpcheck.cgi", "https://www.minorplanetcenter.net/cgi-bin/checkmp.cgi"): try: r = requests.post(url, data=data, timeout=120, headers={"User-Agent": "Mozilla/5.0 (mo_mpc.py)"}) log(f"--- MPC POST {url} -> HTTP {r.status_code}, {len(r.text)} bytes") log(r.text[:8000]) except Exception as e: # noqa: BLE001 log(f"--- MPC POST {url} -> EXCEPTION {type(e).__name__}: {e}") # ----------------------------------------------------------------------- main if __name__ == "__main__": log("MPC / minor-planet field check") log("run at " + datetime.datetime.utcnow().isoformat() + "Z") log(f"field centre {RA_SEX} {DEC_SEX} (J2000) = {RA_DEG}, {DEC_DEG}") log(f"epoch {OBS_TIME} UTC, obs code {MPC_CODE}, " f"radius {RADIUS_ARCMIN}' , V<{VMAG_LIM}") log() for kind, label in (("a", "asteroids"), ("c", "comets")): try: show(jpl(kind), label) except Exception as e: # noqa: BLE001 log(f" JPL {label} EXCEPTION {type(e).__name__}: {e}") log() mpchecker() # --- derived summary: total sky motion and position angle for JPL hits --- log() log("=== derived summary (from JPL second pass, sb-kind=a) ===") try: d = jpl("a") for row in (d.get("data_second_pass") or []): name, ra, dec = row[0], row[1], row[2] norm = float(row[5]) vmag = row[6] dra, ddec = float(row[7]), float(row[8]) # "/h, RA rate has cos(dec) tot = math.hypot(dra, ddec) pa = math.degrees(math.atan2(dra, ddec)) % 360.0 # N through E log(f"{name}: RA {ra} Dec {dec} V={vmag} " f"{norm:.0f}\" ({norm/60:.1f}') from centre " f"motion {tot:.1f}\"/hr at PA {pa:.1f} deg " f"(dRA*cos(dec)={dra:.1f}, dDec={ddec:.1f} \"/hr)") except Exception as e: # noqa: BLE001 log(f"summary EXCEPTION {type(e).__name__}: {e}") with open(OUT, "w", encoding="utf-8") as fh: fh.write("\n".join(log_lines) + "\n") print("\nwrote " + OUT)