1use crate::*;
6use std::ffi::{CStr, CString};
7use std::os::raw::c_int;
8
9#[derive(Debug, Clone)]
11#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen(getter_with_clone))]
12pub struct SwissEphError {
13 pub message: String,
15 pub code: i32,
17}
18
19
20impl std::fmt::Display for SwissEphError {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 write!(f, "SwissEph error ({}): {}", self.code, self.message)
23 }
24}
25
26impl std::error::Error for SwissEphError {}
27
28pub type Result<T> = std::result::Result<T, SwissEphError>;
30
31#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
33#[derive(Debug, Clone, Copy)]
34pub struct Position {
35 pub longitude: f64,
37 pub latitude: f64,
39 pub distance: f64,
41 pub longitude_speed: f64,
43 pub latitude_speed: f64,
45 pub distance_speed: f64,
47}
48
49#[derive(Debug, Clone)]
51pub struct HouseCusps {
52 pub cusps: [f64; 12],
54 pub ascendant: f64,
56 pub mc: f64,
58 pub armc: f64,
60 pub vertex: f64,
62 pub equatorial_ascendant: f64,
64 pub co_ascendant_koch: f64,
66 pub co_ascendant_munkasey: f64,
68 pub polar_ascendant: f64,
70}
71
72#[derive(Debug, Clone, Copy)]
74pub struct NodeApsides {
75 pub ascending: f64,
76 pub descending: f64,
77 pub perihelion: f64,
78 pub aphelion: f64,
79}
80
81#[derive(Debug, Clone, Copy)]
83pub struct Phenomenon {
84 pub phase_angle: f64,
85 pub phase: f64,
86 pub elongation: f64,
87 pub diameter_apparent: f64,
88 pub magnitude: f64,
89}
90
91#[derive(Debug, Clone, Copy)]
93pub struct EclipseAttributes {
94 pub time_max: f64,
95 pub time_beg: f64,
96 pub time_end: f64,
97 pub tot_beg: f64,
98 pub tot_end: f64,
99 pub center_line: bool,
100 pub annular: bool,
101 pub total: bool,
102 pub eclipse_magnitude: f64,
103 pub saros_series: i32,
104 pub saros_member: i32,
105}
106
107#[derive(Debug, Clone, Copy)]
109pub struct GeoPos {
110 pub longitude: f64,
112 pub latitude: f64,
114 pub altitude: f64,
116}
117
118#[derive(Debug, Clone, Copy)]
120pub struct RiseSetEvent {
121 pub time: f64,
123 pub valid: bool,
125}
126
127#[derive(Debug, Clone, Copy, Default)]
129pub struct CalcFlags {
130 flags: i32,
131}
132
133impl CalcFlags {
134 pub fn new() -> Self {
136 Self { flags: SEFLG_SWIEPH }
137 }
138
139 pub fn with_speed(mut self) -> Self {
141 self.flags |= SEFLG_SPEED;
142 self
143 }
144
145 pub fn with_true_position(mut self) -> Self {
147 self.flags |= SEFLG_TRUEPOS;
148 self
149 }
150
151 pub fn with_no_aberration(mut self) -> Self {
153 self.flags |= SEFLG_NOABERR;
154 self
155 }
156
157 pub fn with_no_nutation(mut self) -> Self {
159 self.flags |= SEFLG_NONUT;
160 self
161 }
162
163 pub fn with_equatorial(mut self) -> Self {
165 self.flags |= SEFLG_EQUATORIAL;
166 self
167 }
168
169 pub fn with_heliocentric(mut self) -> Self {
171 self.flags |= SEFLG_HELCTR;
172 self
173 }
174
175 pub fn with_topocentric(mut self) -> Self {
177 self.flags |= SEFLG_TOPOCTR;
178 self
179 }
180
181 pub fn with_sidereal(mut self) -> Self {
183 self.flags |= SEFLG_SIDEREAL;
184 self
185 }
186
187 pub fn with_moshier(mut self) -> Self {
189 self.flags = (self.flags & !SEFLG_DEFAULTEPH) | SEFLG_MOSEPH;
190 self
191 }
192
193 pub fn with_swiss_ephemeris(mut self) -> Self {
195 self.flags = (self.flags & !SEFLG_MOSEPH & !SEFLG_JPLEPH) | SEFLG_SWIEPH;
196 self
197 }
198
199 pub fn with_jpl(mut self) -> Self {
201 self.flags = (self.flags & !SEFLG_DEFAULTEPH) | SEFLG_JPLEPH;
202 self
203 }
204
205 pub fn raw(&self) -> i32 {
207 self.flags
208 }
209}
210
211#[derive(Debug, Clone, Copy)]
213#[repr(u8)]
214pub enum HouseSystem {
215 Placidus = b'P',
216 Koch = b'K',
217 Porphyrius = b'O',
218 Regiomontanus = b'R',
219 Campanus = b'C',
220 Equal = b'E',
221 WholeSign = b'W',
222 Alcabitus = b'B',
223 Morinus = b'M',
224 Topocentric = b'T',
225 Vehlow = b'V',
226}
227
228impl HouseSystem {
229 fn as_char(&self) -> c_int {
230 *self as c_int
231 }
232}
233
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
236#[repr(i32)]
237pub enum Planet {
238 Sun = SE_SUN,
239 Moon = SE_MOON,
240 Mercury = SE_MERCURY,
241 Venus = SE_VENUS,
242 Mars = SE_MARS,
243 Jupiter = SE_JUPITER,
244 Saturn = SE_SATURN,
245 Uranus = SE_URANUS,
246 Neptune = SE_NEPTUNE,
247 Pluto = SE_PLUTO,
248 MeanNode = SE_MEAN_NODE,
249 TrueNode = SE_TRUE_NODE,
250 MeanApog = SE_MEAN_APOG,
251 OscuApog = SE_OSCU_APOG,
252 Earth = SE_EARTH,
253 Chiron = SE_CHIRON,
254 Pholus = SE_PHOLUS,
255 Ceres = SE_CERES,
256 Pallas = SE_PALLAS,
257 Juno = SE_JUNO,
258 Vesta = SE_VESTA,
259 IntpApog = SE_INTP_APOG,
260 IntpPerg = SE_INTP_PERG,
261}
262
263impl Planet {
264 pub fn to_int(&self) -> i32 {
265 *self as i32
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271#[repr(i32)]
272pub enum SiderealMode {
273 FaganBradley = SE_SIDM_FAGAN_BRADLEY,
274 Lahiri = SE_SIDM_LAHIRI,
275 DeLuce = SE_SIDM_DELUCE,
276 Raman = SE_SIDM_RAMAN,
277 Ushashashi = SE_SIDM_USHASHASHI,
278 Krishnamurti = SE_SIDM_KRISHNAMURTI,
279 DjwhalKhul = SE_SIDM_DJWHAL_KHUL,
280 Yukteshwar = SE_SIDM_YUKTESHWAR,
281 JNBhasin = SE_SIDM_JN_BHASIN,
282 BabylKugler1 = SE_SIDM_BABYL_KUGLER1,
283 BabylKugler2 = SE_SIDM_BABYL_KUGLER2,
284 BabylKugler3 = SE_SIDM_BABYL_KUGLER3,
285 BabylHuber = SE_SIDM_BABYL_HUBER,
286 BabylEtpsc = SE_SIDM_BABYL_ETPSC,
287 Aldebaran15Tau = SE_SIDM_ALDEBARAN_15TAU,
288 Hipparchos = SE_SIDM_HIPPARCHOS,
289 Sassanian = SE_SIDM_SASSANIAN,
290 Galcent0Sag = SE_SIDM_GALCENT_0SAG,
291 J2000 = SE_SIDM_J2000,
292 J1900 = SE_SIDM_J1900,
293 B1950 = SE_SIDM_B1950,
294 Suryasiddhanta = SE_SIDM_SURYASIDDHANTA,
295 SuryasiddhantaMsun = SE_SIDM_SURYASIDDHANTA_MSUN,
296 Aryabhata = SE_SIDM_ARYABHATA,
297 AryabhataMsun = SE_SIDM_ARYABHATA_MSUN,
298 SsRevati = SE_SIDM_SS_REVATI,
299 SsCitra = SE_SIDM_SS_CITRA,
300 TrueCitra = SE_SIDM_TRUE_CITRA,
301 TrueRevati = SE_SIDM_TRUE_REVATI,
302 TruePushya = SE_SIDM_TRUE_PUSHYA,
303 GalcentRgilbrand = SE_SIDM_GALCENT_RGILBRAND,
304 GalequIau1958 = SE_SIDM_GALEQU_IAU1958,
305 GalequTrue = SE_SIDM_GALEQU_TRUE,
306 GalequMula = SE_SIDM_GALEQU_MULA,
307 GalalignMardyks = SE_SIDM_GALALIGN_MARDYKS,
308 TrueMula = SE_SIDM_TRUE_MULA,
309 GalcentMulaWilhelm = SE_SIDM_GALCENT_MULA_WILHELM,
310 Aryabhata522 = SE_SIDM_ARYABHATA_522,
311 BabylBritton = SE_SIDM_BABYL_BRITTON,
312 TrueSheoran = SE_SIDM_TRUE_SHEORAN,
313 GalcentCochrane = SE_SIDM_GALCENT_COCHRANE,
314 GalequFiorenza = SE_SIDM_GALEQU_FIORENZA,
315 ValensMoon = SE_SIDM_VALENS_MOON,
316 Lahiri1940 = SE_SIDM_LAHIRI_1940,
317 LahiriVp285 = SE_SIDM_LAHIRI_VP285,
318 KrishnamurtiVp291 = SE_SIDM_KRISHNAMURTI_VP291,
319 LahiriIcrc = SE_SIDM_LAHIRI_ICRC,
320 User = SE_SIDM_USER,
321}
322
323impl SiderealMode {
324 pub fn to_int(&self) -> i32 {
325 *self as i32
326 }
327}
328
329#[derive(Debug, Clone, Copy, Default)]
331pub struct RiseTransFlags {
332 flags: i32,
333}
334
335impl RiseTransFlags {
336 pub fn new() -> Self {
337 Self { flags: 0 }
338 }
339
340 pub fn with_rise(mut self) -> Self {
341 self.flags |= SE_CALC_RISE;
342 self
343 }
344
345 pub fn with_set(mut self) -> Self {
346 self.flags |= SE_CALC_SET;
347 self
348 }
349
350 pub fn with_mtransit(mut self) -> Self {
351 self.flags |= SE_CALC_MTRANSIT;
352 self
353 }
354
355 pub fn with_itransit(mut self) -> Self {
356 self.flags |= SE_CALC_ITRANSIT;
357 self
358 }
359
360 pub fn with_disc_center(mut self) -> Self {
361 self.flags |= SE_BIT_DISC_CENTER;
362 self
363 }
364
365 pub fn with_no_refraction(mut self) -> Self {
366 self.flags |= SE_BIT_NO_REFRACTION;
367 self
368 }
369
370 pub fn raw(&self) -> i32 {
371 self.flags
372 }
373}
374
375#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
377pub fn set_ephe_path(path: &str) {
378 let c_path = CString::new(path).unwrap();
379 unsafe {
380 swe_set_ephe_path(c_path.as_ptr());
381 }
382}
383
384pub fn set_topo(longitude: f64, latitude: f64, altitude: f64) {
386 unsafe {
387 swe_set_topo(longitude, latitude, altitude);
388 }
389}
390
391pub fn set_sidereal_mode(mode: SiderealMode) {
393 unsafe {
394 swe_set_sid_mode(mode.to_int(), 0.0, 0.0);
395 }
396}
397
398pub fn close() {
400 unsafe {
401 swe_close();
402 }
403}
404
405#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
407pub fn version() -> String {
408 let mut buf = [0i8; 256];
409 unsafe {
410 swe_version(buf.as_mut_ptr());
411 CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned()
412 }
413}
414
415pub fn julday(year: i32, month: i32, day: i32, hour: f64) -> f64 {
417 unsafe { swe_julday(year, month, day, hour, SE_GREG_CAL) }
418}
419
420pub fn revjul(jd: f64) -> (i32, i32, i32, f64) {
422 let mut year = 0;
423 let mut month = 0;
424 let mut day = 0;
425 let mut hour = 0.0;
426 unsafe {
427 swe_revjul(jd, SE_GREG_CAL, &mut year, &mut month, &mut day, &mut hour);
428 }
429 (year, month, day, hour)
430}
431
432pub fn deltat(jd: f64) -> f64 {
434 unsafe { swe_deltat(jd) }
435}
436
437pub fn sidereal_time(jd_ut: f64) -> f64 {
439 unsafe { swe_sidtime(jd_ut) }
440}
441
442pub fn calc(jd: f64, planet: Planet, flags: CalcFlags) -> Result<Position> {
453 let mut xx = [0.0f64; 6];
454 let mut serr = [0i8; 256];
455
456 let ret = unsafe {
457 swe_calc(jd, planet.to_int(), flags.raw(), xx.as_mut_ptr(), serr.as_mut_ptr())
458 };
459
460 if ret < 0 {
461 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
462 .to_string_lossy()
463 .into_owned();
464 return Err(SwissEphError { message: msg, code: ret });
465 }
466
467 Ok(Position {
468 longitude: xx[0],
469 latitude: xx[1],
470 distance: xx[2],
471 longitude_speed: xx[3],
472 latitude_speed: xx[4],
473 distance_speed: xx[5],
474 })
475}
476
477#[cfg_attr(target_arch = "wasm32", wasm_bindgen::prelude::wasm_bindgen)]
479pub fn calc_ut(jd_ut: f64, planet: i32, flags: i32) -> std::result::Result<Position, SwissEphError> {
480 let mut xx = [0.0f64; 6];
508 let mut serr = [0i8; 256];
509
510 let ret = unsafe {
511 swe_calc_ut(jd_ut, planet, flags, xx.as_mut_ptr(), serr.as_mut_ptr())
512 };
513
514 if ret < 0 {
515 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
516 .to_string_lossy()
517 .into_owned();
518 return Err(SwissEphError { message: msg, code: ret });
519 }
520
521 Ok(Position {
522 longitude: xx[0],
523 latitude: xx[1],
524 distance: xx[2],
525 longitude_speed: xx[3],
526 latitude_speed: xx[4],
527 distance_speed: xx[5],
528 })
529}
530
531pub fn calc_star(jd: f64, star: &str, flags: CalcFlags) -> Result<(String, Position)> {
533 let mut xx = [0.0f64; 6];
534 let mut serr = [0i8; 256];
535 let mut star_buf = [0i8; 512];
536
537 let c_star = CString::new(star).map_err(|e| SwissEphError {
538 message: format!("Invalid star name: {}", e),
539 code: -1,
540 })?;
541
542 let bytes = c_star.as_bytes_with_nul();
543 if bytes.len() > 255 {
544 return Err(SwissEphError {
545 message: "Star name too long".to_string(),
546 code: -1,
547 });
548 }
549 unsafe {
550 std::ptr::copy_nonoverlapping(bytes.as_ptr(), star_buf.as_mut_ptr() as *mut u8, bytes.len());
551 }
552
553 let ret = unsafe {
554 swe_fixstar2(star_buf.as_mut_ptr(), jd, flags.raw(), xx.as_mut_ptr(), serr.as_mut_ptr())
555 };
556
557 if ret < 0 {
558 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
559 .to_string_lossy()
560 .into_owned();
561 return Err(SwissEphError { message: msg, code: ret });
562 }
563
564 let returned_name = unsafe { CStr::from_ptr(star_buf.as_ptr()) }
565 .to_string_lossy()
566 .into_owned();
567
568 Ok((returned_name, Position {
569 longitude: xx[0],
570 latitude: xx[1],
571 distance: xx[2],
572 longitude_speed: xx[3],
573 latitude_speed: xx[4],
574 distance_speed: xx[5],
575 }))
576}
577
578pub fn rise_trans(
580 jd_ut: f64,
581 planet: Planet,
582 star_name: Option<&str>,
583 geopos: GeoPos,
584 flags: RiseTransFlags,
585) -> Result<f64> {
586 let mut tret = 0.0f64;
587 let mut serr = [0i8; 256];
588 let mut dgeo = [geopos.longitude, geopos.latitude, geopos.altitude];
589
590 let mut star_buf = [0i8; 512];
591 if let Some(name) = star_name {
592 let c_star = CString::new(name).map_err(|e| SwissEphError {
593 message: format!("Invalid star name: {}", e),
594 code: -1,
595 })?;
596 let bytes = c_star.as_bytes_with_nul();
597 if bytes.len() < 256 {
598 unsafe {
599 std::ptr::copy_nonoverlapping(bytes.as_ptr(), star_buf.as_mut_ptr() as *mut u8, bytes.len());
600 }
601 }
602 }
603
604 let star_ptr = if star_name.is_some() { star_buf.as_mut_ptr() } else { std::ptr::null_mut() };
605
606 let atpress = 1013.25;
607 let attemp = 10.0;
608
609 let ret = unsafe {
610 swe_rise_trans(
611 jd_ut,
612 planet.to_int(),
613 star_ptr,
614 0, flags.raw(),
616 dgeo.as_mut_ptr(),
617 atpress,
618 attemp,
619 &mut tret,
620 serr.as_mut_ptr()
621 )
622 };
623
624 if ret < 0 {
625 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
626 .to_string_lossy()
627 .into_owned();
628 return Err(SwissEphError { message: msg, code: ret });
629 } else if ret != 0 {
630 return Err(SwissEphError { message: "Event not found (e.g. circumpolar)".to_string(), code: -2 });
632 }
633
634 Ok(tret)
635}
636
637pub fn solar_eclipse_where(jd: f64, flags: i32) -> Result<(f64, f64, f64, f64)> {
639 let mut geopos = [0.0; 10];
640 let mut attr = [0.0; 20];
641 let mut serr = [0i8; 256];
642
643 let ret = unsafe {
644 swe_sol_eclipse_where(jd, flags, geopos.as_mut_ptr(), attr.as_mut_ptr(), serr.as_mut_ptr())
645 };
646
647 if ret < 0 {
648 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
649 .to_string_lossy()
650 .into_owned();
651 return Err(SwissEphError { message: msg, code: ret });
652 }
653
654 Ok((geopos[0], geopos[1], attr[0], attr[1]))
655}
656
657pub fn solar_eclipse_when_loc(
659 jd_start: f64,
660 flags: i32,
661 geopos: GeoPos,
662 backward: bool
663) -> Result<(f64, EclipseAttributes)> {
664 let mut tret = [0.0; 10];
665 let mut attr = [0.0; 20];
666 let mut serr = [0i8; 256];
667 let mut dgeo = [geopos.longitude, geopos.latitude, geopos.altitude];
668
669 let ret = unsafe {
670 swe_sol_eclipse_when_loc(
671 jd_start,
672 flags,
673 dgeo.as_mut_ptr(),
674 tret.as_mut_ptr(),
675 attr.as_mut_ptr(),
676 backward as i32,
677 serr.as_mut_ptr()
678 )
679 };
680
681 if ret < 0 {
682 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
683 .to_string_lossy()
684 .into_owned();
685 return Err(SwissEphError { message: msg, code: ret });
686 }
687
688 Ok((tret[0], EclipseAttributes {
689 time_max: tret[0],
690 time_beg: tret[1],
691 time_end: tret[2],
692 tot_beg: tret[3],
693 tot_end: tret[4],
694 center_line: attr[0] != 0.0,
695 annular: (ret & SE_ECL_ANNULAR) != 0,
696 total: (ret & SE_ECL_TOTAL) != 0,
697 eclipse_magnitude: attr[8],
698 saros_series: attr[10] as i32,
699 saros_member: attr[11] as i32,
700 }))
701}
702
703pub fn heliacal_event(
705 jd_start: f64,
706 geopos: GeoPos,
707 datm: [f64; 4],
708 dobs: [f64; 6],
709 object: &str,
710 event_type: i32,
711 flags: i32
712) -> Result<f64> {
713 let mut dret = [0.0; 50];
714 let mut serr = [0i8; 256];
715 let mut dgeo = [geopos.longitude, geopos.latitude, geopos.altitude];
716 let mut datm_mut = datm; let mut dobs_mut = dobs; let c_obj = CString::new(object).unwrap();
720 let mut obj_buf = [0i8; 256];
722 let bytes = c_obj.as_bytes_with_nul();
723 unsafe {
724 std::ptr::copy_nonoverlapping(bytes.as_ptr(), obj_buf.as_mut_ptr() as *mut u8, bytes.len());
725 }
726
727 let ret = unsafe {
728 swe_heliacal_ut(
729 jd_start,
730 dgeo.as_mut_ptr(),
731 datm_mut.as_mut_ptr(),
732 dobs_mut.as_mut_ptr(),
733 obj_buf.as_mut_ptr(),
734 event_type,
735 flags,
736 dret.as_mut_ptr(),
737 serr.as_mut_ptr()
738 )
739 };
740
741 if ret < 0 {
742 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
743 .to_string_lossy()
744 .into_owned();
745 return Err(SwissEphError { message: msg, code: ret });
746 }
747
748 Ok(dret[0])
749}
750
751
752pub fn azimuth_altitude(
753 jd_ut: f64,
754 flags: i32,
755 geopos: GeoPos,
756 coord: Position
757) -> Result<(f64, f64)> {
758 let mut med_geopos = [geopos.longitude, geopos.latitude, geopos.altitude];
759 let mut xin = [coord.longitude, coord.latitude, coord.distance];
760 let mut xaz = [0.0; 3];
761
762 unsafe {
763 swe_azalt(
764 jd_ut,
765 flags,
766 med_geopos.as_mut_ptr(),
767 1013.25, 10.0, xin.as_mut_ptr(),
770 xaz.as_mut_ptr()
771 );
772 }
773
774 Ok((xaz[0], xaz[1]))
776}
777
778pub fn lunar_eclipse_when_loc(
780 jd_start: f64,
781 flags: i32,
782 geopos: GeoPos,
783 backward: bool
784) -> Result<(f64, EclipseAttributes)> {
785 let mut tret = [0.0; 10];
786 let mut attr = [0.0; 20];
787 let mut serr = [0i8; 256];
788 let mut dgeo = [geopos.longitude, geopos.latitude, geopos.altitude];
789
790 let ret = unsafe {
791 swe_lun_eclipse_when_loc(
792 jd_start,
793 flags,
794 dgeo.as_mut_ptr(),
795 tret.as_mut_ptr(),
796 attr.as_mut_ptr(),
797 backward as i32,
798 serr.as_mut_ptr()
799 )
800 };
801
802 if ret < 0 {
803 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
804 .to_string_lossy()
805 .into_owned();
806 return Err(SwissEphError { message: msg, code: ret });
807 }
808
809 Ok((tret[0], EclipseAttributes {
810 time_max: tret[0],
811 time_beg: tret[1],
812 time_end: tret[2],
813 tot_beg: tret[3],
814 tot_end: tret[4],
815 center_line: false, annular: false, total: (ret & SE_ECL_TOTAL) != 0,
818 eclipse_magnitude: attr[8],
819 saros_series: attr[10] as i32,
820 saros_member: attr[11] as i32,
821 }))
822}
823
824pub fn utc_to_jd(year: i32, month: i32, day: i32, hour: i32, min: i32, sec: f64, gregflag: i32) -> Result<(f64, f64)> {
827 let mut dret = [0.0; 2];
828 let mut serr = [0i8; 256];
829
830 let ret = unsafe {
831 swe_utc_to_jd(year, month, day, hour, min, sec, gregflag, dret.as_mut_ptr(), serr.as_mut_ptr())
832 };
833
834 if ret < 0 {
835 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
836 .to_string_lossy()
837 .into_owned();
838 return Err(SwissEphError { message: msg, code: ret });
839 }
840
841 Ok((dret[0], dret[1]))
842}
843
844pub fn jdet_to_utc(jd_et: f64, gregflag: i32) -> (i32, i32, i32, i32, i32, f64) {
847 let mut year = 0;
848 let mut month = 0;
849 let mut day = 0;
850 let mut hour = 0;
851 let mut min = 0;
852 let mut sec = 0.0;
853
854 unsafe {
855 swe_jdet_to_utc(jd_et, gregflag, &mut year, &mut month, &mut day, &mut hour, &mut min, &mut sec);
856 }
857
858 (year, month, day, hour, min, sec)
859}
860
861pub fn coordinate_transform(position: Position, obliquity: f64) -> Position {
864 let mut xin = [position.longitude, position.latitude, position.distance];
865 let mut xout = [0.0; 3];
866
867 unsafe {
868 swe_cotrans(xin.as_mut_ptr(), xout.as_mut_ptr(), obliquity);
869 }
870
871 Position {
878 longitude: xout[0],
879 latitude: xout[1],
880 distance: xout[2],
881 longitude_speed: 0.0, latitude_speed: 0.0,
883 distance_speed: 0.0,
884 }
885}
886
887pub fn house_pos(armc: f64, geolat: f64, eps: f64, hsys: char, xpin: [f64; 2]) -> Result<f64> {
895 let mut serr = [0i8; 256];
896 let mut xpin_mut = xpin; let ret = unsafe {
899 swe_house_pos(armc, geolat, eps, hsys as i32, xpin_mut.as_mut_ptr(), serr.as_mut_ptr())
900 };
901
902 if ret == 0.0 {
903 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
912 .to_string_lossy();
913 if !msg.is_empty() {
914 return Err(SwissEphError { message: msg.into_owned(), code: -1 });
915 }
916 }
917
918 Ok(ret)
919}
920
921pub fn gauquelin_sector(
923 jd_ut: f64,
924 ipl: i32,
925 star_name: Option<&str>,
926 flags: i32,
927 imeth: i32,
928 geopos: GeoPos,
929 atpress: f64,
930 attemp: f64
931) -> Result<f64> {
932 let mut dgsect = [0.0; 5];
933 let mut serr = [0i8; 256];
934 let mut geopos_arr = [geopos.longitude, geopos.latitude, geopos.altitude];
935
936 let mut star_buf = [0i8; 256];
937 if let Some(name) = star_name {
938 let c_star = CString::new(name).map_err(|e| SwissEphError {
939 message: format!("Invalid star name: {}", e),
940 code: -1,
941 })?;
942 let bytes = c_star.as_bytes_with_nul();
943 if bytes.len() < 256 {
944 unsafe {
945 std::ptr::copy_nonoverlapping(bytes.as_ptr(), star_buf.as_mut_ptr() as *mut u8, bytes.len());
946 }
947 }
948 }
949
950 let ret = unsafe {
951 swe_gauquelin_sector(
952 jd_ut,
953 ipl,
954 star_buf.as_mut_ptr(),
955 flags,
956 imeth,
957 geopos_arr.as_mut_ptr(),
958 atpress,
959 attemp,
960 dgsect.as_mut_ptr(),
961 serr.as_mut_ptr()
962 )
963 };
964
965 if ret < 0 {
966 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
967 .to_string_lossy()
968 .into_owned();
969 return Err(SwissEphError { message: msg, code: ret });
970 }
971
972 Ok(dgsect[0])
973}
974
975pub fn refraction(inalt: f64, atpress: f64, attemp: f64, calc_flag: i32) -> f64 {
978 unsafe {
979 swe_refrac(inalt, atpress, attemp, calc_flag)
980 }
981}
982
983pub fn get_ayanamsa(jd: f64) -> f64 {
985 unsafe { swe_get_ayanamsa(jd) }
986}
987
988pub fn nodes_apsides(jd: f64, planet: Planet, flags: CalcFlags, method: i32) -> Result<NodeApsides> {
990 let mut xnasc = [0.0; 6];
991 let mut xndsc = [0.0; 6];
992 let mut xperi = [0.0; 6];
993 let mut xaphe = [0.0; 6];
994 let mut serr = [0i8; 256];
995
996 let ret = unsafe {
997 swe_nod_aps(
998 jd,
999 planet.to_int(),
1000 flags.raw(),
1001 method,
1002 xnasc.as_mut_ptr(),
1003 xndsc.as_mut_ptr(),
1004 xperi.as_mut_ptr(),
1005 xaphe.as_mut_ptr(),
1006 serr.as_mut_ptr(),
1007 )
1008 };
1009
1010 if ret < 0 {
1011 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
1012 .to_string_lossy()
1013 .into_owned();
1014 return Err(SwissEphError { message: msg, code: ret });
1015 }
1016
1017 Ok(NodeApsides {
1018 ascending: xnasc[0],
1019 descending: xndsc[0],
1020 perihelion: xperi[0],
1021 aphelion: xaphe[0],
1022 })
1023}
1024
1025pub fn phenomena(jd: f64, planet: Planet, flags: CalcFlags) -> Result<Phenomenon> {
1027 let mut attr = [0.0; 20];
1028 let mut serr = [0i8; 256];
1029
1030 let ret = unsafe {
1031 swe_pheno(jd, planet.to_int(), flags.raw(), attr.as_mut_ptr(), serr.as_mut_ptr())
1032 };
1033
1034 if ret < 0 {
1035 let msg = unsafe { CStr::from_ptr(serr.as_ptr()) }
1036 .to_string_lossy()
1037 .into_owned();
1038 return Err(SwissEphError { message: msg, code: ret });
1039 }
1040
1041 Ok(Phenomenon {
1042 phase_angle: attr[0],
1043 phase: attr[1],
1044 elongation: attr[2],
1045 diameter_apparent: attr[3],
1046 magnitude: attr[4],
1047 })
1048}
1049
1050pub fn houses(jd_ut: f64, latitude: f64, longitude: f64, system: HouseSystem) -> Result<HouseCusps> {
1058 let mut cusps = [0.0f64; 13];
1059 let mut ascmc = [0.0f64; 10];
1060
1061 let ret = unsafe {
1062 swe_houses(
1063 jd_ut,
1064 latitude,
1065 longitude,
1066 system.as_char(),
1067 cusps.as_mut_ptr(),
1068 ascmc.as_mut_ptr(),
1069 )
1070 };
1071
1072 if ret < 0 {
1073 return Err(SwissEphError {
1074 message: "House calculation failed".to_string(),
1075 code: ret,
1076 });
1077 }
1078
1079 let mut result_cusps = [0.0f64; 12];
1081 for i in 0..12 {
1082 result_cusps[i] = cusps[i + 1];
1083 }
1084
1085 Ok(HouseCusps {
1086 cusps: result_cusps,
1087 ascendant: ascmc[0],
1088 mc: ascmc[1],
1089 armc: ascmc[2],
1090 vertex: ascmc[3],
1091 equatorial_ascendant: ascmc[4],
1092 co_ascendant_koch: ascmc[5],
1093 co_ascendant_munkasey: ascmc[6],
1094 polar_ascendant: ascmc[7],
1095 })
1096}
1097
1098pub fn get_planet_name(planet: Planet) -> String {
1100 let mut buf = [0i8; 256];
1101 unsafe {
1102 swe_get_planet_name(planet.to_int(), buf.as_mut_ptr());
1103 CStr::from_ptr(buf.as_ptr()).to_string_lossy().into_owned()
1104 }
1105}
1106
1107pub fn normalize_degrees(deg: f64) -> f64 {
1109 unsafe { swe_degnorm(deg) }
1110}
1111
1112#[cfg(test)]
1113mod tests {
1114 use super::*;
1115
1116 #[test]
1117 fn test_safe_julday() {
1118 let jd = julday(2000, 1, 1, 12.0);
1119 assert!((jd - 2451545.0).abs() < 0.0001);
1120 }
1121
1122 #[test]
1123 fn test_safe_revjul() {
1124 let (year, month, day, hour) = revjul(2451545.0);
1125 assert_eq!(year, 2000);
1126 assert_eq!(month, 1);
1127 assert_eq!(day, 1);
1128 assert!((hour - 12.0).abs() < 0.0001);
1129 }
1130
1131 #[test]
1132 fn test_safe_calc() {
1133 let jd = 2451545.0; let flags = CalcFlags::new().with_speed();
1135 let pos = calc(jd, Planet::Sun, flags).unwrap();
1136
1137 assert!(pos.longitude > 270.0 && pos.longitude < 290.0);
1139 assert!(pos.latitude.abs() < 1.0);
1140 }
1141
1142 #[test]
1143 fn test_safe_houses() {
1144 let jd_ut = 2451545.0;
1145 let cusps = houses(jd_ut, 47.3769, 8.5417, HouseSystem::Placidus).unwrap();
1146
1147 assert!(cusps.ascendant >= 0.0 && cusps.ascendant < 360.0);
1149 assert!(cusps.mc >= 0.0 && cusps.mc < 360.0);
1150 }
1151
1152 #[test]
1153 fn test_version() {
1154 let v = version();
1155 assert!(!v.is_empty());
1156 assert!(v.chars().next().unwrap().is_ascii_digit());
1158 }
1159}