1use std::fs::File;
4use std::io::Read;
5use std::str::FromStr;
6
7use crate::config::AppConfig;
8use crate::time::JulianDate;
9use crate::{Error, Result};
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct Position {
14 pub x: f64,
16 pub y: f64,
18 pub z: f64,
20}
21
22impl Position {
23 pub fn new(x: f64, y: f64, z: f64) -> Self {
25 Self { x, y, z }
26 }
27
28 pub fn distance(&self) -> f64 {
30 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct SpaceObject {
37 pub active: bool,
39 header_data: Vec<i32>,
41 pub name: String,
43 pub coefficient_length: i32,
45}
46
47impl SpaceObject {
48 fn new(name: String, active: bool) -> Self {
49 Self {
50 active,
51 header_data: Vec::new(),
52 name,
53 coefficient_length: 0,
54 }
55 }
56}
57
58pub struct Ephemeris {
60 config: AppConfig,
61 bodies: Vec<SpaceObject>,
62 start_year: i32,
63 end_year: i32,
64 ncoeff: i32,
65 emrat: f64,
66 interval: i32,
67 julian_start: f64,
68 julian_end: f64,
69}
70
71impl Ephemeris {
72 pub fn new(config_path: &str) -> Result<Self> {
84 let config = AppConfig::new(config_path)?;
85 let mut eph = Self {
86 config,
87 bodies: Vec::new(),
88 start_year: 0,
89 end_year: 0,
90 ncoeff: 0,
91 emrat: 0.0,
92 interval: 0,
93 julian_start: 0.0,
94 julian_end: 0.0,
95 };
96
97 eph.initialize()?;
98 Ok(eph)
99 }
100
101 fn initialize(&mut self) -> Result<()> {
103 self.read_init_data()?;
104 self.read_header()?;
105 self.calculate_coefficient_lengths();
106 Ok(())
107 }
108
109 fn read_init_data(&mut self) -> Result<()> {
111 let path = &self.config.initial_data_dat;
112 let mut indat = File::open(path)?;
113 let mut buffer = String::new();
114 indat.read_to_string(&mut buffer)?;
115 let lines: Vec<&str> = buffer.lines().collect();
116 let mut i = 0;
117
118 while i < lines.len() {
119 let line = lines[i];
120 i += 1;
121
122 if line == "BODIES:" {
123 while i < lines.len() {
124 let body_line = lines[i];
125 i += 1;
126 if body_line == "DATE:" {
127 break;
128 }
129 let mut parts = body_line.split_whitespace();
130 if let Some(name) = parts.next() {
131 let mut so = SpaceObject::new(name.to_string(), false);
132 if let Some(state_str) = parts.next() {
133 so.active = bool::from_str(state_str).unwrap_or(false);
134 }
135 self.bodies.push(so);
136 }
137 }
138 }
139
140 if line == "DATE:" {
141 if i < lines.len() {
142 let start_year_str = lines[i];
143 i += 1;
144 if start_year_str.starts_with("Start_year") {
145 self.start_year = start_year_str
146 .split_whitespace()
147 .last()
148 .and_then(|s| i32::from_str(s).ok())
149 .unwrap_or(0);
150 }
151 }
152
153 if i < lines.len() {
154 let end_year_str = lines[i];
155 i += 1;
156 if end_year_str.starts_with("End_year") {
157 self.end_year = end_year_str
158 .split_whitespace()
159 .last()
160 .and_then(|s| i32::from_str(s).ok())
161 .unwrap_or(0);
162 }
163 }
164 }
165 }
166
167 Ok(())
168 }
169
170 fn read_header(&mut self) -> Result<()> {
172 let path = &self.config.header_441;
173 let mut header = File::open(path)?;
174 let mut buffer = String::new();
175 header.read_to_string(&mut buffer)?;
176 let lines: Vec<&str> = buffer.lines().collect();
177 let mut number_emrat = 0;
178
179 for line in lines {
180 let mut parts = line.split_whitespace();
181 if let Some(key) = parts.next() {
182 match key {
183 "NCOEFF=" => {
184 self.ncoeff = parts
185 .next()
186 .and_then(|s| i32::from_str(s).ok())
187 .unwrap_or(0);
188 }
189 "GROUP" => {
190 if let Some(group_number_str) = parts.next() {
191 match group_number_str {
192 "1030" => {
193 if let Some(start_str) = parts.next() {
194 self.julian_start = f64::from_str(start_str).unwrap_or(0.0);
195 }
196 if let Some(end_str) = parts.next() {
197 self.julian_end = f64::from_str(end_str).unwrap_or(0.0);
198 }
199 if let Some(interval_str) = parts.next() {
200 self.interval = i32::from_str(interval_str).unwrap_or(0);
201 }
202 }
203 "1040" => {
204 if let Some(size_str) = parts.next() {
205 let size = i32::from_str(size_str).unwrap_or(0);
206 for i in 0..size {
207 if let Some(tmp) = parts.next() {
208 if tmp == "EMRAT" {
209 number_emrat = i;
210 break;
211 }
212 }
213 }
214 }
215 }
216 "1041" => {
217 for _ in 0..number_emrat {
218 parts.next();
219 }
220 if let Some(emrat_str) = parts.next() {
221 let mut emrat = emrat_str.to_string();
222 let len = emrat.len();
223 emrat.truncate(len - 4);
224 emrat.push('E');
225 self.emrat = f64::from_str(&emrat).unwrap_or(0.0);
226 }
227 }
228 "1050" => {
229 for _ in 0..3 {
230 for body in &mut self.bodies {
231 if let Some(buf_str) = parts.next() {
232 let buf = i32::from_str(buf_str).unwrap_or(0);
233 body.header_data.push(buf);
234 }
235 }
236 }
237 }
238 _ => {}
239 }
240 }
241 }
242 _ => {}
243 }
244 }
245 }
246
247 Ok(())
248 }
249
250 fn calculate_coefficient_lengths(&mut self) {
252 let len = self.bodies.len();
253 for i in 0..len - 1 {
254 if !self.bodies[i].header_data.is_empty() && !self.bodies[i + 1].header_data.is_empty()
255 {
256 self.bodies[i].coefficient_length =
257 self.bodies[i + 1].header_data[0] - self.bodies[i].header_data[0];
258 }
259 }
260 if !self.bodies.is_empty() && !self.bodies[len - 1].header_data.is_empty() {
261 self.bodies[len - 1].coefficient_length =
262 self.ncoeff - self.bodies[len - 1].header_data[0];
263 }
264 }
265
266 pub fn get_position(&self, body_name: &str, jd: JulianDate) -> Result<Position> {
284 if jd.jd < self.julian_start || jd.jd > self.julian_end {
286 return Err(Error::Ephemeris(format!(
287 "Julian date {} is outside valid range [{}, {}]",
288 jd.jd, self.julian_start, self.julian_end
289 )));
290 }
291
292 let body = self
294 .bodies
295 .iter()
296 .find(|b| {
297 b.name.eq_ignore_ascii_case(body_name)
298 || b.name.replace("_", "").eq_ignore_ascii_case(body_name)
299 })
300 .ok_or_else(|| {
301 Error::Ephemeris(format!(
302 "Body '{}' not found. Available bodies: {}",
303 body_name,
304 self.bodies
305 .iter()
306 .map(|b| b.name.clone())
307 .collect::<Vec<_>>()
308 .join(", ")
309 ))
310 })?;
311
312 if !body.active {
313 return Err(Error::Ephemeris(format!(
314 "Body '{}' is not active in this ephemeris",
315 body_name
316 )));
317 }
318
319 Ok(Position::new(0.0, 0.0, 0.0))
323 }
324
325 pub fn get_bodies(&self) -> Vec<&SpaceObject> {
327 self.bodies.iter().collect()
328 }
329
330 pub fn get_date_range(&self) -> (f64, f64) {
332 (self.julian_start, self.julian_end)
333 }
334
335 pub fn get_metadata(&self) -> EphemerisMetadata {
337 EphemerisMetadata {
338 start_year: self.start_year,
339 end_year: self.end_year,
340 julian_start: self.julian_start,
341 julian_end: self.julian_end,
342 interval_days: self.interval as f64,
343 earth_moon_ratio: self.emrat,
344 number_of_coefficients: self.ncoeff,
345 }
346 }
347}
348
349#[derive(Debug, Clone)]
351pub struct EphemerisMetadata {
352 pub start_year: i32,
353 pub end_year: i32,
354 pub julian_start: f64,
355 pub julian_end: f64,
356 pub interval_days: f64,
357 pub earth_moon_ratio: f64,
358 pub number_of_coefficients: i32,
359}