astronoby 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -0
  3. data/.standard.yml +1 -0
  4. data/CHANGELOG.md +162 -0
  5. data/Gemfile.lock +54 -34
  6. data/README.md +42 -272
  7. data/UPGRADING.md +238 -0
  8. data/benchmark/README.md +131 -0
  9. data/benchmark/benchmark.rb +259 -0
  10. data/benchmark/data/imcce.csv.zip +0 -0
  11. data/benchmark/data/sun_calc.csv.zip +0 -0
  12. data/lib/astronoby/aberration.rb +56 -31
  13. data/lib/astronoby/angle.rb +20 -16
  14. data/lib/astronoby/angles/dms.rb +2 -2
  15. data/lib/astronoby/angles/hms.rb +2 -2
  16. data/lib/astronoby/bodies/earth.rb +56 -0
  17. data/lib/astronoby/bodies/jupiter.rb +11 -0
  18. data/lib/astronoby/bodies/mars.rb +11 -0
  19. data/lib/astronoby/bodies/mercury.rb +11 -0
  20. data/lib/astronoby/bodies/moon.rb +50 -285
  21. data/lib/astronoby/bodies/neptune.rb +11 -0
  22. data/lib/astronoby/bodies/saturn.rb +11 -0
  23. data/lib/astronoby/bodies/solar_system_body.rb +122 -0
  24. data/lib/astronoby/bodies/sun.rb +16 -220
  25. data/lib/astronoby/bodies/uranus.rb +11 -0
  26. data/lib/astronoby/bodies/venus.rb +11 -0
  27. data/lib/astronoby/constants.rb +13 -1
  28. data/lib/astronoby/coordinates/ecliptic.rb +2 -37
  29. data/lib/astronoby/coordinates/equatorial.rb +25 -7
  30. data/lib/astronoby/coordinates/horizontal.rb +0 -46
  31. data/lib/astronoby/corrections/light_time_delay.rb +90 -0
  32. data/lib/astronoby/deflection.rb +187 -0
  33. data/lib/astronoby/distance.rb +9 -0
  34. data/lib/astronoby/ephem.rb +39 -0
  35. data/lib/astronoby/equinox_solstice.rb +21 -18
  36. data/lib/astronoby/errors.rb +4 -0
  37. data/lib/astronoby/events/moon_phases.rb +2 -1
  38. data/lib/astronoby/events/rise_transit_set_calculator.rb +352 -0
  39. data/lib/astronoby/events/rise_transit_set_event.rb +13 -0
  40. data/lib/astronoby/events/rise_transit_set_events.rb +13 -0
  41. data/lib/astronoby/events/twilight_calculator.rb +166 -0
  42. data/lib/astronoby/events/twilight_event.rb +28 -0
  43. data/lib/astronoby/instant.rb +171 -0
  44. data/lib/astronoby/mean_obliquity.rb +23 -10
  45. data/lib/astronoby/nutation.rb +227 -42
  46. data/lib/astronoby/observer.rb +66 -1
  47. data/lib/astronoby/precession.rb +91 -17
  48. data/lib/astronoby/reference_frame.rb +49 -0
  49. data/lib/astronoby/reference_frames/apparent.rb +60 -0
  50. data/lib/astronoby/reference_frames/astrometric.rb +21 -0
  51. data/lib/astronoby/reference_frames/geometric.rb +20 -0
  52. data/lib/astronoby/reference_frames/mean_of_date.rb +38 -0
  53. data/lib/astronoby/reference_frames/topocentric.rb +82 -0
  54. data/lib/astronoby/time/greenwich_sidereal_time.rb +1 -1
  55. data/lib/astronoby/true_obliquity.rb +2 -1
  56. data/lib/astronoby/util/maths.rb +68 -49
  57. data/lib/astronoby/util/time.rb +466 -32
  58. data/lib/astronoby/vector.rb +36 -0
  59. data/lib/astronoby/velocity.rb +116 -0
  60. data/lib/astronoby/version.rb +1 -1
  61. data/lib/astronoby.rb +26 -5
  62. metadata +81 -18
  63. data/.tool-versions +0 -1
  64. data/lib/astronoby/astronomical_models/ephemeride_lunaire_parisienne.rb +0 -143
  65. data/lib/astronoby/events/observation_events.rb +0 -259
  66. data/lib/astronoby/events/rise_transit_set_iteration.rb +0 -215
  67. data/lib/astronoby/events/twilight_events.rb +0 -121
  68. data/lib/astronoby/util/astrodynamics.rb +0 -60
data/UPGRADING.md CHANGED
@@ -7,6 +7,244 @@ changes to it as long as a major version has not been released.
7
7
  If you are already using Astronoby and wish to follow the changes to its
8
8
  public API, please read the upgrading notes for each release.
9
9
 
10
+ ## Upgrading from 0.6.0 to 0.7.0
11
+
12
+ ## Signature change for `Sun` and `Moon`
13
+
14
+ Both classes are now initialized with the key arguments `ephem` and `instant`.
15
+ `ephem` comes from `Ephem.load` which provides the raw geometric data for Solar
16
+ System bodies. `instant` is an instance of `Instant`, the new concept for
17
+ representing an instant in time.
18
+
19
+ ```rb
20
+ Ephem::IO::Download.call(name: "de421.bsp", target: "tmp/de421.bsp")
21
+ ephem = Astronoby::Ephem.load("tmp/de421.bsp")
22
+
23
+ time = Time.utc(2025, 6, 19, 12, 0, 0)
24
+ instant = Astronoby::Instant.from_time(time)
25
+
26
+ sun = Astronoby::Sun.new(instant: instant, ephem: ephem)
27
+ ```
28
+
29
+ Learn more on [ephem].
30
+
31
+ [ephem]: https://github.com/rhannequin/astronoby/wiki/Ephem
32
+
33
+ ## Drop of methods for `Sun`
34
+
35
+ `Sun` doesn't expose the following class and instance methods anymore:
36
+ * `::equation_of_time` (replaced with `#equation_of_time`)
37
+ * `#epoch` (replaced with `#instant`)
38
+ * `#true_ecliptic_coordinates` (replaced with `#ecliptic` on reference frames)
39
+ * `#apparent_ecliptic_coordinates` (replaced with `#ecliptic` on reference frames)
40
+ * `#horizontal_coordinates` (replaced with `#horizontal` on the Topocentric reference frame)
41
+ * `#observation_events`
42
+ * `#twilight_events`
43
+ * `#earth_distance` (replaced with `#distance` on referential frames)
44
+ * `#angular_size` (replaced with `#angular_diameter` on referential frames)
45
+ * `#true_anomaly`
46
+ * `#mean_anomaly`
47
+ * `#longitude_at_perigee`
48
+ * `#orbital_eccentricity`
49
+
50
+ Learn more on [reference frames].
51
+
52
+ [reference frames]: https://github.com/rhannequin/astronoby/wiki/Reference-Frames
53
+
54
+ ## Drop of instance methods for `Moon`
55
+
56
+ `Moon` doesn't expose the following nstance methods anymore:
57
+ * `#apparent_ecliptic_coordinates` (replaced with `#ecliptic` on reference frames)
58
+ * `#apparent_equatorial_coordinates` (replaced with `#equatorial` on reference frames)
59
+ * `#horizontal_coordinates` (replaced with the Apparent and Topocentric reference frames)
60
+ * `#distance` (replaced with `#distance` on referential frames)
61
+ * `#mean_longitude`
62
+ * `#mean_elongation`
63
+ * `#mean_anomaly`
64
+ * `#argument_of_latitude`
65
+ * `#phase_angle`
66
+ * `#observation_events`
67
+
68
+ ## Signature change for `Aberration`
69
+
70
+ `Aberration` is now initialized with the key arguments `astrometric_position`
71
+ and `observer_velocity`. `astrometric_position` is a position vector
72
+ (`Astronoby::Vector<Astronoby::Distance>`) available from any referential frame
73
+ with the `#position` method.`observer_velocity` is a velocity vector
74
+ (`Astronoby::Vector<Astronoby::Distance>`) available from any referential frame
75
+ with the `#velocity` method.
76
+
77
+ `observer_velocity` is meant to be a geometric velocity, while
78
+ `astrometric_position` is meant to be an astrometric position.
79
+
80
+ ```rb
81
+ time = Time.utc(2025, 4, 1)
82
+ instant = Astronoby::Instant.from_time(time)
83
+ ephem = Astronoby::Ephem.load("de421.bsp")
84
+ earth = Astronoby::Earth.new(instant: instant, ephem: ephem)
85
+ mars = Astronoby::Mars.new(instant: instant, ephem: ephem)
86
+ earth_geometric_velocity = earth.geometric.velocity
87
+ mars_astrometric_position = mars.astrometric.position
88
+
89
+ aberration = Astronoby::Aberration.new(
90
+ astrometric_position: mars_astrometric_position,
91
+ observer_velocity: earth_geometric_velocity
92
+ )
93
+ ```
94
+
95
+ ## Signature change for `Angle#to_dms` and `Angle#to_hms`
96
+
97
+ `Angle#to_dms` and `Angle#to_hms` don't have arguments anymore. The angle value
98
+ is now taken from the object itself. This was a misbehavior in the previous
99
+ implementation.
100
+
101
+ ```rb
102
+ angle = Astronoby::Angle.from_degrees(45.0)
103
+ dms = angle.to_dms
104
+
105
+ dms.format
106
+ # => "+45° 0′ 0.0″"
107
+ ```
108
+
109
+ ## Signature change for `EquinoxSolstice`
110
+
111
+ `EquinoxSolstice.new` now takes an additional argument expected to be an ephem
112
+ (`Astronoby::Ephem`).
113
+
114
+ ## Signature change for `Nutation`
115
+
116
+ The expected `epoch` (`Astronoby::Epoch`) argument has been replaced by an
117
+ `instant` (`Astronoby::Instant`) key argument.
118
+
119
+ ## Drop of `Nutation::for_ecliptic_longitude` and `Nutation::for_obliquity_of_the_ecliptic`
120
+
121
+ `Nutation::for_ecliptic_longitude` and `Nutation::for_obliquity_of_the_ecliptic`
122
+ methods have been removed. The `Nutation` class now exposes the
123
+ `#nutation_in_longitude` and `#nutation_in_obliquity` instance methods which
124
+ both return an angle (`Astronoby::Angle`).
125
+
126
+ ## Signature change for `Precession`
127
+
128
+ The expected `coordinates` and `epoch` (`Astronoby::Epoch`) key arguments have
129
+ been replaced by an `instant` (`Astronoby::Instant`) key argument.
130
+
131
+ ## Drop of `Precession#for_equatorial_coordinates` and `Precession#precess`
132
+
133
+ `Precession#for_equatorial_coordinates` and `Precession#precess` methods have
134
+ been refactoed into class methods.
135
+
136
+ ## Drop of `Coordinates::Horizontal#to_equatorial`
137
+
138
+ `Coordinates::Horizontal#to_equatorial` has been removed as equatorial
139
+ coordinates are now available from the reference frames.
140
+
141
+ ## Drop of instance methods for `Coordinates::Ecliptic`
142
+
143
+ `Coordinates::Ecliptic#to_true_equatorial` and
144
+ `Coordinates::Ecliptic#to_apparent_equatorial` have been removed as
145
+ equatorial coordinates are now available from the reference frames.
146
+
147
+ ## Drop of `Coordinates::Equatorial#to_epoch`
148
+
149
+ `Coordinates::Equatorial#to_epoch` has been removed.
150
+
151
+ ## Drop of `Events::ObservationEvents`
152
+
153
+ `Events::ObservationEvents` has been removed. The rising, transit and setting
154
+ times can now be calculated using `RiseTransitSetCalculator`.
155
+
156
+ ```rb
157
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
158
+ observer = Astronoby::Observer.new(
159
+ latitude: Astronoby::Angle.from_degrees(48),
160
+ longitude: Astronoby::Angle.from_degrees(2)
161
+ )
162
+ date = Date.new(2025, 4, 24)
163
+
164
+ calculator = Astronoby::RiseTransitSetCalculator.new(
165
+ body: Astronoby::Sun,
166
+ observer: observer,
167
+ ephem: ephem
168
+ )
169
+
170
+ event = calculator.event_on(date)
171
+
172
+ event.rising_time
173
+ # => 2025-04-24 04:45:42 UTC
174
+
175
+ event.transit_time
176
+ # => 2025-04-24 11:50:04 UTC
177
+
178
+ event.setting_time
179
+ # => 2025-04-24 18:55:24 UTC
180
+ ```
181
+
182
+ ## Drop of `RiseTransitSetIteration`
183
+
184
+ `RiseTransitSetIteration` has been removed as it was only used by
185
+ `Events::ObservationEvents`.
186
+
187
+ ## Drop of `Events::TwilightEvents`
188
+
189
+ `Events::TwilightEvents` has been removed. The twilight times can now be
190
+ calculated using `TwilightCalculator`.
191
+
192
+ ```rb
193
+ ephem = Astronoby::Ephem.load("inpop19a.bsp")
194
+ observer = Astronoby::Observer.new(
195
+ latitude: Astronoby::Angle.from_degrees(48),
196
+ longitude: Astronoby::Angle.from_degrees(2)
197
+ )
198
+ date = Date.new(2025, 4, 24)
199
+
200
+ calculator = Astronoby::TwilightCalculator.new(
201
+ observer: observer,
202
+ ephem: ephem
203
+ )
204
+
205
+ event = calculator.event_on(date)
206
+ # Returns a Astronoby::TwilightEvent object
207
+
208
+ event.morning_astronomical_twilight_time
209
+ # => 2025-04-24 02:43:38 UTC
210
+
211
+ event.morning_nautical_twilight_time
212
+ # => 2025-04-24 03:30:45 UTC
213
+
214
+ event.morning_civil_twilight_time
215
+ # => 2025-04-24 04:12:38 UTC
216
+
217
+ event.evening_civil_twilight_time
218
+ # => 2025-04-24 19:27:34 UTC
219
+
220
+ event.evening_nautical_twilight_time
221
+ # => 2025-04-24 20:09:27 UTC
222
+
223
+ event.evening_astronomical_twilight_time
224
+ # => 2025-04-24 20:56:34 UTC
225
+ ```
226
+
227
+ ## Drop of `EphemerideLunaireParisienne`
228
+
229
+ `EphemerideLunaireParisienne` has been removed.
230
+
231
+ ## Drop of `Util::Astrodynamics`
232
+
233
+ `Util::Astrodynamics` has been removed.
234
+
235
+ ## Drop of `Util::Maths.interpolate` and `Util::Maths.normalize_angles_for_interpolation`
236
+
237
+ `Util::Maths.interpolate` and `Util::Maths.normalize_angles_for_interpolation`
238
+ have been removed.
239
+
240
+ ## Drop of `Constants::SECONDS_PER_DEGREE`
241
+
242
+ `Constants::SECONDS_PER_DEGREE` has been removed.
243
+
244
+ ## Upgrading from 0.5.0 to 0.6.0
245
+
246
+ No breaking changes.
247
+
10
248
  ## Upgrading from 0.4.0 to 0.5.0
11
249
 
12
250
  ### `Sun#horizontal_coordinates` method signature changed ([#69])
@@ -0,0 +1,131 @@
1
+ # Benchmark
2
+
3
+ This is a first attempt to benchmark the accuracy of the library. It is not
4
+ very scientific, but it gives a rough idea.
5
+
6
+ ## Method
7
+
8
+ The goal is to answer these two questions:
9
+ - Is the library accurate enough compared to a source of truth?
10
+ - Is the library accurate enough compared with other Ruby libraries?
11
+
12
+ The source of truth is the <abbr title="Institut de Mécanique
13
+ Céleste et de Calcul des Éphémérides">IMCCE</abbr>, a French public
14
+ institude attached to the Paris Observatory. Their ephemerides are used by
15
+ governements and public institutions in multiple European countries, their
16
+ precesion is among the highest in the world.
17
+ They also provide web services to easily access all their data. Many thanks
18
+ for providing such high accuracy data for free.
19
+
20
+ The other Ruby library is [sun_calc](https://github.com/fishbrain/sun_calc).
21
+
22
+ 474,336 combinations of dates, latitudes and longitudes have been used to
23
+ produce time predictions for the following events:
24
+ - sunrise
25
+ - sun's highest point
26
+ - sunset
27
+ - moonrise
28
+ - moon's highest point
29
+ - moonset
30
+
31
+ For each combination, we first find out which of SunCalc or Astronoby is the
32
+ closest to the IMCCE. Then we calculate the difference between Astronoby and
33
+ the IMCCE, to discover if the difference is larger than the defined
34
+ threshold of 5 minutes.
35
+
36
+ ## Results
37
+
38
+ The following output has been generated using what will be part of version 0.6.
39
+
40
+ ```
41
+ Unarchiving sun_calc.csv.zip...
42
+ Done unarchiving sun_calc.csv.zip.
43
+ Parsing sun_calc.csv...
44
+ Done parsing sun_calc.csv.
45
+ Unarchiving imcce.csv.zip...
46
+ Done unarchiving imcce.csv.zip.
47
+ Parsing imcce.csv...
48
+ Done parsing imcce.csv.
49
+ Comparing data...
50
+ 2024-01-01: Done.
51
+ 2024-01-02: Done.
52
+ ...
53
+ 2024-12-30: Done.
54
+ 2024-12-31: Done.
55
+ Done comparing data.
56
+
57
+
58
+ Sun rising time:
59
+ astronoby: 295395 (62.28%)
60
+ sun_calc: 99710 (21.02%)
61
+ n/a: 79231 (16.7%)
62
+
63
+ Sun transit time:
64
+ astronoby: 434495 (91.6%)
65
+ sun_calc: 39769 (8.38%)
66
+ n/a: 72 (0.02%)
67
+
68
+ Sun setting time:
69
+ astronoby: 358428 (75.56%)
70
+ n/a: 79231 (16.7%)
71
+ sun_calc: 36677 (7.73%)
72
+
73
+ Moon rising time:
74
+ astronoby: 290866 (61.32%)
75
+ n/a: 113815 (23.99%)
76
+ sun_calc: 69655 (14.68%)
77
+
78
+ Moon transit time:
79
+ astronoby: 341902 (72.08%)
80
+ n/a: 101916 (21.49%)
81
+ sun_calc: 30518 (6.43%)
82
+
83
+ Moon setting time:
84
+ astronoby: 327099 (68.96%)
85
+ n/a: 114308 (24.1%)
86
+ sun_calc: 32929 (6.94%)
87
+
88
+ Moon illuminated fraction:
89
+ astronoby: 474336 (100.0%)
90
+
91
+ Sun rising time too far:
92
+ false: 452887 (95.48%)
93
+ true: 21449 (4.52%)
94
+
95
+ Sun transit time too far:
96
+ false: 396617 (83.62%)
97
+ true: 77719 (16.38%)
98
+
99
+ Sun setting time too far:
100
+ false: 453208 (95.55%)
101
+ true: 21128 (4.45%)
102
+
103
+ Moon rising time too far:
104
+ false: 459044 (96.78%)
105
+ true: 15292 (3.22%)
106
+
107
+ Moon transit time too far:
108
+ false: 384516 (81.06%)
109
+ true: 89820 (18.94%)
110
+
111
+ Moon setting time too far:
112
+ false: 459222 (96.81%)
113
+ true: 15114 (3.19%)
114
+ ```
115
+
116
+ ## Conclusion
117
+
118
+ As we can see, Astronoby is more accurate than SunCalc in a vast majority of
119
+ cases. When it comes to the Moon's illuminated fraction, Astronoby is always
120
+ more accurate than SunCalc.
121
+
122
+ `n/a` values means that at least one of the three sources don't have a value
123
+ for the combination of date, latitude and longitude. This happens because
124
+ the Moon and the Sun cannot always rise, transit and set everywhere on Earth
125
+ every day of the year. Latitudes close to the poles are more likely to miss
126
+ data.
127
+
128
+ Astronoby can be considered "good enough" for more around 90% of the cases,
129
+ which means there is still work to do if we want to always be less than 5
130
+ minutes away from the what the IMCCE provides. We can notice that transit
131
+ times are those that experience the most significant differences.
@@ -0,0 +1,259 @@
1
+ require "astronoby"
2
+ require "csv"
3
+ require "zip"
4
+
5
+ class Source
6
+ NAMES = [
7
+ ASTRONOBY = "astronoby",
8
+ IMCCE = "imcce",
9
+ SUN_CALC = "sun_calc"
10
+ ].freeze
11
+
12
+ attr_accessor :name,
13
+ :sun_rising_time,
14
+ :sun_transit_time,
15
+ :sun_setting_time,
16
+ :moon_rising_time,
17
+ :moon_transit_time,
18
+ :moon_setting_time,
19
+ :moon_illuminated_fraction
20
+ end
21
+
22
+ class Comparison
23
+ SUN_CALC = "sun_calc"
24
+ ASTRONOBY = "astronoby"
25
+ NON_APPLICABLE = "n/a"
26
+
27
+ TOO_FAR_THRESHOLD = 60 * 5 # 5 minutes
28
+
29
+ attr_accessor :sources, :truth
30
+
31
+ def initialize
32
+ @sources = []
33
+ end
34
+
35
+ %i[
36
+ sun_rising_time
37
+ sun_transit_time
38
+ sun_setting_time
39
+ moon_rising_time
40
+ moon_transit_time
41
+ moon_setting_time
42
+ ].each do |attribute|
43
+ define_method(:"closest_#{attribute}") do
44
+ compare(attribute)
45
+ end
46
+
47
+ define_method(:"#{attribute}_too_far?") do
48
+ too_far?(attribute)
49
+ end
50
+ end
51
+
52
+ def closest_moon_illuminated_fraction
53
+ compare(:moon_illuminated_fraction)
54
+ end
55
+
56
+ private
57
+
58
+ def compare(attribute)
59
+ unless truth.public_send(attribute) && sources.all? { |source| source.public_send(attribute) }
60
+ return NON_APPLICABLE
61
+ end
62
+
63
+ closest_source = sources.min_by do |source|
64
+ (truth.public_send(attribute) - source.public_send(attribute)).abs
65
+ end
66
+
67
+ closest_source.name
68
+ end
69
+
70
+ def too_far?(attribute)
71
+ truth_attribute = truth.public_send(attribute)
72
+ astronoby_attribute = sources
73
+ .find { _1.name == Source::ASTRONOBY }
74
+ .public_send(attribute)
75
+
76
+ return false unless truth_attribute && astronoby_attribute
77
+
78
+ (truth_attribute - astronoby_attribute).abs > TOO_FAR_THRESHOLD
79
+ end
80
+ end
81
+
82
+ class Result
83
+ def initialize
84
+ @sun_rising_time = []
85
+ @sun_transit_time = []
86
+ @sun_setting_time = []
87
+ @moon_rising_time = []
88
+ @moon_transit_time = []
89
+ @moon_setting_time = []
90
+ @illuminated_fraction = []
91
+ @sun_rising_time_too_far = []
92
+ @sun_transit_time_too_far = []
93
+ @sun_setting_time_too_far = []
94
+ @moon_rising_time_too_far = []
95
+ @moon_transit_time_too_far = []
96
+ @moon_setting_time_too_far = []
97
+ end
98
+
99
+ def add_comparison(comparison)
100
+ @sun_rising_time << comparison.closest_sun_rising_time
101
+ @sun_transit_time << comparison.closest_sun_transit_time
102
+ @sun_setting_time << comparison.closest_sun_setting_time
103
+ @moon_rising_time << comparison.closest_moon_rising_time
104
+ @moon_transit_time << comparison.closest_moon_transit_time
105
+ @moon_setting_time << comparison.closest_moon_setting_time
106
+ @illuminated_fraction << comparison.closest_moon_illuminated_fraction
107
+ @sun_rising_time_too_far << comparison.sun_rising_time_too_far?
108
+ @sun_transit_time_too_far << comparison.sun_transit_time_too_far?
109
+ @sun_setting_time_too_far << comparison.sun_setting_time_too_far?
110
+ @moon_rising_time_too_far << comparison.moon_rising_time_too_far?
111
+ @moon_transit_time_too_far << comparison.moon_transit_time_too_far?
112
+ @moon_setting_time_too_far << comparison.moon_setting_time_too_far?
113
+ end
114
+
115
+ def display
116
+ puts "Sun rising time:"
117
+ tally(@sun_rising_time)
118
+ puts "Sun transit time:"
119
+ tally(@sun_transit_time)
120
+ puts "Sun setting time:"
121
+ tally(@sun_setting_time)
122
+ puts "Moon rising time:"
123
+ tally(@moon_rising_time)
124
+ puts "Moon transit time:"
125
+ tally(@moon_transit_time)
126
+ puts "Moon setting time:"
127
+ tally(@moon_setting_time)
128
+ puts "Moon illuminated fraction:"
129
+ tally(@illuminated_fraction)
130
+ puts "Sun rising time too far:"
131
+ tally(@sun_rising_time_too_far)
132
+ puts "Sun transit time too far:"
133
+ tally(@sun_transit_time_too_far)
134
+ puts "Sun setting time too far:"
135
+ tally(@sun_setting_time_too_far)
136
+ puts "Moon rising time too far:"
137
+ tally(@moon_rising_time_too_far)
138
+ puts "Moon transit time too far:"
139
+ tally(@moon_transit_time_too_far)
140
+ puts "Moon setting time too far:"
141
+ tally(@moon_setting_time_too_far)
142
+ end
143
+
144
+ private
145
+
146
+ def tally(data)
147
+ t = data.tally
148
+ t.sort_by { |_key, value| -value }.each do |key, value|
149
+ puts "#{key}: #{value} (#{(value.to_f / t.values.sum * 100).round(2)}%)"
150
+ end
151
+ puts "\n"
152
+ end
153
+ end
154
+
155
+ data = {}
156
+ result = Result.new
157
+
158
+ sun_calc_zip_file = File.join(File.dirname(__FILE__), "data/sun_calc.csv.zip")
159
+ imcce_zip_file = File.join(File.dirname(__FILE__), "data/imcce.csv.zip")
160
+
161
+ puts "Unarchiving sun_calc.csv.zip..."
162
+
163
+ Zip::File.open(sun_calc_zip_file) do |zip_file|
164
+ puts "Done unarchiving sun_calc.csv.zip."
165
+
166
+ csv_file = zip_file.find { |entry| entry.name.end_with?(".csv") }
167
+ break unless csv_file
168
+
169
+ puts "Parsing sun_calc.csv..."
170
+
171
+ csv_content = csv_file.get_input_stream.read
172
+ CSV.parse(csv_content, headers: true) do |row|
173
+ data[row["date"]] ||= {}
174
+ data[row["date"]][row["latitude"]] ||= {}
175
+ data[row["date"]][row["latitude"]][row["longitude"]] = Comparison.new.tap do |comparison|
176
+ source = Source.new.tap do |source|
177
+ source.name = Source::SUN_CALC
178
+ source.sun_rising_time = Time.new(row["sun_rising_time"]) if row["sun_rising_time"]
179
+ source.sun_transit_time = Time.new(row["sun_transit_time"]) if row["sun_transit_time"]
180
+ source.sun_setting_time = Time.new(row["sun_setting_time"]) if row["sun_setting_time"]
181
+ source.moon_rising_time = Time.new(row["moon_rising_time"]) if row["moon_rising_time"]
182
+ source.moon_transit_time = Time.new(row["moon_transit_time"]) if row["moon_transit_time"]
183
+ source.moon_setting_time = Time.new(row["moon_setting_time"]) if row["moon_setting_time"]
184
+ source.moon_illuminated_fraction = row["illuminated_fraction"].to_f
185
+ end
186
+ comparison.sources << source
187
+ end
188
+ end
189
+
190
+ puts "Done parsing sun_calc.csv."
191
+ end
192
+
193
+ puts "Unarchiving imcce.csv.zip..."
194
+
195
+ Zip::File.open(imcce_zip_file) do |zip_file|
196
+ puts "Done unarchiving imcce.csv.zip."
197
+
198
+ csv_file = zip_file.find { |entry| entry.name.end_with?(".csv") }
199
+ break unless csv_file
200
+
201
+ puts "Parsing imcce.csv..."
202
+
203
+ csv_content = csv_file.get_input_stream.read
204
+ CSV.parse(csv_content, headers: true) do |row|
205
+ comparison = data[row["date"]][row["latitude"]][row["longitude"]]
206
+ comparison.truth = Source.new.tap do |source|
207
+ source.name = Source::IMCCE
208
+ source.sun_rising_time = Time.new(row["sun_rising_time"] + " UTC") if row["sun_rising_time"]
209
+ source.sun_transit_time = Time.new(row["sun_transit_time"] + " UTC") if row["sun_transit_time"]
210
+ source.sun_setting_time = Time.new(row["sun_setting_time"] + " UTC") if row["sun_setting_time"]
211
+ source.moon_rising_time = Time.new(row["moon_rising_time"] + " UTC") if row["moon_rising_time"]
212
+ source.moon_transit_time = Time.new(row["moon_transit_time"] + " UTC") if row["moon_transit_time"]
213
+ source.moon_setting_time = Time.new(row["moon_setting_time"] + " UTC") if row["moon_setting_time"]
214
+ source.moon_illuminated_fraction = row["illuminated_fraction"].to_f
215
+ end
216
+ end
217
+
218
+ puts "Done parsing imcce.csv."
219
+ end
220
+
221
+ puts "Comparing data..."
222
+
223
+ data.each do |date, latitudes|
224
+ latitudes.each do |latitude, longitudes|
225
+ longitudes.each do |longitude, comparison|
226
+ noon = Time.new("#{date}T12:00:00Z")
227
+ observer = Astronoby::Observer.new(
228
+ latitude: Astronoby::Angle.from_degrees(latitude.to_i),
229
+ longitude: Astronoby::Angle.from_degrees(longitude.to_i)
230
+ )
231
+ sun = Astronoby::Sun.new(time: noon)
232
+ sun_observation_events = sun.observation_events(observer: observer)
233
+ moon = Astronoby::Moon.new(time: noon)
234
+ moon_observation_events = moon.observation_events(observer: observer)
235
+
236
+ source = Source.new.tap do |source|
237
+ source.name = Source::ASTRONOBY
238
+ source.sun_rising_time = sun_observation_events.rising_time
239
+ source.sun_transit_time = sun_observation_events.transit_time
240
+ source.sun_setting_time = sun_observation_events.setting_time
241
+ source.moon_rising_time = moon_observation_events.rising_time
242
+ source.moon_transit_time = moon_observation_events.transit_time
243
+ source.moon_setting_time = moon_observation_events.setting_time
244
+ source.moon_illuminated_fraction = moon.illuminated_fraction
245
+ end
246
+
247
+ comparison.sources << source
248
+ result.add_comparison(comparison)
249
+ end
250
+ end
251
+
252
+ puts "#{date}: Done."
253
+ end
254
+
255
+ puts "Done comparing data."
256
+ puts
257
+ puts
258
+
259
+ puts result.display
Binary file
Binary file