voruby 1.0.1
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.
- data/LICENSE +339 -0
- data/REQUIREMENTS +4 -0
- data/Rakefile.rb +296 -0
- data/lib/voruby/adql/adql.rb +2735 -0
- data/lib/voruby/adql/ext.rb +15 -0
- data/lib/voruby/adql/loader.rb +5 -0
- data/lib/voruby/adql/operations.rb +54 -0
- data/lib/voruby/adql/parser.rb +160 -0
- data/lib/voruby/adql/transforms.rb +573 -0
- data/lib/voruby/ext.rb +17 -0
- data/lib/voruby/loader.rb +4 -0
- data/lib/voruby/misc/propertyfile.rb +36 -0
- data/lib/voruby/plastic/applications.rb +174 -0
- data/lib/voruby/plastic/constants.rb +30 -0
- data/lib/voruby/plastic/loader.rb +10 -0
- data/lib/voruby/plastic/plastic.rb +1 -0
- data/lib/voruby/resources/conesearch/conesearch.rb +9 -0
- data/lib/voruby/resources/conesearch/conesearch_v0_2.rb +55 -0
- data/lib/voruby/resources/conesearch/conesearch_v0_3.rb +50 -0
- data/lib/voruby/resources/conesearch/conesearch_v1_0.rb +72 -0
- data/lib/voruby/resources/conesearch/loader.rb +4 -0
- data/lib/voruby/resources/loader.rb +50 -0
- data/lib/voruby/resources/nodes.rb +190 -0
- data/lib/voruby/resources/openskynode/loader.rb +4 -0
- data/lib/voruby/resources/openskynode/openskynode.rb +9 -0
- data/lib/voruby/resources/openskynode/openskynode_v0_1.rb +54 -0
- data/lib/voruby/resources/sia/loader.rb +5 -0
- data/lib/voruby/resources/sia/sia.rb +9 -0
- data/lib/voruby/resources/sia/sia_v0_6.rb +90 -0
- data/lib/voruby/resources/sia/sia_v0_7.rb +89 -0
- data/lib/voruby/resources/sia/sia_v1_0.rb +122 -0
- data/lib/voruby/resources/stsci.rb +59 -0
- data/lib/voruby/resources/vodataservice/coverage_v0_2.rb +195 -0
- data/lib/voruby/resources/vodataservice/coverage_v0_3.rb +158 -0
- data/lib/voruby/resources/vodataservice/loader.rb +5 -0
- data/lib/voruby/resources/vodataservice/vodataservice.rb +9 -0
- data/lib/voruby/resources/vodataservice/vodataservice_v0_4.rb +189 -0
- data/lib/voruby/resources/vodataservice/vodataservice_v0_5.rb +163 -0
- data/lib/voruby/resources/vodataservice/vodataservice_v1_0.rb +221 -0
- data/lib/voruby/resources/voregistry/loader.rb +4 -0
- data/lib/voruby/resources/voregistry/voregistry.rb +9 -0
- data/lib/voruby/resources/voregistry/voregistry_v0_2.rb +40 -0
- data/lib/voruby/resources/voregistry/voregistry_v0_3.rb +30 -0
- data/lib/voruby/resources/voregistry/voregistry_v1_0.rb +86 -0
- data/lib/voruby/resources/voresource/loader.rb +17 -0
- data/lib/voruby/resources/voresource/voresource.rb +9 -0
- data/lib/voruby/resources/voresource/voresource_v0_10.rb +322 -0
- data/lib/voruby/resources/voresource/voresource_v0_9.rb +405 -0
- data/lib/voruby/resources/voresource/voresource_v1_0.rb +230 -0
- data/lib/voruby/services/ext.rb +11 -0
- data/lib/voruby/services/gestalt/footprint.rb +95 -0
- data/lib/voruby/services/gestalt/wcs_fixer.rb +105 -0
- data/lib/voruby/services/gestalt/wesix.rb +155 -0
- data/lib/voruby/services/loader.rb +7 -0
- data/lib/voruby/services/registry/registry.rb +53 -0
- data/lib/voruby/services/resolver/resolver.rb +35 -0
- data/lib/voruby/sesame/loader.rb +6 -0
- data/lib/voruby/sesame/sesame_v1_0.rb +64 -0
- data/lib/voruby/simple/loader.rb +6 -0
- data/lib/voruby/simple/parameters.rb +196 -0
- data/lib/voruby/simple/sap.rb +446 -0
- data/lib/voruby/spacetime/loader.rb +3 -0
- data/lib/voruby/spacetime/spacetime.rb +607 -0
- data/lib/voruby/stc/coords_v1_20.rb +900 -0
- data/lib/voruby/stc/loader.rb +55 -0
- data/lib/voruby/stc/region_v1_20.rb +274 -0
- data/lib/voruby/stc/stc_v1_20.rb +1196 -0
- data/lib/voruby/util.rb +27 -0
- data/lib/voruby/voevent/loader.rb +7 -0
- data/lib/voruby/voevent/voevent_v1_0.rb +213 -0
- data/lib/voruby/voevent/voevent_v1_1.rb +196 -0
- data/lib/voruby/votables/chandra.rb +410 -0
- data/lib/voruby/votables/cnoc.rb +393 -0
- data/lib/voruby/votables/data.rb +179 -0
- data/lib/voruby/votables/galex.rb +390 -0
- data/lib/voruby/votables/hst.rb +391 -0
- data/lib/voruby/votables/int.rb +391 -0
- data/lib/voruby/votables/libxml_parser.rb +411 -0
- data/lib/voruby/votables/libxml_votable.rb +67 -0
- data/lib/voruby/votables/loader.rb +10 -0
- data/lib/voruby/votables/meta.rb +763 -0
- data/lib/voruby/votables/misc.rb +51 -0
- data/lib/voruby/votables/nsa.rb +393 -0
- data/lib/voruby/votables/nsar3.rb +410 -0
- data/lib/voruby/votables/rexml_parser.rb +408 -0
- data/lib/voruby/votables/rexml_votable.rb +67 -0
- data/lib/voruby/votables/sdss.rb +393 -0
- data/lib/voruby/votables/transforms.rb +388 -0
- data/lib/voruby/votables/tree.rb +45 -0
- data/lib/voruby/votables/types.rb +391 -0
- data/lib/voruby/votables/votable.rb +630 -0
- data/lib/voruby/votables/xmm.rb +394 -0
- data/test/adql/test1.adql +49 -0
- data/test/adql/test2.adql +51 -0
- data/test/adql/test3.adql +81 -0
- data/test/adql/test4.adql +53 -0
- data/test/adql/test5.adql +55 -0
- data/test/adql/test6.adql +18 -0
- data/test/adql/test7.adql +48 -0
- data/test/adql/unittest.rb +1672 -0
- data/test/plastic/test.rb +44 -0
- data/test/plastic/test.vot +5385 -0
- data/test/plastic/unittest.rb +66 -0
- data/test/resources/conesearch/conesearch_v0_3.xml +31 -0
- data/test/resources/conesearch/conesearch_v1_0.xml +86 -0
- data/test/resources/conesearch/unittest_v0_3.rb +22 -0
- data/test/resources/conesearch/unittest_v1_0.rb +24 -0
- data/test/resources/openskynode/open_sky_node_v0_1.xml +32 -0
- data/test/resources/openskynode/unittest_v0_1.rb +31 -0
- data/test/resources/sia/simple_image_access_v0_7.xml +36 -0
- data/test/resources/sia/simple_image_access_v1_0.xml +122 -0
- data/test/resources/sia/unittest_v0_7.rb +24 -0
- data/test/resources/sia/unittest_v1_0.rb +29 -0
- data/test/resources/stsci.xml +336 -0
- data/test/resources/unittest_stsci.rb +25 -0
- data/test/resources/vodataservice/catalog_service_resource_v1_0.xml +128 -0
- data/test/resources/vodataservice/data_collection_resource_v0_5.xml +54 -0
- data/test/resources/vodataservice/data_collection_resource_v1_0.xml +117 -0
- data/test/resources/vodataservice/data_service_resource_v1_0.xml +115 -0
- data/test/resources/vodataservice/sky_service_resource_v0_10.xml +45 -0
- data/test/resources/vodataservice/table_service_resource_v1_0.xml +122 -0
- data/test/resources/vodataservice/tabular_sky_service_resource_v0_10.xml +60 -0
- data/test/resources/vodataservice/unittest_v0_5.rb +126 -0
- data/test/resources/vodataservice/unittest_v1_0.rb +151 -0
- data/test/resources/voregistry/authority_resource_v0_3.xml +20 -0
- data/test/resources/voregistry/authority_resource_v1_0.xml +82 -0
- data/test/resources/voregistry/registry_service_v0_3.xml +20 -0
- data/test/resources/voregistry/registry_service_v1_0.xml +107 -0
- data/test/resources/voregistry/unittest_v0_3.rb +31 -0
- data/test/resources/voregistry/unittest_v1_0.rb +34 -0
- data/test/resources/voresource/organisation_resource_v1_0.xml +90 -0
- data/test/resources/voresource/resource_organisation_v0_10.xml +22 -0
- data/test/resources/voresource/resource_service_v0_10.xml +19 -0
- data/test/resources/voresource/resource_v0_10.xml +19 -0
- data/test/resources/voresource/resource_v1_0.xml +79 -0
- data/test/resources/voresource/service_resource_v1_0.xml +91 -0
- data/test/resources/voresource/unittest_v0_10.rb +61 -0
- data/test/resources/voresource/unittest_v0_9.rb +4 -0
- data/test/resources/voresource/unittest_v1_0.rb +190 -0
- data/test/services/gestalt/unittest.rb +74 -0
- data/test/services/registry/unittest.rb +34 -0
- data/test/services/resolver/unittest.rb +38 -0
- data/test/simple/unittest.rb +46 -0
- data/test/spacetime/unittest.rb +39 -0
- data/test/stc/catalog_entry_location_v1_20.xml +112 -0
- data/test/stc/obs_data_location_v1_20.xml +108 -0
- data/test/stc/search_location_v1_20.xml +54 -0
- data/test/stc/stc_resource_profile_v1_20.xml +60 -0
- data/test/stc/unittest_v1_20.rb +620 -0
- data/test/voevent/unittest_v1_0.rb +79 -0
- data/test/voevent/unittest_v1_1.rb +70 -0
- data/test/voevent/voevent_v1_0.xml +96 -0
- data/test/voevent/voevent_v1_1.xml +76 -0
- data/test/votables/test.vot +366 -0
- data/test/votables/unittest.rb +54 -0
- metadata +256 -0
@@ -0,0 +1,607 @@
|
|
1
|
+
require 'voruby/spacetime/loader'
|
2
|
+
|
3
|
+
module VORuby
|
4
|
+
|
5
|
+
module SpaceTime
|
6
|
+
|
7
|
+
class SpatialPosition
|
8
|
+
attr_accessor :axis1, :axis2, :axis3
|
9
|
+
|
10
|
+
def initialize(x, y, z)
|
11
|
+
self.axis1 = x
|
12
|
+
self.axis2 = y
|
13
|
+
self.axis3 = z
|
14
|
+
end
|
15
|
+
end
|
16
|
+
class TemporalPosition
|
17
|
+
attr_accessor :time
|
18
|
+
|
19
|
+
def initialize(t)
|
20
|
+
self.time = t
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Event
|
25
|
+
attr_accessor :spatial_position, :temporal_position
|
26
|
+
|
27
|
+
# Create a space-time even with the given spatial and temporal parts.
|
28
|
+
def initialize(spatial, temporal)
|
29
|
+
raise Exception::NotASpatialPosition.new(spatial) if !spatial.is_a?(SpatialPosition)
|
30
|
+
raise Exception::NotATemporalPosition.new(temporal) if !temporal.is_a?(TemporalPosition)
|
31
|
+
|
32
|
+
self.spatial_position = spatial
|
33
|
+
self.temporal_position = temporal
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
"#{self.spatial_position}, #{self.temporal_position}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Exception
|
42
|
+
class NotASpatialPosition < RuntimeError
|
43
|
+
def initialize(value)
|
44
|
+
super("#{value} is not a SpatialPosition")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class NotATemporalPosition < RuntimeError
|
49
|
+
def initialize(value)
|
50
|
+
super("#{value} is not a TemporalPosition")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
module CoordinateSystems
|
58
|
+
|
59
|
+
class CoordinateOutOfRangeException < RuntimeError
|
60
|
+
def initialize(coord_name, value, start_range, end_range)
|
61
|
+
super("#{coord_name} of #{value} out of allowed range. Must be between #{start_range} and #{end_range}.")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class NotACoordinateException < RuntimeError
|
66
|
+
def initialize(value, intended_name)
|
67
|
+
super("'#{value}' (#{value.class}) is not (or can't be converted to) a #{intended_name} object.")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class NotATimeException < NotACoordinateException
|
72
|
+
def initialize(value)
|
73
|
+
super(value, 'Time')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class InvalidTimeOffsetException < RuntimeError
|
78
|
+
def initialize(value)
|
79
|
+
super("Offset '#{value}' not recognized. Try a number with s, m, h or d appended.")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class StandardTime < SpaceTime::TemporalPosition
|
84
|
+
attr_reader :value
|
85
|
+
|
86
|
+
# Create a StandardTime object from Ruby's core Time object.
|
87
|
+
def initialize(time)
|
88
|
+
self.value = time
|
89
|
+
|
90
|
+
super(self.value)
|
91
|
+
end
|
92
|
+
|
93
|
+
def value=(time)
|
94
|
+
raise NotATimeException.new(time) if !time.is_a?(Time)
|
95
|
+
@value = time
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns an ISO 8601 compliant string representing the UTC time.
|
99
|
+
def to_utc_s
|
100
|
+
self.value.getutc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns an ISO 8601 compliant string representing the local time.
|
104
|
+
def to_local_s
|
105
|
+
utc_offset_hrs = self.value.getlocal.utc_offset / (60.0 * 60.0)
|
106
|
+
utc_offset_min = ((utc_offset_hrs - utc_offset_hrs.floor) * 60.0).to_i
|
107
|
+
utc_offset = sprintf("%+03d:%02d", utc_offset_hrs.floor, utc_offset_min)
|
108
|
+
|
109
|
+
self.value.getlocal.strftime("%Y-%m-%dT%H:%M:%S#{utc_offset}")
|
110
|
+
end
|
111
|
+
|
112
|
+
def extract_time(time_s)
|
113
|
+
matches = time_s.match(/^(\d+(\.\d+)?)([smhd])?$/)
|
114
|
+
raise InvalidTimeOffsetException.new(time_s) if !matches
|
115
|
+
|
116
|
+
return calculate_time(matches[1].to_f, matches[3])
|
117
|
+
end
|
118
|
+
|
119
|
+
def calculate_time(value, unit)
|
120
|
+
case unit
|
121
|
+
when 's' then return value
|
122
|
+
when 'm' then return value * 60.0
|
123
|
+
when 'h' then return value * 60.0 * 60.0
|
124
|
+
when 'd' then return value * 24.0 * 60.0 * 60.0
|
125
|
+
else return value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a StandardTime object projected forward the specified
|
130
|
+
# amount of time. Input maybe be:
|
131
|
+
# 1) a decimal number of seconds
|
132
|
+
# 2) a string with a decimal number followed by a modifier specifying the unit.
|
133
|
+
# The unit may be s = seconds, m = minutes, h = hours, d = days
|
134
|
+
# i.e. 12.3h for 12.3 hours
|
135
|
+
def +(time_s)
|
136
|
+
new_time = nil
|
137
|
+
(time_s.kind_of?(Numeric))? new_time = self.value + time_s: new_time = self.value + extract_time(time_s)
|
138
|
+
return StandardTime.new(new_time)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Add the specified number of seconds to the time.
|
142
|
+
def increment(seconds)
|
143
|
+
self.value = (self + seconds).value
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a StandardTime object projected backward the specified
|
147
|
+
# amount of time. Input maybe be:
|
148
|
+
# 1) a decimal number of seconds
|
149
|
+
# 2) a string with a decimal number followed by a modifier specifying the unit.
|
150
|
+
# The unit may be s = seconds, m = minutes, h = hours, d = days
|
151
|
+
# i.e. '12.3h' for 12.3 hours
|
152
|
+
def -(time_s)
|
153
|
+
new_time = nil
|
154
|
+
(time_s.kind_of?(Numeric))? new_time = self.value + time_s: new_time = self.value - extract_time(time_s)
|
155
|
+
return StandardTime.new(new_time)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Subtract the specified number of seconds to the time.
|
159
|
+
def decrement(seconds)
|
160
|
+
self.value = (self - seconds).value
|
161
|
+
end
|
162
|
+
|
163
|
+
# Compare two StandardTimes.
|
164
|
+
# if timeA before timeB, returns -1
|
165
|
+
# if timeA == timeB, returns 0
|
166
|
+
# if timeA after timeB, returns 1
|
167
|
+
def <=>(time)
|
168
|
+
self.value <=> time.value
|
169
|
+
end
|
170
|
+
|
171
|
+
def ==(time)
|
172
|
+
self.value.eql?(time.value)
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_s
|
176
|
+
self.to_utc_s
|
177
|
+
end
|
178
|
+
|
179
|
+
private :extract_time, :calculate_time
|
180
|
+
end
|
181
|
+
|
182
|
+
module Equatorial
|
183
|
+
|
184
|
+
module Exception
|
185
|
+
class RAOutOfRange < CoordinateOutOfRangeException
|
186
|
+
def initialize(value)
|
187
|
+
super("Right ascension", value, 0, 360)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
class DecOutOfRange < CoordinateOutOfRangeException
|
192
|
+
def initialize(value)
|
193
|
+
super("Declination", value, -90, 90)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class InvalidSexigesimalFormat < RuntimeError
|
198
|
+
def initialize(value)
|
199
|
+
super("Sexigesimal value of #{value} in a format that is not understood. Try NN:NN:NN.NN.")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class NotARightAscension < NotACoordinateException
|
204
|
+
def initialize(value)
|
205
|
+
super(value, 'RightAscension')
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class NotADeclination < NotACoordinateException
|
210
|
+
def initialize(value)
|
211
|
+
super(value, 'Declination')
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Returns true if the provided string looks like it's
|
217
|
+
# in standard sexigesimal format.
|
218
|
+
def self.looks_like_sexigesimal?(sexig, sep='(:|(\s)+)')
|
219
|
+
return true if sexig.match(/^[+-]?\d+#{sep}\d+#{sep}\d+/)
|
220
|
+
return false
|
221
|
+
end
|
222
|
+
|
223
|
+
# Returns true if the provided string looks like it's
|
224
|
+
# in standard decimal format.
|
225
|
+
def self.looks_like_decimal?(decimal)
|
226
|
+
return true if decimal.to_s.match(/^[+-]?\d+(\.\d*)?$/)
|
227
|
+
return false
|
228
|
+
end
|
229
|
+
|
230
|
+
# Default is to J2000.0 (i.e. equinox2=2000.0, system=:fk5)
|
231
|
+
def self.precession_matrix(equinox1, equinox2=2000.0, system=:fk5)
|
232
|
+
a, b, c = (system == :fk4)? Equatorial::fk4_rotation_angles(equinox1, equinox2): Equatorial::fk5_rotation_angles(equinox1, equinox2)
|
233
|
+
|
234
|
+
sina = Math.sin(a)
|
235
|
+
sinb = Math.sin(b)
|
236
|
+
sinc = Math.sin(c)
|
237
|
+
|
238
|
+
cosa = Math.cos(a)
|
239
|
+
cosb = Math.cos(b)
|
240
|
+
cosc = Math.cos(c)
|
241
|
+
|
242
|
+
Matrix.rows([
|
243
|
+
[cosa*cosb*cosc-sina*sinb, sina*cosb+cosa*sinb*cosc, cosa*sinc],
|
244
|
+
[-cosa*sinb-sina*cosb*cosc, cosa*cosb-sina*sinb*cosc, -sina*sinc],
|
245
|
+
[-cosb*sinc, -sinb*sinc, cosc]
|
246
|
+
])
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.fk5_rotation_angles(equinox1, equinox2=2000.0)
|
250
|
+
deg_to_rad = Math::PI / 180.0
|
251
|
+
sec_to_rad = deg_to_rad / 3600.0
|
252
|
+
t = 0.001 * (equinox2 - equinox1)
|
253
|
+
st = 0.001 * (equinox1 - 2000.0)
|
254
|
+
|
255
|
+
a = sec_to_rad * t *
|
256
|
+
(23062.181 +
|
257
|
+
st * (139.656 + 0.0139 * st) +
|
258
|
+
t * (30.188 - 0.344 * st + 17.998))
|
259
|
+
b = sec_to_rad * t * t * (79.280 + 0.410 * st + 0.205 * t) + a
|
260
|
+
c = sec_to_rad * t *
|
261
|
+
(20043.109 -
|
262
|
+
st * (85.33 + 0.217 * st) +
|
263
|
+
t * (-42.665 - 0.217 * st - 41.833 * t))
|
264
|
+
|
265
|
+
return [a, b, c]
|
266
|
+
end
|
267
|
+
|
268
|
+
def self.fk4_rotation_angles(equinox1, equinox2=2000.0)
|
269
|
+
deg_to_rad = Math::PI / 180.0
|
270
|
+
sec_to_rad = deg_to_rad / 3600.0
|
271
|
+
t = 0.001 * (equinox2 - equinox1)
|
272
|
+
st = 0.001 * (equinox1 - 1900.0)
|
273
|
+
|
274
|
+
a = sec_to_rad * t *
|
275
|
+
(23042.53 +
|
276
|
+
st * (139.75 + 0.06 * st) +
|
277
|
+
t * (30.23 - 0.27 * st + 18.0 * t))
|
278
|
+
b = sec_to_rad * t * t * (79.27 + 0.66 * st + 0.32 * t) + a
|
279
|
+
c = sec_to_rad * t *
|
280
|
+
(20046.85 -
|
281
|
+
st * (85.33 + 0.37 * st) +
|
282
|
+
t * (-42.67 - 0.37 * st - 41.8 * t))
|
283
|
+
|
284
|
+
return [a, b, c]
|
285
|
+
end
|
286
|
+
|
287
|
+
class RightAscension
|
288
|
+
attr_reader :value
|
289
|
+
|
290
|
+
MIN_ALLOWED_VALUE = 0.0
|
291
|
+
MAX_ALLOWED_VALUE = 360.0
|
292
|
+
|
293
|
+
# Create a RightAscenson object.
|
294
|
+
def initialize(ra)
|
295
|
+
self.value = ra
|
296
|
+
end
|
297
|
+
|
298
|
+
def value=(ra)
|
299
|
+
if ra.is_a?(Numeric) or Equatorial::looks_like_decimal?(ra)
|
300
|
+
@value = ra.to_f
|
301
|
+
elsif ra.is_a?(String)
|
302
|
+
if Equatorial::looks_like_sexigesimal?(ra)
|
303
|
+
@value = RightAscension.from_sexigesimal(ra).value
|
304
|
+
else
|
305
|
+
raise Exception::InvalidSexigesimalFormat.new(ra)
|
306
|
+
end
|
307
|
+
else
|
308
|
+
raise Exception::NotARightAscension.new(ra)
|
309
|
+
end
|
310
|
+
|
311
|
+
raise Exception::RAOutOfRange.new(ra) if !RightAscension::in_range?(@value)
|
312
|
+
end
|
313
|
+
|
314
|
+
# Is the supplied number in the correct range for a right ascension
|
315
|
+
# (i.e between 0 and 360 degrees)?
|
316
|
+
def self.in_range?(ra)
|
317
|
+
(MIN_ALLOWED_VALUE..MAX_ALLOWED_VALUE) === ra
|
318
|
+
end
|
319
|
+
|
320
|
+
# Create a RightAscension object from a string in sexigesimal format.
|
321
|
+
# Numerical values may be separated by a space or colon (but not both).
|
322
|
+
def self.from_sexigesimal(ra)
|
323
|
+
raise Exception::InvalidSexigesimalFormat.new(ra) if !Equatorial::looks_like_sexigesimal?(ra)
|
324
|
+
|
325
|
+
hours_s, minutes_s, seconds_s = ra.split(/:|\s+/)
|
326
|
+
|
327
|
+
hours = hours_s.to_f
|
328
|
+
minutes = minutes_s.to_f
|
329
|
+
seconds = seconds_s.to_f
|
330
|
+
|
331
|
+
ra_degrees = 15.0 * (hours + (minutes + seconds.to_f / 60.0) / 60.0)
|
332
|
+
return RightAscension.new(ra_degrees)
|
333
|
+
end
|
334
|
+
|
335
|
+
# Return the value of the ra as a string in sexigesimal format.
|
336
|
+
def to_sexigesimal(sep=':')
|
337
|
+
rah = (self.value / 15.0).to_i()
|
338
|
+
ram = ((self.value - 15.0 * rah) * (60.0 / 15.0)).to_i()
|
339
|
+
ras = self.value - (rah * 15.0) - (ram / (60.0 / 15.0))
|
340
|
+
ras = ras * (60.0 * 60.0 / 15.0)
|
341
|
+
|
342
|
+
return sprintf("%02u#{sep}%02u#{sep}%0.2f", rah, ram, ras)
|
343
|
+
end
|
344
|
+
|
345
|
+
# Returns a RightAscension object projected forward the specified
|
346
|
+
# number of degrees.
|
347
|
+
def +(degrees)
|
348
|
+
new_degrees = self.value + degrees
|
349
|
+
new_degrees = new_degrees - MAX_ALLOWED_VALUE if new_degrees > MAX_ALLOWED_VALUE
|
350
|
+
|
351
|
+
RightAscension.new(new_degrees)
|
352
|
+
end
|
353
|
+
|
354
|
+
# Add the specified number of degrees to the ra.
|
355
|
+
def increment(degrees)
|
356
|
+
new_degrees = self + degrees
|
357
|
+
self.value = new_degrees.value
|
358
|
+
end
|
359
|
+
|
360
|
+
# Returns a RightAscension object projected backward the specified
|
361
|
+
# number of degrees.
|
362
|
+
def -(degrees)
|
363
|
+
new_degrees = self.value - degrees
|
364
|
+
new_degrees = MAX_ALLOWED_VALUE + new_degrees if new_degrees < MIN_ALLOWED_VALUE
|
365
|
+
|
366
|
+
RightAscension.new(new_degrees)
|
367
|
+
end
|
368
|
+
|
369
|
+
# Subtract the specified number of degrees from the ra.
|
370
|
+
def decrement(degrees)
|
371
|
+
new_degrees = self - degrees
|
372
|
+
self.value = new_degrees.value
|
373
|
+
end
|
374
|
+
|
375
|
+
def <=>(ra)
|
376
|
+
self.value <=> ra.value
|
377
|
+
end
|
378
|
+
|
379
|
+
def ==(ra)
|
380
|
+
self.value == ra.value
|
381
|
+
end
|
382
|
+
|
383
|
+
def to_s
|
384
|
+
"#{self.to_sexigesimal}"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
class Declination
|
389
|
+
attr_reader :value
|
390
|
+
|
391
|
+
MIN_ALLOWED_VALUE = -90.0
|
392
|
+
MAX_ALLOWED_VALUE = 90.0
|
393
|
+
|
394
|
+
# Create a Declination object from a decimal number.
|
395
|
+
def initialize(dec)
|
396
|
+
self.value = dec
|
397
|
+
end
|
398
|
+
|
399
|
+
def value=(dec)
|
400
|
+
if dec.is_a?(Numeric) or Equatorial::looks_like_decimal?(dec)
|
401
|
+
@value = dec.to_f
|
402
|
+
elsif dec.is_a?(String)
|
403
|
+
if Equatorial::looks_like_sexigesimal?(dec)
|
404
|
+
@value = Declination.from_sexigesimal(dec).value
|
405
|
+
else
|
406
|
+
raise Exception::InvalidSexigesimalFormat.new(dec)
|
407
|
+
end
|
408
|
+
else
|
409
|
+
raise Exception::NotADeclinationAscension.new(dec)
|
410
|
+
end
|
411
|
+
|
412
|
+
raise Exception::DecOutOfRange.new(dec) if !Declination::in_range?(@value)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Is the supplied number in the correct range for a declination
|
416
|
+
# (i.e between -90 and 90 degrees)?
|
417
|
+
def self.in_range?(dec)
|
418
|
+
(MIN_ALLOWED_VALUE..MAX_ALLOWED_VALUE) === dec
|
419
|
+
end
|
420
|
+
|
421
|
+
# Create a Declination object from a string in sexigesimal format.
|
422
|
+
# Numerical values may be separated by a space or colon (but not both).
|
423
|
+
def self.from_sexigesimal(dec)
|
424
|
+
raise Exception::InvalidSexigesimalFormat.new(dec) if !Equatorial::looks_like_sexigesimal?(dec)
|
425
|
+
|
426
|
+
degree, arcmin, arcsec = dec.split(/:|\s+/)
|
427
|
+
degree_f = degree.to_f()
|
428
|
+
arcmin_f = (arcmin.to_f() / 60.0)
|
429
|
+
arcsec_f = (arcsec.to_f() / (60.0 * 60.0))
|
430
|
+
|
431
|
+
return Declination.new(degree_f - arcmin_f - arcsec_f) if degree_f < 0
|
432
|
+
return Declination.new(degree_f + arcmin_f + arcsec_f)
|
433
|
+
end
|
434
|
+
|
435
|
+
# Return the value of the dec as a string in sexigesimal format.
|
436
|
+
def to_sexigesimal(sep=':')
|
437
|
+
degrees = self.value
|
438
|
+
decsign = '+'
|
439
|
+
if degrees < 0
|
440
|
+
decsign = '-'
|
441
|
+
degrees = -degrees
|
442
|
+
end
|
443
|
+
decd = degrees.to_i()
|
444
|
+
decm = ((degrees - decd) * 60.0).to_i()
|
445
|
+
decs = degrees - decd - (decm / 60.0)
|
446
|
+
decs = decs * 60.0 * 60.0
|
447
|
+
|
448
|
+
return sprintf("%s%02u#{sep}%02u#{sep}%0.2f", decsign, decd, decm, decs)
|
449
|
+
end
|
450
|
+
|
451
|
+
# Returns a declination object projected forward the specified
|
452
|
+
# number of degrees. Note that if the new object will have a
|
453
|
+
# value greater than 90 degrees, the declination will be assumed
|
454
|
+
# to be exactly 90 degrees, and similarly for -90 degrees.
|
455
|
+
def +(degrees)
|
456
|
+
new_degrees = self.value + degrees
|
457
|
+
new_degrees = MAX_ALLOWED_VALUE if new_degrees > MAX_ALLOWED_VALUE
|
458
|
+
new_degrees = MIN_ALLOWED_VALUE if new_degrees < MIN_ALLOWED_VALUE
|
459
|
+
|
460
|
+
Declination.new(new_degrees)
|
461
|
+
end
|
462
|
+
|
463
|
+
# Add the specified number of degrees to the dec.
|
464
|
+
def increment(degrees)
|
465
|
+
new_degrees = self + degrees
|
466
|
+
self.value = new_degrees.value
|
467
|
+
end
|
468
|
+
|
469
|
+
# Returns a declination object projected backwards the specified
|
470
|
+
# number of degrees. Note that if the new object will have a
|
471
|
+
# value greater less than -90 degrees, the declination will be assumed
|
472
|
+
# to be exactly -90 degrees.
|
473
|
+
def -(degrees)
|
474
|
+
new_degrees = self.value - degrees
|
475
|
+
new_degrees = MIN_ALLOWED_VALUE if new_degrees < MIN_ALLOWED_VALUE
|
476
|
+
|
477
|
+
Declination.new(new_degrees)
|
478
|
+
end
|
479
|
+
|
480
|
+
# Subtract the specified number of degrees from the dec.
|
481
|
+
def decrement(degrees)
|
482
|
+
new_degrees = self - degrees
|
483
|
+
self.value = new_degrees.value
|
484
|
+
end
|
485
|
+
|
486
|
+
def <=>(dec)
|
487
|
+
self.value <=> dec.value
|
488
|
+
end
|
489
|
+
|
490
|
+
def ==(dec)
|
491
|
+
self.value == dec.value
|
492
|
+
end
|
493
|
+
|
494
|
+
def to_s
|
495
|
+
"#{self.to_sexigesimal}"
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
class RADecPosition < SpaceTime::SpatialPosition
|
500
|
+
attr_reader :ra, :dec, :distance
|
501
|
+
attr_accessor :equinox, :system
|
502
|
+
|
503
|
+
# Create a Position object. ra and dec may be:
|
504
|
+
# 1) RightAscension or Declination objects
|
505
|
+
# 2) Sexigesimal strings in standard format
|
506
|
+
# 3) Strings that looks like decimals
|
507
|
+
# 4) Degrees in decimal format.
|
508
|
+
def initialize(ra, dec, distance=nil, equinox=2000.0, system=:fk5)
|
509
|
+
self.ra = ra
|
510
|
+
self.dec = dec
|
511
|
+
self.distance = distance
|
512
|
+
self.equinox = equinox
|
513
|
+
self.system = system
|
514
|
+
|
515
|
+
super(self.ra, self.dec, self.distance)
|
516
|
+
end
|
517
|
+
|
518
|
+
def ra=(ra)
|
519
|
+
(ra.is_a?(RightAscension))? @ra = ra: @ra = RightAscension.new(ra)
|
520
|
+
end
|
521
|
+
|
522
|
+
def dec=(dec)
|
523
|
+
(dec.is_a?(Declination))? @dec = dec: @dec = Declination.new(dec)
|
524
|
+
end
|
525
|
+
|
526
|
+
def distance=(dist)
|
527
|
+
@distance = dist
|
528
|
+
end
|
529
|
+
|
530
|
+
# Returns the angular separation between two positions in degrees.
|
531
|
+
def angular_separation(position)
|
532
|
+
# Convert to radians
|
533
|
+
ra1 = self.ra.value * (Math::PI / 180.0)
|
534
|
+
dec1 = self.dec.value * (Math::PI / 180.0)
|
535
|
+
ra2 = position.ra.value * (Math::PI / 180.0)
|
536
|
+
dec2 = position.dec.value * (Math::PI / 180.0)
|
537
|
+
|
538
|
+
ninety = 90.0 * (Math::PI / 180) # One quarter of a circle (90 degrees) in radians
|
539
|
+
|
540
|
+
sep = Math.acos(
|
541
|
+
Math.cos(ninety - dec1) * Math.cos(ninety - dec2) +
|
542
|
+
Math.sin(ninety - dec1) * Math.sin(ninety - dec2) * Math.cos(ra1 - ra2)
|
543
|
+
)
|
544
|
+
|
545
|
+
return sep * (180.0 / Math::PI)
|
546
|
+
end
|
547
|
+
|
548
|
+
def ==(position)
|
549
|
+
# Precess the new position object into the same equinox as the receiver.
|
550
|
+
position = position.precess(self.equinox, self.system) if position.equinox != self.equinox and position.system != self.system
|
551
|
+
self.ra == position.ra && self.dec == position.dec && self.distance == position.distance
|
552
|
+
end
|
553
|
+
|
554
|
+
# Precess the position to the specified equinox.
|
555
|
+
# The constants for the precession matrix and general algorithm are taken from the IDL astro routines PREMAT and PRECESS.
|
556
|
+
# Notes: Accuracy of precession decreases for declination values near 90 degrees.
|
557
|
+
# Should not be used more than 2.5 centuries from 2000 on the FK5 system (1950.0 on the FK4 system).
|
558
|
+
def precess(to_equinox=2000.0, system=:fk5)
|
559
|
+
if self.equinox != to_equinox or self.system != system
|
560
|
+
deg_to_rad = Math::PI / 180.0
|
561
|
+
|
562
|
+
from_components = Matrix.row_vector(RADecPosition::equatorial_to_rectangular(self.ra.value, self.dec.value))
|
563
|
+
precession_matrix = Equatorial::precession_matrix(self.equinox, to_equinox, system)
|
564
|
+
to_components = from_components * precession_matrix
|
565
|
+
ra, dec = RADecPosition::rectangular_to_equatorial(to_components[0, 0], to_components[0, 1], to_components[0, 2])
|
566
|
+
|
567
|
+
return RADecPosition.new(ra, dec, self.distance, to_equinox, system)
|
568
|
+
else
|
569
|
+
return self
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
def to_s
|
574
|
+
coords = "#{self.ra}, #{self.dec}"
|
575
|
+
coords << ", #{self.distance}" if self.distance
|
576
|
+
|
577
|
+
coords
|
578
|
+
end
|
579
|
+
|
580
|
+
# Convert RA and Dec in decimal degrees to rectangular coordinates.
|
581
|
+
def self.equatorial_to_rectangular(ra, dec)
|
582
|
+
deg_to_rad = Math::PI / 180.0
|
583
|
+
|
584
|
+
x = Math.cos(ra * deg_to_rad) * Math.cos(dec * deg_to_rad)
|
585
|
+
y = Math.sin(ra * deg_to_rad) * Math.cos(dec * deg_to_rad)
|
586
|
+
z = Math.sin(dec * deg_to_rad)
|
587
|
+
|
588
|
+
return [x, y, z]
|
589
|
+
end
|
590
|
+
|
591
|
+
# Convert rectangular coordinates to equatorial coordinates in degrees.
|
592
|
+
def self.rectangular_to_equatorial(x, y, z)
|
593
|
+
rad_to_deg = 180.0 / Math::PI
|
594
|
+
|
595
|
+
ra_rad = Math.atan(y/x)
|
596
|
+
dec_rad = Math.asin(z)
|
597
|
+
|
598
|
+
ra_rad = ra_rad + 2 * Math::PI if ra_rad < 0.0
|
599
|
+
|
600
|
+
[ra_rad * rad_to_deg, dec_rad * rad_to_deg]
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|