practical_astronomy_rust/
sun.rs

1use crate::macros as pa_m;
2use crate::types as pa_t;
3use crate::util as pa_u;
4
5/// Calculate approximate position of the sun for a local date and time.
6///
7/// ## Arguments
8/// * `lct_hours` -- Local civil time, in hours.
9/// * `lct_minutes` -- Local civil time, in minutes.
10/// * `lct_seconds` -- Local civil time, in seconds.
11/// * `local_day` -- Local date, day part.
12/// * `local_month` -- Local date, month part.
13/// * `local_year` -- Local date, year part.
14/// * `is_daylight_saving` -- Is daylight savings in effect?
15/// * `zone_correction` -- Time zone correction, in hours.
16///
17/// ## Returns
18/// * `sun_ra_hour` -- Right Ascension of Sun, hour part
19/// * `sun_ra_min` -- Right Ascension of Sun, minutes part
20/// * `sun_ra_sec` -- Right Ascension of Sun, seconds part
21/// * `sun_dec_deg` -- Declination of Sun, degrees part
22/// * `sun_dec_min` -- Declination of Sun, minutes part
23/// * `sun_dec_sec` -- Declination of Sun, seconds part
24pub fn approximate_position_of_sun(
25    lct_hours: f64,
26    lct_minutes: f64,
27    lct_seconds: f64,
28    local_day: f64,
29    local_month: u32,
30    local_year: u32,
31    is_daylight_saving: bool,
32    zone_correction: i32,
33) -> (f64, f64, f64, f64, f64, f64) {
34    let daylight_saving = if is_daylight_saving == true { 1 } else { 0 };
35
36    let greenwich_date_day = pa_m::lct_gday(
37        lct_hours,
38        lct_minutes,
39        lct_seconds,
40        daylight_saving,
41        zone_correction,
42        local_day,
43        local_month,
44        local_year,
45    );
46    let greenwich_date_month = pa_m::lct_gmonth(
47        lct_hours,
48        lct_minutes,
49        lct_seconds,
50        daylight_saving,
51        zone_correction,
52        local_day,
53        local_month,
54        local_year,
55    );
56    let greenwich_date_year = pa_m::lct_gyear(
57        lct_hours,
58        lct_minutes,
59        lct_seconds,
60        daylight_saving,
61        zone_correction,
62        local_day,
63        local_month,
64        local_year,
65    );
66    let ut_hours = pa_m::lct_ut(
67        lct_hours,
68        lct_minutes,
69        lct_seconds,
70        daylight_saving,
71        zone_correction,
72        local_day,
73        local_month,
74        local_year,
75    );
76    let ut_days = ut_hours / 24.0;
77    let jd_days = pa_m::cd_jd(
78        greenwich_date_day,
79        greenwich_date_month,
80        greenwich_date_year,
81    ) + ut_days;
82    let d_days = jd_days - pa_m::cd_jd(0 as f64, 1, 2010);
83    let n_deg = 360.0 * d_days / 365.242191;
84    let m_deg1 = n_deg + pa_m::sun_e_long(0 as f64, 1, 2010) - pa_m::sun_peri(0 as f64, 1, 2010);
85    let m_deg2 = m_deg1 - 360.0 * (m_deg1 / 360.0).floor();
86    let e_c_deg =
87        360.0 * pa_m::sun_ecc(0 as f64, 1, 2010) * m_deg2.to_radians().sin() / std::f64::consts::PI;
88    let l_s_deg1 = n_deg + e_c_deg + pa_m::sun_e_long(0 as f64, 1, 2010);
89    let l_s_deg2 = l_s_deg1 - 360.0 * (l_s_deg1 / 360.0).floor();
90    let ra_deg = pa_m::ec_ra(
91        l_s_deg2,
92        0 as f64,
93        0 as f64,
94        0 as f64,
95        0 as f64,
96        0 as f64,
97        greenwich_date_day,
98        greenwich_date_month,
99        greenwich_date_year,
100    );
101    let ra_hours = pa_m::dd_dh(ra_deg);
102    let dec_deg = pa_m::ec_dec(
103        l_s_deg2,
104        0 as f64,
105        0 as f64,
106        0 as f64,
107        0 as f64,
108        0 as f64,
109        greenwich_date_day,
110        greenwich_date_month,
111        greenwich_date_year,
112    );
113
114    let sun_ra_hour = pa_m::dh_hour(ra_hours);
115    let sun_ra_min = pa_m::dh_min(ra_hours);
116    let sun_ra_sec = pa_m::dh_sec(ra_hours);
117    let sun_dec_deg = pa_m::dd_deg(dec_deg);
118    let sun_dec_min = pa_m::dd_min(dec_deg);
119    let sun_dec_sec = pa_m::dd_sec(dec_deg);
120
121    return (
122        sun_ra_hour as f64,
123        sun_ra_min as f64,
124        sun_ra_sec,
125        sun_dec_deg,
126        sun_dec_min,
127        sun_dec_sec,
128    );
129}
130
131/// Calculate precise position of the sun for a local date and time.
132///
133/// ## Arguments
134/// * `lct_hours` -- Local civil time, in hours.
135/// * `lct_minutes` -- Local civil time, in minutes.
136/// * `lct_seconds` -- Local civil time, in seconds.
137/// * `local_day` -- Local date, day part.
138/// * `local_month` -- Local date, month part.
139/// * `local_year` -- Local date, year part.
140/// * `is_daylight_saving` -- Is daylight savings in effect?
141/// * `zone_correction` -- Time zone correction, in hours.
142///
143/// ## Returns
144/// * `sun_ra_hour` -- Right Ascension of Sun, hour part
145/// * `sun_ra_min` -- Right Ascension of Sun, minutes part
146/// * `sun_ra_sec` -- Right Ascension of Sun, seconds part
147/// * `sun_dec_deg` -- Declination of Sun, degrees part
148/// * `sun_dec_min` -- Declination of Sun, minutes part
149/// * `sun_dec_sec` -- Declination of Sun, seconds part
150pub fn precise_position_of_sun(
151    lct_hours: f64,
152    lct_minutes: f64,
153    lct_seconds: f64,
154    local_day: f64,
155    local_month: u32,
156    local_year: u32,
157    is_daylight_saving: bool,
158    zone_correction: i32,
159) -> (f64, f64, f64, f64, f64, f64) {
160    let daylight_saving = if is_daylight_saving == true { 1 } else { 0 };
161
162    let g_day = pa_m::lct_gday(
163        lct_hours,
164        lct_minutes,
165        lct_seconds,
166        daylight_saving,
167        zone_correction,
168        local_day,
169        local_month,
170        local_year,
171    );
172    let g_month = pa_m::lct_gmonth(
173        lct_hours,
174        lct_minutes,
175        lct_seconds,
176        daylight_saving,
177        zone_correction,
178        local_day,
179        local_month,
180        local_year,
181    );
182    let g_year = pa_m::lct_gyear(
183        lct_hours,
184        lct_minutes,
185        lct_seconds,
186        daylight_saving,
187        zone_correction,
188        local_day,
189        local_month,
190        local_year,
191    );
192    let sun_ecliptic_longitude_deg = pa_m::sun_long(
193        lct_hours,
194        lct_minutes,
195        lct_seconds,
196        daylight_saving,
197        zone_correction,
198        local_day,
199        local_month,
200        local_year,
201    );
202    let ra_deg = pa_m::ec_ra(
203        sun_ecliptic_longitude_deg,
204        0.0,
205        0.0,
206        0.0,
207        0.0,
208        0.0,
209        g_day,
210        g_month,
211        g_year,
212    );
213    let ra_hours = pa_m::dd_dh(ra_deg);
214    let dec_deg = pa_m::ec_dec(
215        sun_ecliptic_longitude_deg,
216        0.0,
217        0.0,
218        0.0,
219        0.0,
220        0.0,
221        g_day,
222        g_month,
223        g_year,
224    );
225
226    let sun_ra_hour = pa_m::dh_hour(ra_hours);
227    let sun_ra_min = pa_m::dh_min(ra_hours);
228    let sun_ra_sec = pa_m::dh_sec(ra_hours);
229    let sun_dec_deg = pa_m::dd_deg(dec_deg);
230    let sun_dec_min = pa_m::dd_min(dec_deg);
231    let sun_dec_sec = pa_m::dd_sec(dec_deg);
232
233    return (
234        sun_ra_hour as f64,
235        sun_ra_min as f64,
236        sun_ra_sec,
237        sun_dec_deg,
238        sun_dec_min,
239        sun_dec_sec,
240    );
241}
242
243/// Calculate distance to the Sun (in km), and angular size.
244///
245/// ## Arguments
246/// * `lct_hours` -- Local civil time, in hours.
247/// * `lct_minutes` -- Local civil time, in minutes.
248/// * `lct_seconds` -- Local civil time, in seconds.
249/// * `local_day` -- Local date, day part.
250/// * `local_month` -- Local date, month part.
251/// * `local_year` -- Local date, year part.
252/// * `is_daylight_saving` -- Is daylight savings in effect?
253/// * `zone_correction` -- Time zone correction, in hours.
254///
255/// ## Returns
256/// * `sun_dist_km` -- Sun's distance, in kilometers
257/// * `sun_ang_size_deg` -- Sun's angular size (degrees part)
258/// * `sun_ang_size_min` -- Sun's angular size (minutes part)
259/// * `sun_ang_size_sec` -- Sun's angular size (seconds part)
260pub fn sun_distance_and_angular_size(
261    lct_hours: f64,
262    lct_minutes: f64,
263    lct_seconds: f64,
264    local_day: f64,
265    local_month: u32,
266    local_year: u32,
267    is_daylight_saving: bool,
268    zone_correction: i32,
269) -> (f64, f64, f64, f64) {
270    let daylight_saving = if is_daylight_saving == true { 1 } else { 0 };
271
272    let g_day = pa_m::lct_gday(
273        lct_hours,
274        lct_minutes,
275        lct_seconds,
276        daylight_saving,
277        zone_correction,
278        local_day,
279        local_month,
280        local_year,
281    );
282    let g_month = pa_m::lct_gmonth(
283        lct_hours,
284        lct_minutes,
285        lct_seconds,
286        daylight_saving,
287        zone_correction,
288        local_day,
289        local_month,
290        local_year,
291    );
292    let g_year = pa_m::lct_gyear(
293        lct_hours,
294        lct_minutes,
295        lct_seconds,
296        daylight_saving,
297        zone_correction,
298        local_day,
299        local_month,
300        local_year,
301    );
302    let true_anomaly_deg = pa_m::sun_true_anomaly(
303        lct_hours,
304        lct_minutes,
305        lct_seconds,
306        daylight_saving,
307        zone_correction,
308        local_day,
309        local_month,
310        local_year,
311    );
312    let true_anomaly_rad = true_anomaly_deg.to_radians();
313    let eccentricity = pa_m::sun_ecc(g_day, g_month, g_year);
314    let f = (1.0 + eccentricity * true_anomaly_rad.cos()) / (1.0 - eccentricity * eccentricity);
315    let r_km = 149598500.0 / f;
316    let theta_deg = f * 0.533128;
317
318    let sun_dist_km = pa_u::round_f64(r_km, 0);
319    let sun_ang_size_deg = pa_m::dd_deg(theta_deg);
320    let sun_ang_size_min = pa_m::dd_min(theta_deg);
321    let sun_ang_size_sec = pa_m::dd_sec(theta_deg);
322
323    return (
324        sun_dist_km,
325        sun_ang_size_deg,
326        sun_ang_size_min,
327        sun_ang_size_sec,
328    );
329}
330
331/// Calculate local sunrise and sunset.
332///
333/// ## Arguments
334/// * local_day -- Local date, day part.
335/// * local_month -- Local date, month part.
336/// * local_year -- Local date, year part.
337/// * is_daylight_saving -- Is daylight savings in effect?
338/// * zone_correction -- Time zone correction, in hours.
339/// * geographical_long_deg -- Geographical longitude, in degrees.
340/// * geographical_lat_deg -- Geographical latitude, in degrees.
341///
342/// ## Returns
343/// * local_sunrise_hour -- Local sunrise, hour part
344/// * local_sunrise_minute -- Local sunrise, minutes part
345/// * local_sunset_hour -- Local sunset, hour part
346/// * local_sunset_minute -- Local sunset, minutes part
347/// * azimuth_of_sunrise_deg -- Azimuth (horizon direction) of sunrise, in degrees
348/// * azimuth_of_sunset_deg -- Azimuth (horizon direction) of sunset, in degrees
349/// * status -- Calculation status
350pub fn sunrise_and_sunset(
351    local_day: f64,
352    local_month: u32,
353    local_year: u32,
354    is_daylight_saving: bool,
355    zone_correction: i32,
356    geographical_long_deg: f64,
357    geographical_lat_deg: f64,
358) -> (f64, f64, f64, f64, f64, f64, String) {
359    let daylight_saving = if is_daylight_saving == true { 1 } else { 0 };
360
361    let local_sunrise_hours = pa_m::sunrise_lct(
362        local_day,
363        local_month,
364        local_year,
365        daylight_saving,
366        zone_correction,
367        geographical_long_deg,
368        geographical_lat_deg,
369    );
370
371    let local_sunset_hours = pa_m::sunset_lct(
372        local_day,
373        local_month,
374        local_year,
375        daylight_saving,
376        zone_correction,
377        geographical_long_deg,
378        geographical_lat_deg,
379    );
380
381    let sun_rise_set_status = pa_m::e_sun_rs(
382        local_day,
383        local_month,
384        local_year,
385        daylight_saving,
386        zone_correction,
387        geographical_long_deg,
388        geographical_lat_deg,
389    );
390
391    let adjusted_sunrise_hours = local_sunrise_hours + 0.008333;
392    let adjusted_sunset_hours = local_sunset_hours + 0.008333;
393    let azimuth_of_sunrise_deg1 = pa_m::sunrise_az(
394        local_day,
395        local_month,
396        local_year,
397        daylight_saving,
398        zone_correction,
399        geographical_long_deg,
400        geographical_lat_deg,
401    );
402    let azimuth_of_sunset_deg1 = pa_m::sunset_az(
403        local_day,
404        local_month,
405        local_year,
406        daylight_saving,
407        zone_correction,
408        geographical_long_deg,
409        geographical_lat_deg,
410    );
411
412    let local_sunrise_hour = if sun_rise_set_status == "OK" {
413        pa_m::dh_hour(adjusted_sunrise_hours) as f64
414    } else {
415        0.0
416    };
417    let local_sunrise_minute = if sun_rise_set_status == "OK" {
418        pa_m::dh_min(adjusted_sunrise_hours) as f64
419    } else {
420        0.0
421    };
422    let local_sunset_hour = if sun_rise_set_status == "OK" {
423        pa_m::dh_hour(adjusted_sunset_hours) as f64
424    } else {
425        0.0
426    };
427    let local_sunset_minute = if sun_rise_set_status == "OK" {
428        pa_m::dh_min(adjusted_sunset_hours) as f64
429    } else {
430        0.0
431    };
432    let azimuth_of_sunrise_deg = if sun_rise_set_status == "OK" {
433        pa_u::round_f64(azimuth_of_sunrise_deg1, 2)
434    } else {
435        0.0
436    };
437    let azimuth_of_sunset_deg = if sun_rise_set_status == "OK" {
438        pa_u::round_f64(azimuth_of_sunset_deg1, 2)
439    } else {
440        0.0
441    };
442    let status = sun_rise_set_status.to_string();
443
444    return (
445        local_sunrise_hour,
446        local_sunrise_minute,
447        local_sunset_hour,
448        local_sunset_minute,
449        azimuth_of_sunrise_deg,
450        azimuth_of_sunset_deg,
451        status,
452    );
453}
454
455/// Calculate times of morning and evening twilight.
456///
457/// ## Arguments
458/// * `local_day` -- Local date, day part.
459/// * `local_month` -- Local date, month part.
460/// * `local_year` -- Local date, year part.
461/// * `is_daylight_saving` -- Is daylight savings in effect?
462/// * `zone_correction` -- Time zone correction, in hours.
463/// * `geographical_long_deg` -- Geographical longitude, in degrees.
464/// * `geographical_lat_deg` -- Geographical latitude, in degrees.
465/// * `twilight_type` -- "C" (civil), "N" (nautical), or "A" (astronomical).
466///
467/// ## Returns
468/// * `am_twilight_begins_hour` -- Beginning of AM twilight (hour part)
469/// * `am_twilight_begins_min` -- Beginning of AM twilight (minutes part)
470/// * `pm_twilight_ends_hour` -- Ending of PM twilight (hour part)
471/// * `pm_twilight_ends_min` -- Ending of PM twilight (minutes part)
472/// * `status` -- Calculation status
473pub fn morning_and_evening_twilight(
474    local_day: f64,
475    local_month: u32,
476    local_year: u32,
477    is_daylight_saving: bool,
478    zone_correction: i32,
479    geographical_long_deg: f64,
480    geographical_lat_deg: f64,
481    twilight_type: pa_t::TwilightType,
482) -> (f64, f64, f64, f64, String) {
483    let daylight_saving = if is_daylight_saving == true { 1 } else { 0 };
484
485    let start_of_am_twilight_hours = pa_m::twilight_am_lct(
486        local_day,
487        local_month,
488        local_year,
489        daylight_saving,
490        zone_correction,
491        geographical_long_deg,
492        geographical_lat_deg,
493        &twilight_type,
494    );
495
496    let end_of_pm_twilight_hours = pa_m::twilight_pm_lct(
497        local_day,
498        local_month,
499        local_year,
500        daylight_saving,
501        zone_correction,
502        geographical_long_deg,
503        geographical_lat_deg,
504        &twilight_type,
505    );
506
507    let twilight_status = pa_m::e_twilight(
508        local_day,
509        local_month,
510        local_year,
511        daylight_saving,
512        zone_correction,
513        geographical_long_deg,
514        geographical_lat_deg,
515        &twilight_type,
516    );
517
518    let adjusted_am_start_time = start_of_am_twilight_hours + 0.008333;
519    let adjusted_pm_start_time = end_of_pm_twilight_hours + 0.008333;
520
521    let am_twilight_begins_hour = if twilight_status == "OK" {
522        pa_m::dh_hour(adjusted_am_start_time) as f64
523    } else {
524        -99.0
525    };
526    let am_twilight_begins_min = if twilight_status == "OK" {
527        pa_m::dh_min(adjusted_am_start_time) as f64
528    } else {
529        -99.0
530    };
531    let pm_twilight_ends_hour = if twilight_status == "OK" {
532        pa_m::dh_hour(adjusted_pm_start_time) as f64
533    } else {
534        -99.0
535    };
536    let pm_twilight_ends_min = if twilight_status == "OK" {
537        pa_m::dh_min(adjusted_pm_start_time) as f64
538    } else {
539        -99.0
540    };
541    let status = twilight_status;
542
543    return (
544        am_twilight_begins_hour,
545        am_twilight_begins_min,
546        pm_twilight_ends_hour,
547        pm_twilight_ends_min,
548        status,
549    );
550}
551
552/// Calculate the equation of time. (The difference between the real Sun time and the mean Sun time.)
553///
554/// ## Arguments
555/// * `gwdate_day` -- Greenwich date (day part)
556/// * `gwdate_month` -- Greenwich date (month part)
557/// * `gwdate_year` -- Greenwich date (year part)
558///
559/// ## Returns
560/// * `equation_of_time_min` -- equation of time (minute part)
561/// * `equation_of_time_sec` -- equation of time (seconds part)
562pub fn equation_of_time(gwdate_day: f64, gwdate_month: u32, gwdate_year: u32) -> (f64, f64) {
563    let sun_longitude_deg =
564        pa_m::sun_long(12.0, 0.0, 0.0, 0, 0, gwdate_day, gwdate_month, gwdate_year);
565    let sun_ra_hours = pa_m::dd_dh(pa_m::ec_ra(
566        sun_longitude_deg,
567        0.0,
568        0.0,
569        0.0,
570        0.0,
571        0.0,
572        gwdate_day,
573        gwdate_month,
574        gwdate_year,
575    ));
576    let equivalent_ut_hours = pa_m::gst_ut(
577        sun_ra_hours,
578        0.0,
579        0.0,
580        gwdate_day,
581        gwdate_month,
582        gwdate_year,
583    );
584    let equation_of_time_hours = equivalent_ut_hours - 12.0;
585
586    let equation_of_time_min = pa_m::dh_min(equation_of_time_hours) as f64;
587    let equation_of_time_sec = pa_m::dh_sec(equation_of_time_hours);
588
589    return (equation_of_time_min, equation_of_time_sec);
590}
591
592/// Calculate solar elongation for a celestial body.
593///
594/// Solar elongation is the angle between the lines of sight from the Earth to the Sun and from the Earth to the celestial body.
595///
596/// ## Arguments
597/// * `ra_hour` -- Right Ascension, hour part
598/// * `ra_min` -- Right Ascension, minutes part
599/// * `ra_sec` -- Right Ascension, seconds part
600/// * `dec_deg` -- Declination, degrees part
601/// * `dec_min` -- Declination, minutes part
602/// * `dec_sec` -- Declination, seconds part
603/// * `gwdate_day` -- Greenwich Date, day part
604/// * `gwdate_month` -- Greenwich Date, month part
605/// * `gwdate_year` -- Greenwich Date, year part
606///
607/// ## Returns
608/// * `solar_elongation_deg` -- Solar elongation, in degrees
609pub fn solar_elongation(
610    ra_hour: f64,
611    ra_min: f64,
612    ra_sec: f64,
613    dec_deg: f64,
614    dec_min: f64,
615    dec_sec: f64,
616    gwdate_day: f64,
617    gwdate_month: u32,
618    gwdate_year: u32,
619) -> f64 {
620    let sun_longitude_deg =
621        pa_m::sun_long(0.0, 0.0, 0.0, 0, 0, gwdate_day, gwdate_month, gwdate_year);
622    let sun_ra_hours = pa_m::dd_dh(pa_m::ec_ra(
623        sun_longitude_deg,
624        0.0,
625        0.0,
626        0.0,
627        0.0,
628        0.0,
629        gwdate_day,
630        gwdate_month,
631        gwdate_year,
632    ));
633    let sun_dec_deg = pa_m::ec_dec(
634        sun_longitude_deg,
635        0.0,
636        0.0,
637        0.0,
638        0.0,
639        0.0,
640        gwdate_day,
641        gwdate_month,
642        gwdate_year,
643    );
644    let solar_elongation_deg = pa_m::angle(
645        sun_ra_hours,
646        0.0,
647        0.0,
648        sun_dec_deg,
649        0.0,
650        0.0,
651        ra_hour,
652        ra_min,
653        ra_sec,
654        dec_deg,
655        dec_min,
656        dec_sec,
657        pa_t::AngleMeasure::Hours,
658    );
659
660    return pa_u::round_f64(solar_elongation_deg, 2);
661}