Skip to main content

target_match/
lib.rs

1//! `target-match` — identify which catalogued sky object a telescope frame captured.
2//!
3//! Given a **pointing** (right ascension / declination) and a **field of view**
4//! (derived from optics, or supplied directly), `target-match` ranks a
5//! caller-supplied set of catalogue objects by angular separation and returns
6//! those that fall on the frame. Matching is done **by sky position only, never
7//! by name** — a designation may ride along on a result for display, but it
8//! never influences the match (capture software writes object names
9//! inconsistently; coordinates are authoritative).
10//!
11//! The crate is **catalog-agnostic**: it owns no catalogue data and performs no
12//! I/O. A consumer brings its own objects — from a database, a file, a SIMBAD
13//! resolver, or a hand-built list — by implementing the [`matcher`] crate's
14//! `SkyObject` trait, and `target-match` does the geometry.
15//!
16//! # Modules
17//!
18//! - [`angle`] — angle and equatorial-coordinate primitives: decimal ⇄
19//!   sexagesimal (`HH:MM:SS` / `±DD:MM:SS`) parsing and formatting, and
20//!   great-circle (haversine) angular separation.
21//! - [`optics`] — plate scale and field-of-view geometry from focal length,
22//!   pixel size (x/y), binning (x/y), and sensor dimensions — or a directly
23//!   supplied pixel scale / field of view — plus the search-radius policies.
24//! - [`matcher`] — the `SkyObject` input trait, match constraints (radius,
25//!   rectangular field of view, nearest-N), and deterministic ranking.
26//!
27//! # Example
28//!
29//! ```
30//! use target_match::{Angle, Constraint, Equatorial, Field, Optics, RadiusPolicy, SkyObject, rank};
31//!
32//! struct Target { name: &'static str, ra: f64, dec: f64 }
33//! impl SkyObject for Target {
34//!     fn position(&self) -> Equatorial {
35//!         Equatorial::j2000(Angle::from_degrees(self.ra), Angle::from_degrees(self.dec)).unwrap()
36//!     }
37//! }
38//!
39//! let catalog = [
40//!     Target { name: "M 31",  ra: 10.6847, dec: 41.2688 },
41//!     Target { name: "M 33",  ra: 23.4621, dec: 30.6599 },
42//! ];
43//! let pointing = Equatorial::parse_j2000("00:42:44.3", "+41:16:09").unwrap();
44//! let field = Field::from_optics(Optics {
45//!     focal_mm: 800.0, pixel_um: (3.76, 3.76), binning: (1, 1), pixels: (6248, 4176),
46//! }).unwrap();
47//!
48//! let hits = rank(pointing, &catalog, Constraint::within(&field, RadiusPolicy::Circumscribed).nearest_one());
49//! assert_eq!(hits[0].object.name, "M 31");
50//! ```
51//!
52//! # Status
53//!
54//! Extracted from the `nightwatch-astro/alm` targeting pipeline; specified and
55//! implemented under `specs/001-target-match-core/` (SpecKit).
56
57pub mod angle;
58pub mod error;
59pub mod matcher;
60pub mod optics;
61
62pub use angle::{precess, separation, Angle, Epoch, Equatorial};
63pub use error::{Error, Result};
64pub use matcher::{
65    is_framed, rank, Constraint, Match, Matcher, Membership, Offset, Query, SkyObject,
66};
67pub use optics::{
68    Field, Optics, RadiusPolicy, ARCSEC_PER_DEGREE, ARCSEC_PER_RADIAN, DEFAULT_FALLBACK_RADIUS,
69};