iers 0.0.1 → 0.1.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.
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IERS
4
+ module PolarMotion
5
+ # TIO locator rate in arcseconds per Julian century
6
+ # (IERS Conventions 2010, eq. 5.13)
7
+ S_PRIME_RATE = -0.000047
8
+
9
+ # @attr x [Float] pole x-coordinate in arcseconds
10
+ # @attr y [Float] pole y-coordinate in arcseconds
11
+ # @attr mjd [Float] Modified Julian Date of the query
12
+ # @attr data_quality [Symbol] +:observed+ or +:predicted+
13
+ Entry = ::Data.define(:x, :y, :mjd, :data_quality) do
14
+ include HasDate
15
+ include HasDataQuality
16
+
17
+ # Polar motion rotation matrix W per IERS Conventions 2010, Section 5.4.1:
18
+ # W = R3(-s') * R2(xp) * R1(yp)
19
+ #
20
+ # All elements use exact trigonometry — no small-angle approximation.
21
+ #
22
+ # @return [Array<Array<Float>>] 3x3 rotation matrix
23
+ def rotation_matrix
24
+ xp = x * TimeScale::ARCSEC_TO_RAD
25
+ yp = y * TimeScale::ARCSEC_TO_RAD
26
+ t = (mjd - TimeScale::MJD_J2000) / TimeScale::DAYS_PER_JULIAN_CENTURY
27
+ sp = S_PRIME_RATE * t * TimeScale::ARCSEC_TO_RAD
28
+
29
+ cx, sx = Math.cos(xp), Math.sin(xp)
30
+ cy, sy = Math.cos(yp), Math.sin(yp)
31
+ cs, ss = Math.cos(sp), Math.sin(sp)
32
+
33
+ [
34
+ [cx * cs, cx * ss, sx],
35
+ [sy * sx * cs - cy * ss, cy * cs + sy * sx * ss, -sy * cx],
36
+ [-sy * ss - cy * sx * cs, sy * cs - cy * sx * ss, cy * cx]
37
+ ]
38
+ end
39
+ end
40
+
41
+ extend EopParameter
42
+
43
+ module_function
44
+
45
+ # @param input [Time, Date, DateTime, nil]
46
+ # @param jd [Float, nil] Julian Date
47
+ # @param mjd [Float, nil] Modified Julian Date
48
+ # @param interpolation [Symbol, nil] +:lagrange+ or +:linear+
49
+ # @return [Entry]
50
+ # @raise [OutOfRangeError]
51
+ def at(input = nil, jd: nil, mjd: nil, interpolation: nil)
52
+ query_mjd, window, method = resolve(
53
+ input,
54
+ jd: jd,
55
+ mjd: mjd,
56
+ interpolation: interpolation
57
+ )
58
+
59
+ x = interpolate_field(window, query_mjd, method) { |e| best_pm(e, :x) }
60
+ y = interpolate_field(window, query_mjd, method) { |e| best_pm(e, :y) }
61
+
62
+ Entry.new(
63
+ x: x, y: y,
64
+ mjd: query_mjd,
65
+ data_quality: derive_quality(window, :pm_flag)
66
+ )
67
+ end
68
+
69
+ # @param input [Time, Date, DateTime, nil]
70
+ # @param jd [Float, nil] Julian Date
71
+ # @param mjd [Float, nil] Modified Julian Date
72
+ # @param interpolation [Symbol, nil] +:lagrange+ or +:linear+
73
+ # @return [Array<Array<Float>>] 3x3 polar motion rotation matrix
74
+ # @raise [OutOfRangeError]
75
+ def rotation_matrix_at(input = nil, jd: nil, mjd: nil, interpolation: nil)
76
+ at(input, jd: jd, mjd: mjd, interpolation: interpolation)
77
+ .rotation_matrix
78
+ end
79
+
80
+ # @param start_date [Date]
81
+ # @param end_date [Date]
82
+ # @return [Enumerator::Lazy<Entry>]
83
+ def between(start_date, end_date)
84
+ start_mjd = TimeScale.to_mjd(start_date)
85
+ end_mjd = TimeScale.to_mjd(end_date)
86
+ entries = Data.finals_entries
87
+
88
+ EopLookup
89
+ .range(entries, start_mjd, end_mjd)
90
+ .lazy
91
+ .map do |e|
92
+ Entry.new(
93
+ x: best_pm(e, :x),
94
+ y: best_pm(e, :y),
95
+ mjd: e.mjd,
96
+ data_quality: EopParameter::FLAG_TO_QUALITY.fetch(
97
+ e.pm_flag, :observed
98
+ )
99
+ )
100
+ end
101
+ end
102
+
103
+ def best_pm(entry, component)
104
+ case component
105
+ when :x
106
+ entry.bulletin_b_pm_x || entry.pm_x
107
+ when :y
108
+ entry.bulletin_b_pm_y || entry.pm_y
109
+ end
110
+ end
111
+
112
+ private_class_method :best_pm
113
+ end
114
+ end
data/lib/iers/tai.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IERS
4
+ module TAI
5
+ module_function
6
+
7
+ # Convert a UTC instant to TAI.
8
+ #
9
+ # @param input [Time, Date, DateTime, nil]
10
+ # @param jd [Float, nil] Julian Date
11
+ # @param mjd [Float, nil] Modified Julian Date
12
+ # @return [Float] Modified Julian Date in TAI scale
13
+ # @raise [OutOfRangeError]
14
+ def utc_to_tai(input = nil, jd: nil, mjd: nil)
15
+ utc_mjd = TimeScale.to_mjd(input, jd: jd, mjd: mjd)
16
+ tai_utc = LeapSecond.at(mjd: utc_mjd)
17
+ utc_mjd + tai_utc / TimeScale::SECONDS_PER_DAY
18
+ end
19
+
20
+ # Convert a TAI instant to UTC.
21
+ #
22
+ # Uses the TAI instant for an initial leap second lookup, then
23
+ # verifies the offset at the derived UTC. If TAI and UTC straddle
24
+ # a leap second boundary the first lookup may be off by one second;
25
+ # the verification step corrects this exactly.
26
+ #
27
+ # @param input [Time, Date, DateTime, nil]
28
+ # @param jd [Float, nil] Julian Date
29
+ # @param mjd [Float, nil] Modified Julian Date
30
+ # @return [Float] Modified Julian Date in UTC scale
31
+ # @raise [OutOfRangeError]
32
+ def tai_to_utc(input = nil, jd: nil, mjd: nil)
33
+ tai_mjd = TimeScale.to_mjd(input, jd: jd, mjd: mjd)
34
+
35
+ initial_offset = LeapSecond.at(mjd: tai_mjd)
36
+ utc_mjd = tai_mjd - initial_offset / TimeScale::SECONDS_PER_DAY
37
+
38
+ verified_offset = LeapSecond.at(mjd: utc_mjd)
39
+ return utc_mjd if verified_offset == initial_offset
40
+
41
+ tai_mjd - verified_offset / TimeScale::SECONDS_PER_DAY
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IERS
4
+ module TerrestrialRotation
5
+ module_function
6
+
7
+ # Compute R(ERA) × W, the rotation from ITRS to TIRS
8
+ # (IERS Conventions 2010, eq. 5.1), combining the Earth Rotation
9
+ # Angle and polar motion matrices.
10
+ #
11
+ # @param input [Time, Date, DateTime, nil]
12
+ # @param jd [Float, nil] Julian Date
13
+ # @param mjd [Float, nil] Modified Julian Date
14
+ # @param interpolation [Symbol, nil] +:lagrange+ or +:linear+
15
+ # @return [Array<Array<Float>>] 3×3 rotation matrix (row-major)
16
+ # @raise [OutOfRangeError]
17
+ def at(input = nil, jd: nil, mjd: nil, interpolation: nil)
18
+ query_mjd = TimeScale.to_mjd(input, jd: jd, mjd: mjd)
19
+ era = EarthRotationAngle.at(mjd: query_mjd, interpolation: interpolation)
20
+ w = PolarMotion.rotation_matrix_at(
21
+ mjd: query_mjd, interpolation: interpolation
22
+ )
23
+
24
+ multiply(r3_matrix(era), w)
25
+ end
26
+
27
+ def r3_matrix(angle)
28
+ c = Math.cos(angle)
29
+ s = Math.sin(angle)
30
+
31
+ [
32
+ [c, s, 0.0],
33
+ [-s, c, 0.0],
34
+ [0.0, 0.0, 1.0]
35
+ ]
36
+ end
37
+
38
+ def multiply(a, b)
39
+ Array.new(3) { |i|
40
+ Array.new(3) { |j|
41
+ a[i].zip(b.map { |r| r[j] }).sum { |x, y| x * y }
42
+ }
43
+ }
44
+ end
45
+
46
+ private_class_method :r3_matrix, :multiply
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module IERS
6
+ # @api private
7
+ module TimeScale
8
+ JD_MJD_OFFSET = 2_400_000.5
9
+ JD_J2000 = 2_451_545.0
10
+ MJD_J2000 = 51_544.5
11
+ DAYS_PER_JULIAN_CENTURY = 36_525.0
12
+ SECONDS_PER_DAY = 86_400.0
13
+ ARCSEC_TO_RAD = Math::PI / 648_000.0
14
+ TT_TAI = 32.184 # seconds
15
+
16
+ module_function
17
+
18
+ # @param mjd [Float] Modified Julian Date
19
+ # @return [Date]
20
+ def to_date(mjd)
21
+ Date.jd((mjd.floor + JD_MJD_OFFSET).ceil)
22
+ end
23
+
24
+ # @param input [Time, Date, DateTime, nil]
25
+ # @param jd [Float, nil] Julian Date
26
+ # @param mjd [Float, nil] Modified Julian Date
27
+ # @return [Float] Modified Julian Date
28
+ # @raise [ArgumentError] if no valid input is provided
29
+ def to_mjd(input = nil, jd: nil, mjd: nil)
30
+ if mjd
31
+ Float(mjd)
32
+ elsif jd
33
+ Float(jd) - JD_MJD_OFFSET
34
+ elsif input.is_a?(Time)
35
+ input.to_datetime.ajd.to_f - JD_MJD_OFFSET
36
+ elsif input.is_a?(Date)
37
+ input.ajd.to_f - JD_MJD_OFFSET
38
+ else
39
+ raise ArgumentError,
40
+ "Expected Time, Date, DateTime, jd: or mjd: keyword, " \
41
+ "got #{input.inspect}"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IERS
4
+ # @attr updated_files [Array<Symbol>]
5
+ # @attr errors [Hash{Symbol => Error}]
6
+ UpdateResult = Data.define(:updated_files, :errors) do
7
+ # @return [Boolean]
8
+ def success?
9
+ errors.empty?
10
+ end
11
+ end
12
+ end
data/lib/iers/ut1.rb ADDED
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IERS
4
+ module UT1
5
+ # @attr ut1_utc [Float] UT1−UTC in seconds
6
+ # @attr mjd [Float] Modified Julian Date of the query
7
+ # @attr data_quality [Symbol] +:observed+ or +:predicted+
8
+ Entry = ::Data.define(:ut1_utc, :mjd, :data_quality) do
9
+ include HasDate
10
+ include HasDataQuality
11
+ end
12
+
13
+ extend EopParameter
14
+
15
+ module_function
16
+
17
+ # @param input [Time, Date, DateTime, nil]
18
+ # @param jd [Float, nil] Julian Date
19
+ # @param mjd [Float, nil] Modified Julian Date
20
+ # @param interpolation [Symbol, nil] +:lagrange+ or +:linear+
21
+ # @return [Entry]
22
+ # @raise [OutOfRangeError]
23
+ def at(input = nil, jd: nil, mjd: nil, interpolation: nil)
24
+ query_mjd, window, method = resolve(
25
+ input,
26
+ jd: jd,
27
+ mjd: mjd,
28
+ interpolation: interpolation
29
+ )
30
+
31
+ ut1_utc = interpolate_ut1(window, query_mjd, method)
32
+
33
+ Entry.new(
34
+ ut1_utc: ut1_utc,
35
+ mjd: query_mjd,
36
+ data_quality: derive_quality(window, :ut1_flag)
37
+ )
38
+ end
39
+
40
+ # @param start_date [Date]
41
+ # @param end_date [Date]
42
+ # @return [Enumerator::Lazy<Entry>]
43
+ def between(start_date, end_date)
44
+ start_mjd = TimeScale.to_mjd(start_date)
45
+ end_mjd = TimeScale.to_mjd(end_date)
46
+ entries = Data.finals_entries
47
+
48
+ EopLookup
49
+ .range(entries, start_mjd, end_mjd)
50
+ .lazy
51
+ .map do |e|
52
+ Entry.new(
53
+ ut1_utc: best_ut1_utc(e),
54
+ mjd: e.mjd,
55
+ data_quality: EopParameter::FLAG_TO_QUALITY.fetch(e.ut1_flag, :observed)
56
+ )
57
+ end
58
+ end
59
+
60
+ def interpolate_ut1(window, query_mjd, method)
61
+ leap_entries = Data.leap_second_entries
62
+ tai_utc_at_query = tai_utc_for(leap_entries, query_mjd)
63
+
64
+ ut1_tai = interpolate_field(window, query_mjd, method) do |e|
65
+ best_ut1_utc(e) - tai_utc_for(leap_entries, e.mjd)
66
+ end
67
+
68
+ ut1_tai + tai_utc_at_query
69
+ end
70
+
71
+ def tai_utc_for(entries, mjd)
72
+ index = entries.bsearch_index { |e| e.mjd > mjd }
73
+ index.nil? ? entries.last.tai_utc : entries[index - 1].tai_utc
74
+ end
75
+
76
+ def best_ut1_utc(entry)
77
+ entry.bulletin_b_ut1_utc || entry.ut1_utc
78
+ end
79
+
80
+ private_class_method :interpolate_ut1,
81
+ :tai_utc_for,
82
+ :best_ut1_utc
83
+ end
84
+ end
data/lib/iers/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IERS
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/iers.rb CHANGED
@@ -1,6 +1,54 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "iers/version"
4
+ require_relative "iers/errors"
5
+ require_relative "iers/configuration"
6
+ require_relative "iers/update_result"
7
+ require_relative "iers/data_status"
8
+ require_relative "iers/downloader"
9
+ require_relative "iers/parsers"
10
+ require_relative "iers/data"
11
+ require_relative "iers/time_scale"
12
+ require_relative "iers/has_date"
13
+ require_relative "iers/has_data_quality"
14
+ require_relative "iers/interpolation"
15
+ require_relative "iers/eop_lookup"
16
+ require_relative "iers/eop_parameter"
17
+ require_relative "iers/leap_second"
18
+ require_relative "iers/tai"
19
+ require_relative "iers/ut1"
20
+ require_relative "iers/polar_motion"
21
+ require_relative "iers/celestial_pole_offset"
22
+ require_relative "iers/length_of_day"
23
+ require_relative "iers/delta_t"
24
+ require_relative "iers/earth_rotation_angle"
25
+ require_relative "iers/gmst"
26
+ require_relative "iers/terrestrial_rotation"
27
+ require_relative "iers/eop"
4
28
 
5
29
  module IERS
30
+ class << self
31
+ # @return [Configuration]
32
+ def configuration
33
+ @configuration ||= Configuration.new
34
+ end
35
+
36
+ # @yield [Configuration]
37
+ # @return [void]
38
+ def configure
39
+ yield configuration
40
+ end
41
+
42
+ # @return [void]
43
+ def reset_configuration!
44
+ @configuration = nil
45
+ Data.clear_loaded!
46
+ LeapSecond.clear_cached!
47
+ end
48
+
49
+ # @return [void]
50
+ def reset!
51
+ reset_configuration!
52
+ end
53
+ end
6
54
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: iers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rémy Hannequin
@@ -107,6 +107,34 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: webmock
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ - !ruby/object:Gem::Dependency
125
+ name: yard
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
110
138
  description: Access to Earth orientation parameters and time scale values from the
111
139
  International Earth Rotation and Reference Systems Service (IERS).
112
140
  email:
@@ -115,12 +143,41 @@ executables: []
115
143
  extensions: []
116
144
  extra_rdoc_files: []
117
145
  files:
146
+ - ".yardopts"
118
147
  - CHANGELOG.md
119
148
  - CODE_OF_CONDUCT.md
120
149
  - LICENSE.txt
121
150
  - README.md
122
151
  - Rakefile
152
+ - data/Leap_Second.dat
153
+ - data/finals2000A.all
123
154
  - lib/iers.rb
155
+ - lib/iers/celestial_pole_offset.rb
156
+ - lib/iers/configuration.rb
157
+ - lib/iers/data.rb
158
+ - lib/iers/data_status.rb
159
+ - lib/iers/delta_t.rb
160
+ - lib/iers/downloader.rb
161
+ - lib/iers/earth_rotation_angle.rb
162
+ - lib/iers/eop.rb
163
+ - lib/iers/eop_lookup.rb
164
+ - lib/iers/eop_parameter.rb
165
+ - lib/iers/errors.rb
166
+ - lib/iers/gmst.rb
167
+ - lib/iers/has_data_quality.rb
168
+ - lib/iers/has_date.rb
169
+ - lib/iers/interpolation.rb
170
+ - lib/iers/leap_second.rb
171
+ - lib/iers/length_of_day.rb
172
+ - lib/iers/parsers.rb
173
+ - lib/iers/parsers/finals.rb
174
+ - lib/iers/parsers/leap_second.rb
175
+ - lib/iers/polar_motion.rb
176
+ - lib/iers/tai.rb
177
+ - lib/iers/terrestrial_rotation.rb
178
+ - lib/iers/time_scale.rb
179
+ - lib/iers/update_result.rb
180
+ - lib/iers/ut1.rb
124
181
  - lib/iers/version.rb
125
182
  homepage: https://github.com/rhannequin/iers
126
183
  licenses: