voruby2-preview 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,308 @@
1
+ require 'open-uri'
2
+ require 'mathn'
3
+ require 'date'
4
+ require 'tempfile'
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError; end
9
+ require 'xml/libxml'
10
+ require 'builder'
11
+
12
+ require 'voruby'
13
+ require 'voruby/misc/libxml_ext'
14
+
15
+ module VORuby
16
+ # A set of classes designed to read and manipulate the IVOA[http://www.ivoa.net/]
17
+ # standard for representing astronomical tabular data called
18
+ # VOTable[http://www.ivoa.net/Documents/latest/VOT.html].
19
+ #
20
+ # As a quick start, you might do something like this:
21
+ #
22
+ # require 'voruby/votable/votable'
23
+ # include VORuby
24
+ #
25
+ # votable = VOTable.from_xml(File.new('votable.basic.xml')) # this happens to be a 1.1 votable
26
+ # table = votable.resources.first.tables.first # get the first resource and table
27
+ #
28
+ # # See the name of each column
29
+ # puts table.fields.collect{ |field| field.name }.join('|')
30
+ #
31
+ # # Iterate through each row.
32
+ # table.data.format.trs.each do |row|
33
+ # row_as_string = row.tds.collect{ |td| td.value }.join('|')
34
+ # puts row_as_string
35
+ # end
36
+ #
37
+ # Methods that are pluralized (i.e. table.fields and format.trs above) normally return an
38
+ # array-like object called a HomogeneousNodeList. You can do most of the common array
39
+ # operations on it, including array assignment ([]=) and retrieval ([]), appending (<<)
40
+ # and iteration (each). So:
41
+ #
42
+ # table.fields << Field.new(...) # append a new field
43
+ # table.fields.prepend(Field.new(...)) # prepend a new field
44
+ # table.fields.each { |field| ... } # iterate through each field
45
+ # table.fields[2] = Field.new(...) # assign a new field to the third position
46
+ # table.fields[2] # retrieve the third field
47
+ # table.fields.delete_at(2) # delete the third field
48
+ # table.fields.clear # delete all fields
49
+ # table.fields.first # retrieve the first field
50
+ # table.fields.last # retrieve the last field
51
+ # table.fields.size # the number of fields present
52
+ #
53
+ # It also mixes in Enumerable so all the other standard methods are available.
54
+ #
55
+ # To build a simple VOTable from scratch all in one go one might do this:
56
+ #
57
+ # votable = VOTable.new(
58
+ # :version => '1.1',
59
+ # :coordinate_systems => [
60
+ # Coosys.new(:id => 'J2000', :equinox => 'J2000.', :epoch => 'J2000.', :system => 'eq_FK5')
61
+ # ],
62
+ # :resources => [
63
+ # Resource.new(
64
+ # :name => 'myFavouriteGalaxies',
65
+ # :tables => [
66
+ # Table.new(
67
+ # :name => 'results',
68
+ # :description => Description.new(:text => 'Velocities and Distance estimations'),
69
+ # :params => [
70
+ # Param.new(
71
+ # :name => 'Telescope',
72
+ # :datatype => 'float',
73
+ # :ucd => 'phys.size;instr.tel',
74
+ # :unit => 'm',
75
+ # :value => '3.6'
76
+ # )
77
+ # ],
78
+ # :fields => [
79
+ # Field.new(:name => 'RA', :id => 'col1', :ucd => 'pos.eq.ra;meta.main',
80
+ # :ref => 'J2000', :datatype => 'float', :width => 6, :precision => '2', :unit => 'deg'),
81
+ # Field.new(:name => 'Dec', :id => 'col2', :ucd => 'pos.eq.dec;meta.main',
82
+ # :ref => 'J2000', :datatype => 'float', :width => 6, :precision => '2', :unit => 'deg'),
83
+ # Field.new(:name => 'Name', :id => 'col3', :ucd => 'meta.id;meta.main',
84
+ # :datatype => 'char', :arraysize => '8*'),
85
+ # Field.new(:name => 'RVel', :id => 'col4', :ucd => 'src.veloc.hc', :datatype => 'int',
86
+ # :width => 5, :unit => 'km/s'),
87
+ # Field.new(:name => 'e_RVel', :id => 'col5', :ucd => 'stat.error;src.veloc.hc',
88
+ # :datatype => 'int', :width => 3, :unit => 'km/s'),
89
+ # Field.new(:name => 'R', :id => 'col6', :ucd => 'phys.distance', :datatype => 'float',
90
+ # :width => 4, :precision => '1', :unit => 'Mpc',
91
+ # :description => Description.new(:text => 'Distance of Galaxy, assuming H=75km/s/Mpc'))
92
+ # ],
93
+ # :data => Data.new(
94
+ # :format => TableData.new(
95
+ # :trs => [
96
+ # Tr.new(:tds => [
97
+ # Td.new(:text => '010.68'), Td.new(:text => '+41.27'), Td.new(:text => 'N 224'),
98
+ # Td.new(:text => '-297'), Td.new(:text => '5'), Td.new(:text => '0.7')
99
+ # ]),
100
+ # Tr.new(:tds => [
101
+ # Td.new(:text => '287.43'), Td.new(:text => '-63.85'), Td.new(:text => 'N 6744'),
102
+ # Td.new(:text => '839'), Td.new(:text => '6'), Td.new(:text => '10.4')
103
+ # ]),
104
+ # Tr.new(:tds => [
105
+ # Td.new(:text => '023.48'), Td.new(:text => '+30.66'), Td.new(:text => 'N 598'),
106
+ # Td.new(:text => '-182'), Td.new(:text => '3'), Td.new(:text => '0.7')
107
+ # ])
108
+ # ]
109
+ # )
110
+ # )
111
+ # )
112
+ # ]
113
+ # )
114
+ # ]
115
+ # )
116
+ #
117
+ # Of course, it's more likely you would have read the votable in directly from a file.
118
+ # To convert this votable to a (very) simple CSV-like file you could then:
119
+ #
120
+ # puts votable.resources.first.tables.first.fields.collect{ |field| field.name }.join(',')
121
+ # votable.resources.first.tables.first.data.format.trs.each do |tr|
122
+ # puts tr.tds.collect{ |td| td.text }.join(',')
123
+ # end
124
+ #
125
+ # Which would print out something like:
126
+ #
127
+ # RA,Dec,RVel,e_RVel,R
128
+ # 010.68,+41.27,N 224,-297,5,0.7
129
+ # 287.43,-63.85,N 6744,839,6,10.4
130
+ # 023.48,+30.66,N 598,-182,3,0.7
131
+ #
132
+ # You can also convert the votable to an HTML fragment by doing:
133
+ #
134
+ # votable.to_html
135
+ module VOTable
136
+ # Automatically determines the votable version in question and
137
+ # returns the appropriate domain object.
138
+ # _defn_:: a String, XML::Document, XML::Node or IO object representing the XML.
139
+ #
140
+ # votable = VOTable.from_xml(File.new('votable.basic.xml')) # assuming this is 1.1 document
141
+ # puts votable.class #=> VOTable::V1_1::VOTable
142
+ def self.from_xml(defn)
143
+ generic_vot = Base.new(defn).node
144
+
145
+ namespaces = (generic_vot.namespace || []).collect{ |ns| ns.href }
146
+ namespaces << generic_vot['noNamespaceSchemaLocation']
147
+
148
+ if namespaces.include?('http://www.ivoa.net/xml/VOTable/VOTable/v1.1') or generic_vot['version'] =~ /1\.1$/
149
+ require 'voruby/votable/1.1/votable'
150
+ return VOTable::V1_1::VOTable.new(generic_vot)
151
+ elsif namespaces.include?('http://www.ivoa.net/xml/VOTable/v1.0') or generic_vot['version'] =~ /1\.0$/
152
+ require 'voruby/votable/1.0/votable'
153
+ return VOTable::V1_0::VOTable.new(generic_vot)
154
+ else
155
+ require 'voruby/votable/1.1/votable'
156
+ return VOTable::V1_1::VOTable.new(generic_vot) # go with the most recent if namespace indeterminate
157
+ end
158
+ end
159
+
160
+ # The base class that all VOTable domain objects derive from.
161
+ # You'll never instantiate this directly.
162
+ class Base < XML::Object::Base
163
+ # Get the name of the element when serialized
164
+ # to XML that the object in question will take on.
165
+ def self.element_name
166
+ const_get('ELEMENT_NAME')
167
+ end
168
+
169
+ # Determine the xpath corresponding to the specified class.
170
+ # obj.xpath_for(Field) # => "*[local-name()='FIELD']"
171
+ def xpath_for(klass)
172
+ "*[local-name()='#{klass.element_name}']"
173
+ end
174
+
175
+ # Retrieve the domain object that corresponds to
176
+ # the specified class.
177
+ def get_element(klass)
178
+ subnode = self.node.find_first("*[local-name()='#{klass.element_name}']")
179
+ subnode ? klass.new(subnode) : nil
180
+ end
181
+
182
+ # Equality among domain objects.
183
+ # Two objects are considered equal if they're
184
+ # of the same class and their accessor methods
185
+ # themselves return objects which are equal.
186
+ def ==(obj)
187
+ return false if self.class != obj.class
188
+
189
+ self.methods_to_test_for_equality.each do |method|
190
+ return false if self.send(method) != obj.send(method)
191
+ end
192
+
193
+ true
194
+ end
195
+
196
+ private
197
+
198
+ def initialize_members(args)
199
+ if self.class.respond_to?(:serialization_order)
200
+ self.class.serialization_order.each do |name|
201
+ send("#{name}=", args[name]) if args.has_key?(name)
202
+ end
203
+ else
204
+ args.each do |name, value|
205
+ send("#{name}=", value)
206
+ end
207
+ end
208
+ end
209
+ end
210
+
211
+ # Some helper methods for converting string values that have an associated
212
+ # data type (like TDs do through their associated FIELD).
213
+ module Castable
214
+ # Convert a string value into its corresponding basic object representation,
215
+ # if appropriate. This does *not* work on array values, only on scalars.
216
+ # Native ruby types map to votable datatypes in the following way:
217
+ #
218
+ # * 'boolean' => boolean (i.e TrueClass or FalseClass)
219
+ # * 'bit' => Integer (1 or 0)
220
+ # * 'unsignedByte' => Integer representation of the byte
221
+ # * 'short, 'int', 'long' => Integer
222
+ # * 'char', 'unicodeChar' => String
223
+ # * 'float', 'double' => Float or Bignum
224
+ # * 'floatComplex', 'doubleComplex' => Complex
225
+ def cast(txt, to='char')
226
+ case to
227
+ when 'boolean'
228
+ txt == '1' || txt.downcase == 'true' || txt.downcase == 't'
229
+ when 'bit' then txt.to_i
230
+ when 'unsignedByte' then txt[0]
231
+ when 'short' then txt.to_i
232
+ when 'int' then txt.to_i
233
+ when 'long' then txt.to_i
234
+ when 'double' then txt.to_f
235
+ when 'float' then txt.to_f
236
+ else
237
+ txt
238
+ end
239
+ end
240
+
241
+ # Serialize a ruby object to the format that is correct
242
+ # for a votable.
243
+ def convert_to_s(val, from='char')
244
+ case from
245
+ when 'boolean'
246
+ (val.is_a?(TrueClass) or val.to_s.downcase =~ /^t/ or val.to_s == '1') ?
247
+ 'true' : 'false'
248
+ when 'bit' then val.to_i.to_s
249
+ when 'short' then val.to_i.to_s
250
+ when 'int' then val.to_i.to_s
251
+ when 'long' then val.to_i.to_s
252
+ when 'float' then val.to_f.to_s
253
+ when 'double' then val.to_f.to_s
254
+ when 'floatComplex' then "#{val.real} #{val.image}"
255
+ when 'doubleComplex' then "#{val.real} #{val.image}"
256
+ else
257
+ val.to_s
258
+ end
259
+ end
260
+
261
+ # Much like #cast but understands arrays.
262
+ def as_obj(txt, datatype, arraysize=nil)
263
+ raise "Unable to determine datatype" if !datatype
264
+
265
+ txt_list = arraysize ? txt.split(/\s+/) : [txt]
266
+ values_list = []
267
+
268
+ # character string, do nothing
269
+ return txt if datatype == 'char' or datatype == 'unicodeChar'
270
+
271
+ # complex values are a bit different
272
+ if datatype == 'floatComplex' || datatype == 'doubleComplex'
273
+ txt_list = txt.split(/\s+/)
274
+ complex_numbers = []
275
+ i = 0
276
+ (0...txt_list.size/2).each do |n|
277
+ complex_numbers << Complex.new(txt_list[i].to_f, txt_list[i+1].to_f)
278
+ i += 2
279
+ end
280
+ return arraysize ? complex_numbers : complex_numbers.first
281
+ end
282
+
283
+ txt_list.each do |component|
284
+ values_list << cast(component, datatype)
285
+ end
286
+
287
+ arraysize ? values_list : values_list.first
288
+ end
289
+
290
+ # Much like #convert_to_s but understands arrays.
291
+ def as_string(obj, datatype, arraysize=nil)
292
+ raise "Unable to determine datatype" if !datatype
293
+ raise "Supposed to contain an array of size '#{arraysize}'" if arraysize and !obj.is_a?(Array)
294
+ raise "Supposed to contain a single value" if !arraysize and obj.is_a?(Array)
295
+
296
+ obj = [obj] if !obj.is_a?(Array)
297
+
298
+ values = []
299
+ obj.each do |val|
300
+ values << convert_to_s(val, datatype)
301
+ end
302
+
303
+ values.join(' ')
304
+ end
305
+ end
306
+
307
+ end
308
+ end
@@ -0,0 +1,491 @@
1
+ require 'base64'
2
+ require 'open-uri'
3
+
4
+ begin
5
+ require 'rubygems'
6
+ rescue LoadError; end
7
+
8
+ gem 'soap4r'
9
+ require 'soap/wsdlDriver'
10
+
11
+ require 'voruby/votable/1.1/votable'
12
+
13
+ module VORuby
14
+ module Wesix
15
+ class SextractorParams
16
+ WESIX_NS = 'http://wesixtest.phyast.pitt.edu'
17
+
18
+ def initialize(params={})
19
+ params.each{ |key, value| self.send("#{key}=", value) }
20
+ end
21
+ end
22
+
23
+ # Object containing the parameters to be used by WESIX (e.g. SExtractor) to create the source catalog.
24
+ # You can preset values by passing a hash to the constructor:
25
+ #
26
+ # params = Params.new(:analysis_thresh => 2.0)
27
+ # params.detect_thresh = 2.0
28
+ # puts params.analysis_thresh # => 2.0
29
+ #
30
+ # Available methods are:
31
+ # #analysis_thresh, #backphoto_type, #back_filtersize, #back_size, #catalog_name, #catalog_type,
32
+ # #checkimage_name, #checkimage_type, #clean, #clean_param, #deblend_mincont, #deblend_nthresh,
33
+ # #detect_minarea, #detect_thresh, #detect_type, #filter, #filter_name, #flag_image, #gain,
34
+ # #mag_gamma, #mag_zeropoint, #mask_type, #memory_bufsize, #memory_objstack, #memory_pixstack,
35
+ # #parameters_name, #phot_apertures, #phot_autoparams1, #phot_autoparams2, #pixel_scale, #satur_level,
36
+ # #seeing_fwhm, #starnnw_name, #verbose_type, #query1, #query2, #string
37
+ class Params < SextractorParams
38
+ # Returns the schema required to convert this object into a
39
+ # fully-fledged SOAP object. Used by Service.
40
+ def self.soap_registration
41
+ {
42
+ :class => self,
43
+ :schema_type => XSD::QName.new(WESIX_NS, "SexParams"),
44
+ :schema_element => [
45
+ ["analysis_thresh", ["SOAP::SOAPDouble", XSD::QName.new(nil, "ANALYSIS_THRESH")]],
46
+ ["backphoto_type", ["SOAP::SOAPString", XSD::QName.new(nil, "BACKPHOTO_TYPE")]],
47
+ ["back_filtersize", ["SOAP::SOAPInt", XSD::QName.new(nil, "BACK_FILTERSIZE")]],
48
+ ["back_size", ["SOAP::SOAPInt", XSD::QName.new(nil, "BACK_SIZE")]],
49
+ ["catalog_name", ["SOAP::SOAPString", XSD::QName.new(nil, "CATALOG_NAME")]],
50
+ ["catalog_type", ["SOAP::SOAPString", XSD::QName.new(nil, "CATALOG_TYPE")]],
51
+ ["checkimage_name", ["SOAP::SOAPString", XSD::QName.new(nil, "CHECKIMAGE_NAME")]],
52
+ ["checkimage_type", ["SOAP::SOAPString", XSD::QName.new(nil, "CHECKIMAGE_TYPE")]],
53
+ ["clean", ["SOAP::SOAPString", XSD::QName.new(nil, "CLEAN")]],
54
+ ["clean_param", ["SOAP::SOAPDouble", XSD::QName.new(nil, "CLEAN_PARAM")]],
55
+ ["deblend_mincont", ["SOAP::SOAPDouble", XSD::QName.new(nil, "DEBLEND_MINCONT")]],
56
+ ["deblend_nthresh", ["SOAP::SOAPInt", XSD::QName.new(nil, "DEBLEND_NTHRESH")]],
57
+ ["detect_minarea", ["SOAP::SOAPInt", XSD::QName.new(nil, "DETECT_MINAREA")]],
58
+ ["detect_thresh", ["SOAP::SOAPDouble", XSD::QName.new(nil, "DETECT_THRESH")]],
59
+ ["detect_type", ["SOAP::SOAPString", XSD::QName.new(nil, "DETECT_TYPE")]],
60
+ ["filter", ["SOAP::SOAPString", XSD::QName.new(nil, "FILTER")]],
61
+ ["filter_name", ["SOAP::SOAPString", XSD::QName.new(nil, "FILTER_NAME")]],
62
+ ["flag_image", ["SOAP::SOAPString", XSD::QName.new(nil, "FLAG_IMAGE")]],
63
+ ["gain", ["SOAP::SOAPDouble", XSD::QName.new(nil, "GAIN")]],
64
+ ["mag_gamma", ["SOAP::SOAPDouble", XSD::QName.new(nil, "MAG_GAMMA")]],
65
+ ["mag_zeropoint", ["SOAP::SOAPDouble", XSD::QName.new(nil, "MAG_ZEROPOINT")]],
66
+ ["mask_type", ["SOAP::SOAPString", XSD::QName.new(nil, "MASK_TYPE")]],
67
+ ["memory_bufsize", ["SOAP::SOAPInt", XSD::QName.new(nil, "MEMORY_BUFSIZE")]],
68
+ ["memory_objstack", ["SOAP::SOAPInt", XSD::QName.new(nil, "MEMORY_OBJSTACK")]],
69
+ ["memory_pixstack", ["SOAP::SOAPInt", XSD::QName.new(nil, "MEMORY_PIXSTACK")]],
70
+ ["parameters_name", ["SOAP::SOAPString", XSD::QName.new(nil, "PARAMETERS_NAME")]],
71
+ ["phot_apertures", ["SOAP::SOAPInt", XSD::QName.new(nil, "PHOT_APERTURES")]],
72
+ ["phot_autoparams1", ["SOAP::SOAPDouble", XSD::QName.new(nil, "PHOT_AUTOPARAMS1")]],
73
+ ["phot_autoparams2", ["SOAP::SOAPDouble", XSD::QName.new(nil, "PHOT_AUTOPARAMS2")]],
74
+ ["pixel_scale", ["SOAP::SOAPDouble", XSD::QName.new(nil, "PIXEL_SCALE")]],
75
+ ["satur_level", ["SOAP::SOAPDouble", XSD::QName.new(nil, "SATUR_LEVEL")]],
76
+ ["seeing_fwhm", ["SOAP::SOAPDouble", XSD::QName.new(nil, "SEEING_FWHM")]],
77
+ ["starnnw_name", ["SOAP::SOAPString", XSD::QName.new(nil, "STARNNW_NAME")]],
78
+ ["verbose_type", ["SOAP::SOAPString", XSD::QName.new(nil, "VERBOSE_TYPE")]],
79
+ ["query1", ["SOAP::SOAPString", XSD::QName.new(nil, "query1")]],
80
+ ["query2", ["SOAP::SOAPString", XSD::QName.new(nil, "query2")]],
81
+ ["string", ["SOAP::SOAPString", XSD::QName.new(nil, "string")]]
82
+ ]
83
+ }
84
+ end
85
+
86
+ attr_accessor *soap_registration[:schema_element].collect{ |defn| defn.first }
87
+ end
88
+
89
+ # Object containing the measured parameters (all booleans) to be included in the output catalog
90
+ # generated by WESIX. You can preset values by passing a hash to the constructor:
91
+ #
92
+ # params = OutputParams.new(:flux_best => true)
93
+ # params.fluxerr_best = true
94
+ # puts params.flux_best # => true
95
+ #
96
+ # Available methods are:
97
+ # #alpha_b1950, #alpha_j2000, #alpha_sky, #background, #class_star, #cxx_image, #cxx_world,
98
+ # #cxy_image, #cxy_world, #cyy_image, #cyy_world, #delta_b1950, #delta_j2000, #delta_sky,
99
+ # #ellipticity, #elongation, #erra_image, #erra_world, #errb_image, #errb_world, #errcxx_image,
100
+ # #errcxx_world, #errcxy_image, #errcxy_world, #errcyy_image, #errcyy_world, #errtheta_b1950,
101
+ # #errtheta_image, #errtheta_j2000, #errtheta_sky, #errtheta_world, #errx2_image, #errx2_world,
102
+ # #errxy_image, #errxy_world, #erry2_image, #erry2_world, #flags, #fluxerr_aper, #fluxerr_auto,
103
+ # #fluxerr_best, #fluxerr_iso, #fluxerr_isocor, #flux_aper, #flux_auto, #flux_best, #flux_iso,
104
+ # #flux_isocor, #flux_max, #fwhm_image, #fwhm_world, #imaflags_iso, #iso0, #iso1, #iso2, #iso3,
105
+ # #iso4, #iso5, #iso6, #iso7, #isoarea_image, #isoarea_world, #kron_radius, #magerr_aper, #magerr_auto,
106
+ # #magerr_best, #magerr_iso, #magerr_isocor, #mag_aper, #mag_auto, #mag_best, #mag_iso, #mag_isocor, #mu_max,
107
+ # #mu_threshold, #nimaflags_iso, #number, #theta_b1950, #theta_image, #theta_j2000, #theta_sky, #theta_world,
108
+ # #threshold, #vignet, #xmax_image, #xmin_image, #xy_image, #xy_world, #ymax_image, #ymin_image, #a_image, #a_world,
109
+ # #b_image, #b_world, #params, #x2_image, #x2_world, #x_image, #x_world, #y2_image, #y2_world, #y_image, #y_world
110
+ class OutputParams < SextractorParams
111
+ # Returns the schema required to convert this object into a
112
+ # fully-fledged SOAP object. Used by Service.
113
+ def self.soap_registration
114
+ {
115
+ :class => self,
116
+ :schema_type => XSD::QName.new(WESIX_NS, "SexOutputParams"),
117
+ :schema_element => [
118
+ ["alpha_b1950", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ALPHA_B1950")]],
119
+ ["alpha_j2000", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ALPHA_J2000")]],
120
+ ["alpha_sky", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ALPHA_SKY")]],
121
+ ["background", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "BACKGROUND")]],
122
+ ["class_star", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CLASS_STAR")]],
123
+ ["cxx_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CXX_IMAGE")]],
124
+ ["cxx_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CXX_WORLD")]],
125
+ ["cxy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CXY_IMAGE")]],
126
+ ["cxy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CXY_WORLD")]],
127
+ ["cyy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CYY_IMAGE")]],
128
+ ["cyy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "CYY_WORLD")]],
129
+ ["delta_b1950", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "DELTA_B1950")]],
130
+ ["delta_j2000", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "DELTA_J2000")]],
131
+ ["delta_sky", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "DELTA_SKY")]],
132
+ ["ellipticity", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ELLIPTICITY")]],
133
+ ["elongation", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ELONGATION")]],
134
+ ["erra_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRA_IMAGE")]],
135
+ ["erra_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRA_WORLD")]],
136
+ ["errb_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRB_IMAGE")]],
137
+ ["errb_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRB_WORLD")]],
138
+ ["errcxx_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCXX_IMAGE")]],
139
+ ["errcxx_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCXX_WORLD")]],
140
+ ["errcxy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCXY_IMAGE")]],
141
+ ["errcxy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCXY_WORLD")]],
142
+ ["errcyy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCYY_IMAGE")]],
143
+ ["errcyy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRCYY_WORLD")]],
144
+ ["errtheta_b1950", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRTHETA_B1950")]],
145
+ ["errtheta_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRTHETA_IMAGE")]],
146
+ ["errtheta_j2000", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRTHETA_J2000")]],
147
+ ["errtheta_sky", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRTHETA_SKY")]],
148
+ ["errtheta_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRTHETA_WORLD")]],
149
+ ["errx2_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRX2_IMAGE")]],
150
+ ["errx2_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRX2_WORLD")]],
151
+ ["errxy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRXY_IMAGE")]],
152
+ ["errxy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRXY_WORLD")]],
153
+ ["erry2_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRY2_IMAGE")]],
154
+ ["erry2_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ERRY2_WORLD")]],
155
+ ["flags", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLAGS")]],
156
+ ["fluxerr_aper", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUXERR_APER")]],
157
+ ["fluxerr_auto", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUXERR_AUTO")]],
158
+ ["fluxerr_best", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUXERR_BEST")]],
159
+ ["fluxerr_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUXERR_ISO")]],
160
+ ["fluxerr_isocor", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUXERR_ISOCOR")]],
161
+ ["flux_aper", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_APER")]],
162
+ ["flux_auto", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_AUTO")]],
163
+ ["flux_best", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_BEST")]],
164
+ ["flux_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_ISO")]],
165
+ ["flux_isocor", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_ISOCOR")]],
166
+ ["flux_max", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FLUX_MAX")]],
167
+ ["fwhm_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FWHM_IMAGE")]],
168
+ ["fwhm_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "FWHM_WORLD")]],
169
+ ["imaflags_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "IMAFLAGS_ISO")]],
170
+ ["iso0", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO0")]],
171
+ ["iso1", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO1")]],
172
+ ["iso2", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO2")]],
173
+ ["iso3", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO3")]],
174
+ ["iso4", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO4")]],
175
+ ["iso5", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO5")]],
176
+ ["iso6", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO6")]],
177
+ ["iso7", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISO7")]],
178
+ ["isoarea_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISOAREA_IMAGE")]],
179
+ ["isoarea_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "ISOAREA_WORLD")]],
180
+ ["kron_radius", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "KRON_RADIUS")]],
181
+ ["magerr_aper", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAGERR_APER")]],
182
+ ["magerr_auto", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAGERR_AUTO")]],
183
+ ["magerr_best", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAGERR_BEST")]],
184
+ ["magerr_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAGERR_ISO")]],
185
+ ["magerr_isocor", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAGERR_ISOCOR")]],
186
+ ["mag_aper", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAG_APER")]],
187
+ ["mag_auto", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAG_AUTO")]],
188
+ ["mag_best", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAG_BEST")]],
189
+ ["mag_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAG_ISO")]],
190
+ ["mag_isocor", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MAG_ISOCOR")]],
191
+ ["mu_max", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MU_MAX")]],
192
+ ["mu_threshold", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "MU_THRESHOLD")]],
193
+ ["nimaflags_iso", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "NIMAFLAGS_ISO")]],
194
+ ["number", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "NUMBER")]],
195
+ ["theta_b1950", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THETA_B1950")]],
196
+ ["theta_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THETA_IMAGE")]],
197
+ ["theta_j2000", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THETA_J2000")]],
198
+ ["theta_sky", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THETA_SKY")]],
199
+ ["theta_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THETA_WORLD")]],
200
+ ["threshold", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "THRESHOLD")]],
201
+ ["vignet", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "VIGNET")]],
202
+ ["xmax_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "XMAX_IMAGE")]],
203
+ ["xmin_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "XMIN_IMAGE")]],
204
+ ["xy_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "XY_IMAGE")]],
205
+ ["xy_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "XY_WORLD")]],
206
+ ["ymax_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "YMAX_IMAGE")]],
207
+ ["ymin_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "YMIN_IMAGE")]],
208
+ ["a_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "a_IMAGE")]],
209
+ ["a_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "a_WORLD")]],
210
+ ["b_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "b_IMAGE")]],
211
+ ["b_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "b_WORLD")]],
212
+ ["params", ["SOAP::SOAPString", XSD::QName.new(nil, "params")]],
213
+ ["x2_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "x2_IMAGE")]],
214
+ ["x2_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "x2_WORLD")]],
215
+ ["x_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "x_IMAGE")]],
216
+ ["x_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "x_WORLD")]],
217
+ ["y2_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "y2_IMAGE")]],
218
+ ["y2_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "y2_WORLD")]],
219
+ ["y_image", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "y_IMAGE")]],
220
+ ["y_world", ["SOAP::SOAPBoolean", XSD::QName.new(nil, "y_WORLD")]]
221
+ ]
222
+ }
223
+ end
224
+
225
+ attr_accessor *soap_registration[:schema_element].collect{ |defn| defn.first }
226
+ end
227
+
228
+ # Allows access to WESIX[http://nvogre.phyast.pitt.edu:8080/wesix/],
229
+ # a webservice interface to the standard astronomical image analysis package SExtractor
230
+ # together with a cross matching service using OpenSkyQuery[http://openskyquery.net/].
231
+ #
232
+ # Supposing the FITS file you're interested in is available via a URL, to run a
233
+ # source extraction with sensible defaults:
234
+ #
235
+ # wesix = Service.new
236
+ # votable = wesix.extract('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')
237
+ #
238
+ # If it happens to be on disk, you can:
239
+ #
240
+ # votable = wesix.extract(File.new('myfits.fits'))
241
+ #
242
+ # The returned object is a standard VOTable::V1_1::VOTable from which your sources can
243
+ # be extracted.
244
+ #
245
+ # Likewise, an extraction and crossmatch against the SDSS could be done in the following way:
246
+ #
247
+ # votable = wesix.xmatch('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')
248
+ #
249
+ # or if it's on disk:
250
+ #
251
+ # votable = wesix.xmatch(File.new('myfits.fits'))
252
+ class Service
253
+ attr_reader :end_point
254
+
255
+ # Convenience method for Service#extract.
256
+ # votable = Service.extract('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')
257
+ def self.extract(resource, transpose=false, *args)
258
+ wesix = Service.new
259
+ wesix.extract(resource, transpose, *args)
260
+ end
261
+
262
+ # Convenience method for Service#xmatch.
263
+ # votable = Service.xmatch('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')
264
+ def self.xmatch(resource, transpose=false, *args)
265
+ wesix = Service.new
266
+ wesix.xmatch(resource, transpose, *args)
267
+ end
268
+
269
+ # Create a new wesix service instance.
270
+ # Currently the only valid key in the <tt>options</tt> hash is <tt>:end_point</tt>
271
+ # representing the location of the WESIX soap service, and defaults to
272
+ # http://nvogre.phyast.pitt.edu:8080/axis/services/WesixTest. Since at the moment this is
273
+ # the only existing instance of WESIX, you'll never need to specify it.
274
+ #
275
+ # wesix = Wesix.new
276
+ def initialize(options={})
277
+ self.end_point = options[:end_point] || 'http://nvogre.phyast.pitt.edu:8080/axis/services/WesixTest'
278
+ end
279
+
280
+ # Set the locaton of the WESIX webservice.
281
+ # In practice, you'll never need to use this.
282
+ def end_point=(end_point)
283
+ @end_point = end_point
284
+
285
+ # Create the driver.
286
+ # We don't use the WSDL since we won't expose all possible methods.
287
+ @extractor = SOAP::RPC::Driver.new(self.end_point)
288
+ #@extractor.wiredump_dev = STDOUT
289
+ @extractor.return_response_as_xml = true # in prep for dumping the result into a VORuby::VOTable
290
+
291
+ # Map the appropriate objects.
292
+ map = SOAP::Mapping::Registry.new
293
+ map.register(Params.soap_registration)
294
+ map.register(OutputParams.soap_registration)
295
+ @extractor.mapping_registry = map
296
+
297
+ # Define methods.
298
+ @extractor.add_method('wsextractor1VO', 'in0', 'in1')
299
+ @extractor.add_method('wsextractor1VOXmatch', 'in0', 'in1')
300
+ @extractor.add_method('wsextractorURL1VO', 'in0', 'in1')
301
+ @extractor.add_method('wsextractorURL1VOXmatch', 'in0', 'in1')
302
+
303
+ @extractor.add_method('wsextractor2VO', 'in0', 'in1', 'in2')
304
+ @extractor.add_method('wsextractor2VOXmatch', 'in0', 'in1', 'in2')
305
+ @extractor.add_method('wsextractorURL2VO', 'in0', 'in1', 'in2')
306
+ @extractor.add_method('wsextractorURL2VOXmatch', 'in0', 'in1', 'in2')
307
+
308
+ @extractor.add_method('wsextractor3VO', 'in0', 'in1', 'in2', 'in3')
309
+ @extractor.add_method('wsextractor3VOXmatch', 'in0', 'in1', 'in2', 'in3')
310
+ @extractor.add_method('wsextractorURL3VO', 'in0', 'in1', 'in2', 'in3')
311
+ @extractor.add_method('wsextractorURL3VOXmatch', 'in0', 'in1', 'in2', 'in3')
312
+ end
313
+
314
+ # Perform a source extraction on the specified FITS file.
315
+ #
316
+ # <tt>resource</tt> is the FITS file in question. It may be string representing a URL,
317
+ # a URI object or an object that responds to #read (such as a File).
318
+ #
319
+ # <tt>transpose</tt> is a boolean value indicating whether to transpose the image before SExtraction
320
+ # (sometimes necessary for SDSS images).
321
+ #
322
+ # The next two optional arguments are the parameters
323
+ # to be used by SExtractor to create the source catalog (as a Params object) and
324
+ # the measured parameters to be included in the output catalog (as an OutputParams object).
325
+ #
326
+ # A VOTable::V1_1::VOTable is returned.
327
+ #
328
+ # ===== Source extraction with defaults
329
+ #
330
+ # wesix.extract('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits') # from a URL
331
+ # wesix.extract(URL.parse('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')) # this is the same as above
332
+ # wesix.extract(File.new('../fits/my_file.fits')) # from a local file
333
+ #
334
+ # ===== Source extraction with sextractor parameters defined
335
+ #
336
+ # wesix.extract(
337
+ # File.new('../fits/my_file.fits'), # local file, pass a string or URI object if the FITS file is remote
338
+ # false, # no transposition
339
+ # Params.new(:detect_thresh => 2.0, :analysis_thresh => 2.0)
340
+ # )
341
+ #
342
+ # ===== Source extraction with sextractor parameters and measured parameters defined
343
+ #
344
+ # wesix.extract(
345
+ # File.new('../fits/my_file.fits'), # local file, pass a string or URI object if the FITS file is remote
346
+ # false, # no transposition
347
+ # Params.new(:detect_thresh => 2.0, :analysis_thresh => 2.0),
348
+ # OutputParams.new(:flux_best => true, :fluxerr_best => true)
349
+ # )
350
+ def extract(resource, transpose=false, *args)
351
+ if resource.is_a?(URI) or resource.is_a?(String) # FITS file is on a remote machine
352
+ self.extract_from_url(resource.to_s, transpose, *args)
353
+ elsif resource.respond_to?(:read) # FITS file is on a local machine, stored as a diskfile
354
+ self.extract_from_file(resource, transpose, *args)
355
+ else
356
+ raise ArgumentError, 'resource must be a URI, String or respond to a method called #read'
357
+ end
358
+ end
359
+
360
+ # Alias for #extract_and_xmatch
361
+ def xmatch(resource, transpose=false, *args)
362
+ self.extract_and_xmatch(resource, transpose, *args)
363
+ end
364
+
365
+ # Perform a source extraction followed by a crossmatch on the specified FITS file.
366
+ #
367
+ # <tt>resource</tt> is the FITS file in question. It may be string representing a URL,
368
+ # a URI object or an object that responds to #read (such as a File).
369
+ #
370
+ # <tt>transpose</tt> is a boolean value indicating whether to transpose the image before SExtraction
371
+ # (sometimes necessary for SDSS images).
372
+ #
373
+ # The next two optional arguments are the parameters
374
+ # to be used by SExtractor to create the source catalog (as a Params object) and
375
+ # the measured parameters to be included in the output catalog (as an OutputParams object).
376
+ #
377
+ # A VOTable::V1_1::VOTable is returned.
378
+ #
379
+ # ===== Source extraction and crossmatch with defaults
380
+ #
381
+ # wesix.extract_and_xmatch('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits') # from a URL
382
+ # wesix.extract_and_xmatch(URL.parse('http://nvogre.phyast.pitt.edu:8080/wesix/testr.fits')) # this is the same as above
383
+ # wesix.extract_and_xmatch(File.new('../fits/my_file.fits')) # from a local file
384
+ #
385
+ # ===== Source extraction and crossmatch with sextractor parameters defined
386
+ #
387
+ # wesix.extract_and_xmatch(
388
+ # File.new('../fits/my_file.fits'), # local file, pass a string or URI object if the FITS file is remote
389
+ # false, # no transposition
390
+ # Params.new(:detect_thresh => 2.0, :analysis_thresh => 2.0)
391
+ # )
392
+ #
393
+ # ===== Source extraction and crossmatch with sextractor parameters and measured parameters defined
394
+ #
395
+ # wesix.extract_and_xmatch(
396
+ # File.new('../fits/my_file.fits'), # local file, pass a string or URI object if the FITS file is remote
397
+ # false, # no transposition
398
+ # Params.new(:detect_thresh => 2.0, :analysis_thresh => 2.0),
399
+ # OutputParams.new(:flux_best => true, :fluxerr_best => true)
400
+ # )
401
+ def extract_and_xmatch(resource, transpose=false, *args)
402
+ if resource.is_a?(URI) or resource.is_a?(String) # FITS file is on a remote machine
403
+ self.extract_and_xmatch_from_url(resource.to_s, transpose, *args)
404
+ elsif resource.respond_to?(:read) # FITS file is on a local machine, stored as a diskfile
405
+ self.extract_and_xmatch_from_file(resource, transpose, *args)
406
+ else
407
+ raise ArgumentError, 'resource must be a URI, String or respond to a method called #read'
408
+ end
409
+ end
410
+
411
+ # Perform a source extraction on a local FITS file.
412
+ # <tt>file</tt> is any object with a #read method (such as File).
413
+ # Otherwise exactly as #extract.
414
+ def extract_from_file(file, transpose=false, *args)
415
+ raise "resource does not respond to #read" if !file.respond_to?(:read)
416
+
417
+ file = SOAP::SOAPBase64.new(file.read)
418
+ file.type = XSD::QName.new('http://www.w3.org/2001/XMLSchema', 'base64Binary')
419
+ transpose = transpose ? 1 : 0
420
+
421
+ response = case args.size
422
+ when 0 then @extractor.wsextractor1VO(file, transpose) # basic
423
+ when 1 then @extractor.wsextractor2VO(file, args.first, transpose) # input params specified
424
+ when 2 then @extractor.wsextractor3VO(file, args.first, args.last, transpose) # input params/output flags specified
425
+ else
426
+ raise ArgumentError, "wrong number of optional arguments: #{args.size} > 2"
427
+ end
428
+
429
+ vot_node = XML::Parser.string(response).parse.root.find_first("//*[local-name()='VOTABLE']")
430
+ VORuby::VOTable.from_xml(vot_node)
431
+ end
432
+
433
+ # Perform a source extraction and crossmatch on a local FITS file.
434
+ # <tt>file</tt> is any object with a #read method (such as File).
435
+ # Otherwise exactly as #extract_and_xmatch.
436
+ def extract_and_xmatch_from_file(file, transpose=false, *args)
437
+ raise "resource does not respond to #read" if !file.respond_to?(:read)
438
+
439
+ file = SOAP::SOAPBase64.new(file.read)
440
+ file.type = XSD::QName.new('http://www.w3.org/2001/XMLSchema', 'base64Binary')
441
+ transpose = transpose ? 1 : 0
442
+
443
+ response = case args.size
444
+ when 0 then @extractor.wsextractor1VOXmatch(file, transpose) # basic
445
+ when 1 then @extractor.wsextractor2VOXmatch(file, args.first, transpose) # input params specified
446
+ when 2 then @extractor.wsextractor3VOXmatch(file, args.first, args.last, transpose) # input params/output flags specified
447
+ else
448
+ raise ArgumentError, "wrong number of optional arguments: #{args.size} > 2"
449
+ end
450
+
451
+ vot_node = XML::Parser.string(response).parse.root.find_first("//*[local-name()='VOTABLE']")
452
+ VORuby::VOTable.from_xml(vot_node)
453
+ end
454
+
455
+ # Perform a source extraction on a remotely accessible FITS file.
456
+ # <tt>url</tt> may be a string or a URI object. Otherwise exactly as for #extract.
457
+ def extract_from_url(url, transpose=false, *args)
458
+ transpose = transpose ? 1 : 0
459
+
460
+ response = case args.size
461
+ when 0 then @extractor.wsextractorURL1VO(url.to_s, transpose) # basic
462
+ when 1 then @extractor.wsextractorURL2VO(url.to_s, args.first, transpose) # input params specified
463
+ when 2 then @extractor.wsextractorURL3VO(url.to_s, args.first, args.last, transpose) # input params/output flags specified
464
+ else
465
+ raise ArgumentError, "wrong number of optional arguments: #{args.size} > 2"
466
+ end
467
+
468
+ vot_node = XML::Parser.string(response).parse.root.find_first("//*[local-name()='VOTABLE']")
469
+ VORuby::VOTable.from_xml(vot_node)
470
+ end
471
+
472
+ # Perform a source extraction and crossmatch on a remotely accessible FITS file.
473
+ # <tt>url</tt> may be a string or a URI object. Otherwise exactly as for #extract_and_xmatch.
474
+ def extract_and_xmatch_from_url(url, transpose=false, *args)
475
+ transpose = transpose ? 1 : 0
476
+
477
+ response = case args.size
478
+ when 0 then @extractor.wsextractorURL1VOXmatch(url.to_s, transpose) # basic
479
+ when 1 then @extractor.wsextractorURL2VOXmatch(url.to_s, args.first, transpose) # input params specified
480
+ when 2 then @extractor.wsextractorURL3VOXmatch(url.to_s, args.first, args.last, transpose) # input params/output flags specified
481
+ else
482
+ raise ArgumentError, "wrong number of optional arguments: #{args.size} > 2"
483
+ end
484
+
485
+ vot_node = XML::Parser.string(response).parse.root.find_first("//*[local-name()='VOTABLE']")
486
+ VORuby::VOTable.from_xml(vot_node)
487
+ end
488
+ end
489
+
490
+ end
491
+ end