use std::collections::HashMap;
use std::path::Path;
use crate::geom::sip::SipWcs;
use super::sidecar::{SidecarReader, SidecarRecord};
pub struct ObservationContext {
pub time: starfield::time::Time,
pub observer: ObserverState,
}
#[derive(Debug, Clone, Copy)]
pub enum ObserverState {
Barycentric {
position_au: [f64; 3],
velocity_au_per_day: [f64; 3],
},
}
#[derive(Debug, Clone, Copy)]
pub struct GaiaAstrometry {
pub ra_deg: f64,
pub dec_deg: f64,
pub pmra_mas_per_year: f64,
pub pmdec_mas_per_year: f64,
pub parallax_mas: f64,
pub radial_km_per_s: f64,
pub ref_epoch_jyear: f64,
pub sigma_ra_mas: f64,
pub sigma_dec_mas: f64,
pub sigma_pmra_mas_per_year: f64,
pub sigma_pmdec_mas_per_year: f64,
pub sigma_parallax_mas: f64,
}
#[derive(Debug, Default, Clone)]
pub struct RefinementCatalog {
pub sources: HashMap<u64, GaiaAstrometry>,
}
impl RefinementCatalog {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, catalog_id: u64, astrometry: GaiaAstrometry) {
self.sources.insert(catalog_id, astrometry);
}
pub fn get(&self, catalog_id: u64) -> Option<&GaiaAstrometry> {
self.sources.get(&catalog_id)
}
pub fn len(&self) -> usize {
self.sources.len()
}
pub fn is_empty(&self) -> bool {
self.sources.is_empty()
}
pub fn load_sidecar_filtered(path: &Path, source_ids: &[u64]) -> Result<Self, RefinementError> {
let reader = SidecarReader::open(path)
.map_err(|e| RefinementError::Starfield(format!("sidecar open failed: {e}")))?;
let results = reader.get_many(source_ids);
let mut sources = HashMap::with_capacity(results.len());
for record in results.into_iter().flatten() {
sources.insert(record.source_id, sidecar_record_to_astrometry(&record));
}
Ok(Self { sources })
}
}
fn sidecar_record_to_astrometry(r: &SidecarRecord) -> GaiaAstrometry {
fn nan_to_zero(v: f64) -> f64 {
if v.is_nan() { 0.0 } else { v }
}
GaiaAstrometry {
ra_deg: r.ra,
dec_deg: r.dec,
pmra_mas_per_year: nan_to_zero(r.pmra),
pmdec_mas_per_year: nan_to_zero(r.pmdec),
parallax_mas: nan_to_zero(r.parallax),
radial_km_per_s: nan_to_zero(r.radial_velocity),
ref_epoch_jyear: r.ref_epoch,
sigma_ra_mas: r.sigma_ra as f64,
sigma_dec_mas: r.sigma_dec as f64,
sigma_pmra_mas_per_year: r.sigma_pmra as f64,
sigma_pmdec_mas_per_year: r.sigma_pmdec as f64,
sigma_parallax_mas: r.sigma_parallax as f64,
}
}
#[derive(Debug, Clone)]
pub struct RefinementConfig {
pub match_radius_pix: f64,
pub max_iterations: usize,
pub convergence_pix: f64,
pub min_matches: usize,
}
impl Default for RefinementConfig {
fn default() -> Self {
Self {
match_radius_pix: 3.0,
max_iterations: 5,
convergence_pix: 0.05,
min_matches: 10,
}
}
}
#[derive(Debug, Clone)]
pub struct RefinedSolution {
pub wcs: SipWcs,
pub n_iterations: usize,
pub residual_rms_mas: f64,
pub residual_rms_pix: f64,
pub matched: Vec<RefinedMatch>,
}
#[derive(Debug, Clone, Copy)]
pub struct RefinedMatch {
pub catalog_id: u64,
pub field_source_idx: usize,
pub apparent_ra_deg: f64,
pub apparent_dec_deg: f64,
pub residual_mas: f64,
pub weight: f64,
}
#[derive(Debug)]
pub enum RefinementError {
InsufficientMatches { required: usize, actual: usize },
DidNotConverge(usize),
TerrestrialNotSupported,
Starfield(String),
}
impl std::fmt::Display for RefinementError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InsufficientMatches { required, actual } => {
write!(
f,
"fewer than {required} field↔catalog matches found ({actual})"
)
}
Self::DidNotConverge(n) => write!(f, "refinement did not converge in {n} iterations"),
Self::TerrestrialNotSupported => {
write!(
f,
"terrestrial observer not yet supported; provide Barycentric state"
)
}
Self::Starfield(msg) => write!(f, "starfield error: {msg}"),
}
}
}
impl std::error::Error for RefinementError {}