use std::env;
use std::io::{self, Write};
use std::path::Path;
use starfield::catalogs::hipparcos::HipparcosEntry;
use starfield::catalogs::{MinimalCatalog, MinimalStar, StarCatalog};
use starfield::Loader;
fn print_progress(progress: f64, width: usize) {
let filled_width = (progress * width as f64).round() as usize;
let empty_width = width - filled_width;
print!("\r[");
for _ in 0..filled_width {
print!("#");
}
for _ in 0..empty_width {
print!(" ");
}
print!("] {:.1}%", progress * 100.0);
io::stdout().flush().unwrap();
}
fn filter_and_save<P: AsRef<Path>>(
catalog: &impl StarCatalog<Star = HipparcosEntry>,
output_path: P,
magnitude_limit: f64,
) -> Result<usize, Box<dyn std::error::Error>> {
println!(
"Filtering catalog to magnitude {} and saving to {}...",
magnitude_limit,
output_path.as_ref().display()
);
let desc = format!(
"Hipparcos filtered catalog: magnitude <= {}, created on {}",
magnitude_limit,
chrono::Local::now().format("%Y-%m-%d")
);
let mut count = 0;
let mut filtered_stars = Vec::new();
for star in catalog.stars() {
if star.mag <= magnitude_limit {
let minimal_star = MinimalStar::new(star.hip as u64, star.ra, star.dec, star.mag);
filtered_stars.push(minimal_star);
count += 1;
}
}
let catalog = MinimalCatalog::from_stars(filtered_stars, &desc);
catalog.save(output_path)?;
println!("Saved {} stars to minimal catalog", count);
Ok(count)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = env::args().collect();
let mut catalog_type = "hipparcos";
let mut operation = "stats";
let mut magnitude_limit = 10.0;
let mut output_path = None;
let mut i = 1;
while i < args.len() {
match args[i].as_str() {
"--catalog" | "-c" => {
if i + 1 < args.len() {
catalog_type = &args[i + 1];
i += 2;
} else {
return Err("Missing value for --catalog".into());
}
}
"--operation" | "-o" => {
if i + 1 < args.len() {
operation = &args[i + 1];
i += 2;
} else {
return Err("Missing value for --operation".into());
}
}
"--magnitude" | "-m" => {
if i + 1 < args.len() {
magnitude_limit = args[i + 1].parse()?;
i += 2;
} else {
return Err("Missing value for --magnitude".into());
}
}
"--output" | "-f" => {
if i + 1 < args.len() {
output_path = Some(args[i + 1].clone());
i += 2;
} else {
return Err("Missing value for --output".into());
}
}
"--help" | "-h" => {
println!("Star Catalog Tool");
println!("================");
println!("Usage: cargo run --bin catalog_stats -- [OPTIONS]");
println!();
println!("Options:");
println!(
" -c, --catalog TYPE Catalog type: hipparcos or gaia (default: hipparcos)"
);
println!(" -o, --operation OP Operation: stats or filter (default: stats)");
println!(" -m, --magnitude MAG Magnitude limit (default: 10.0)");
println!(" -f, --output PATH Output file path for filter operation");
println!(" -h, --help Show this help message");
return Ok(());
}
_ => {
eprintln!("Unknown argument: {}", args[i]);
i += 1;
}
}
}
println!("Star Catalog Tool");
println!("================");
let loader = Loader::new();
println!(
"Loading {} catalog (magnitude <= {})...",
catalog_type, magnitude_limit
);
let catalog = if catalog_type == "hipparcos" {
loader.load_hipparcos_catalog(magnitude_limit)?
} else if catalog_type == "gaia" {
return Err("Direct Gaia catalog loading not implemented in stats tool. Use gaia_filter example instead.".into());
} else {
return Err(format!("Unknown catalog type: {}", catalog_type).into());
};
println!("\nCatalog loaded successfully!");
println!("Total stars: {}", catalog.len());
match operation {
"stats" => {
println!("\nCalculating magnitude distribution...");
let mag_ranges = [
(-2.0, 0.0, "Very bright stars (mag -2 to 0)"),
(0.0, 2.0, "Bright stars (mag 0 to 2)"),
(2.0, 4.0, "Medium bright stars (mag 2 to 4)"),
(4.0, 6.0, "Naked eye visible stars (mag 4 to 6)"),
(6.0, 8.0, "Binocular visible stars (mag 6 to 8)"),
(8.0, 10.0, "Telescope visible stars (mag 8 to 10)"),
];
let mut magnitude_counts = vec![0; mag_ranges.len()];
for (i, (min, max, _)) in mag_ranges.iter().enumerate() {
print_progress(i as f64 / mag_ranges.len() as f64, 50);
let stars_in_range = catalog
.filter(|star| star.mag >= *min && star.mag < *max)
.len();
magnitude_counts[i] = stars_in_range;
}
print_progress(1.0, 50);
println!();
println!("\nMagnitude Distribution:");
for (i, (_, _, desc)) in mag_ranges.iter().enumerate() {
let count = magnitude_counts[i];
let percentage = count as f64 / catalog.len() as f64 * 100.0;
println!(" {}: {} stars ({:.1}%)", desc, count, percentage);
}
println!("\nCalculating spatial distribution by declination bands...");
let dec_bands = [
(-90.0, -60.0, "South polar region (dec -90° to -60°)"),
(-60.0, -30.0, "South temperate (dec -60° to -30°)"),
(-30.0, 0.0, "South tropical (dec -30° to 0°)"),
(0.0, 30.0, "North tropical (dec 0° to 30°)"),
(30.0, 60.0, "North temperate (dec 30° to 60°)"),
(60.0, 90.0, "North polar region (dec 60° to 90°)"),
];
let mut dec_counts = vec![0; dec_bands.len()];
for (i, (min, max, _)) in dec_bands.iter().enumerate() {
print_progress(i as f64 / dec_bands.len() as f64, 50);
let stars_in_band = catalog
.filter(|star| star.dec >= *min && star.dec < *max)
.len();
dec_counts[i] = stars_in_band;
}
print_progress(1.0, 50);
println!();
println!("\nSpatial Distribution (by declination):");
for (i, (_, _, desc)) in dec_bands.iter().enumerate() {
let count = dec_counts[i];
let percentage = count as f64 / catalog.len() as f64 * 100.0;
println!(" {}: {} stars ({:.1}%)", desc, count, percentage);
}
println!("\nNotable Stars:");
if let Some(sirius) = catalog.get_star(32349) {
println!(" Sirius (Alpha Canis Majoris):");
println!(" HIP: {}", sirius.hip);
println!(" Magnitude: {:.2}", sirius.mag);
println!(" Position: RA {:.4}°, Dec {:.4}°", sirius.ra, sirius.dec);
if let Some(parallax) = sirius.parallax {
let distance_pc = 1000.0 / parallax;
println!(
" Distance: {:.2} parsecs ({:.2} light years)",
distance_pc,
distance_pc * 3.26156
);
}
} else {
println!(" Sirius not found in catalog");
}
if let Some(betelgeuse) = catalog.get_star(27989) {
println!("\n Betelgeuse (Alpha Orionis):");
println!(" HIP: {}", betelgeuse.hip);
println!(" Magnitude: {:.2}", betelgeuse.mag);
println!(
" Position: RA {:.4}°, Dec {:.4}°",
betelgeuse.ra, betelgeuse.dec
);
if let Some(b_v) = betelgeuse.b_v {
println!(" B-V Color Index: {:.2} (reddish)", b_v);
}
} else {
println!(" Betelgeuse not found in catalog");
}
if let Some(vega) = catalog.get_star(91262) {
println!("\n Vega (Alpha Lyrae):");
println!(" HIP: {}", vega.hip);
println!(" Magnitude: {:.2}", vega.mag);
println!(" Position: RA {:.4}°, Dec {:.4}°", vega.ra, vega.dec);
if let Some(b_v) = vega.b_v {
println!(" B-V Color Index: {:.2} (blue-white)", b_v);
}
} else {
println!(" Vega not found in catalog");
}
println!("\nStars with Highest Proper Motion:");
let mut stars_vec: Vec<_> = catalog.stars().collect();
stars_vec.sort_by(|a, b| {
let a_pm = a
.pm_ra
.unwrap_or(0.0)
.abs()
.hypot(a.pm_dec.unwrap_or(0.0).abs());
let b_pm = b
.pm_ra
.unwrap_or(0.0)
.abs()
.hypot(b.pm_dec.unwrap_or(0.0).abs());
b_pm.partial_cmp(&a_pm).unwrap()
});
for (i, star) in stars_vec.iter().take(5).enumerate() {
let pm = star.pm_ra.unwrap_or(0.0).hypot(star.pm_dec.unwrap_or(0.0));
println!(
" {}. HIP {} - Proper Motion: {:.2} mas/year",
i + 1,
star.hip,
pm
);
}
println!("\nSimple Sky Map (RA vs Dec, brightest stars only):");
println!(" Legend: * (mag < 0), + (mag < 1), . (mag < 2)");
let height: usize = 20;
let width: usize = 100;
let mut grid = vec![vec![' '; width]; height];
let bright_stars = catalog.brighter_than(2.0);
for star in bright_stars {
let x = ((star.ra / 360.0) * width as f64) as usize % width;
let y = ((90.0 - star.dec) / 180.0 * height as f64) as usize;
let y = y.min(height - 1);
let char = if star.mag < 0.0 {
'*' } else if star.mag < 1.0 {
'+' } else {
'.' };
if y < height && x < width {
grid[y][x] = char;
}
}
println!(" {}", "-".repeat(width + 2));
for row in grid {
print!(" |");
for cell in row {
print!("{}", cell);
}
println!("|");
}
println!(" {}", "-".repeat(width + 2));
println!(" South Pole");
println!(" (RA increases from left to right, Dec increases from bottom to top)");
println!(
" Map width: {} characters (auto-adjusted to terminal size)",
width
);
}
"filter" => {
if let Some(path) = output_path {
filter_and_save(&catalog, path, magnitude_limit)?;
} else {
return Err(
"Output path required for filter operation. Use --output option.".into(),
);
}
}
_ => {
return Err(format!("Unknown operation: {}", operation).into());
}
}
Ok(())
}