require 'voruby/votables/loader' # A set of classes designed to read and manipulate VOTables[http://www.ivoa.net/Documents/latest/VOT.html]. # In practice you are most likely interested in the TreeParsedVOTable. module VORuby module VOTables module VOTable # An implementation of a standard VOTable based on the VOTable XML-Schema # available at the US-VO[http://www.us-vo.org/VOTable/]. Typically one # wouldn't construct this object directly, but rather use something like # VOTable::TreeParsedVOTable to do so from an XML file. class VOTable attr_accessor :description, :definitions, :coosys, :params, :info, :resources, :id, :version # [_id_:] # The VOTable's ID attribute. # [_version_:] # The VOTable version. # [_description_:] # The VOTable DESCRIPTION element (type: Meta::Description). # [_definitions_:] # The VOTable DEFINITIONS element (type: Meta::Definitions). # [_coosys_:] # The coordinate system (COOSYS) definitions for the the VOTable # (type: Meta::CooSys). # [_params_:] # The PARAM elements for the VOTable (type: Meta::Param). # [_info_:] # The INFO elements for the VOTable (type: Meta::Info). # [_resources_:] # The RESOURCE elements for the VOTable (type: Meta::Resource). def initialize(id=nil, version='1.1', description=nil, definitions=nil, coosys=[], params=[], info=[], resources=[]) @id = id @version = version Misc::TypeCheck.new(description, Meta::Description).check() @description = description Misc::TypeCheck.new(definitions, Meta::Definitions).check() @definitions = definitions coosys.each{ |sys| Misc::TypeCheck.new(sys, Meta::CooSys).check() } @coosys = coosys params.each{ |param| Misc::TypeCheck.new(param, Meta::Param).check() } @params = params info.each{ |inf| Misc::TypeCheck.new(inf, Meta::Info).check() } @info = info resources.each{ |res| Misc::TypeCheck.new(res, Meta::Resource).check() } @resources = resources end # Get the compact string representation of the VOTable. def to_s coosys = @coosys.each{|x| x.to_s}.join('|') params = @params.each{|x| x.to_s}.join('|') info = @info.each{|x| x.to_s}.join('|') resources = @resources.each{|x| x.to_s}.join('|') return "{id=#{@id};version=#{@version};" + "description=#{@description};definitions=#{@definitions};" + "coosys=#{coosys};params=#{params};info=#{info};" + "resources=#{resources}}" end # A convenience method for accessing the fields of a votable, # rather than having to iterate through each resource and table # manually. # Returns a list of Meta::Field objects. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def fields(res=0, tbl=0) @resources[res].tables()[tbl].fields() if @resources[res] end # A convenience method for determining the number of columns in # a votable. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def number_of_fields(res=0, tbl=0) return fields(res, tbl).length() || [] end # A convenience method for accessing the row data of a votable. # Returns a list of Data::TR objects. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def rows(res=0, tbl=0) @resources[res].tables()[tbl].data().format().trs() if @resources[res].tables()[tbl].data() end # A convenience method for determining the number of data rows # in a votable. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def number_of_rows(res=0, tbl=0) return rows(res, tbl).length() end # Find the main positional right ascension and declination # fields in a votable using UCDs. This should work for both # version 1 and 1+ UCDs. # Returns a hash with the keys "ra" and "dec". # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def main_positional_fields(res=0, tbl=0) main_pos = {} field_count = 0 fields(res, tbl).each do |field| if field.ucd() != nil ucd = field.ucd() # Determine if the UCD is of intereset. if ucd.value() == 'POS_EQ_RA_MAIN' or ucd.value() == 'pos.eq.ra;meta.main' # RA main_pos["ra"] = [field_count, field] elsif ucd.value() == 'POS_EQ_DEC_MAIN' or ucd.value() == 'pos.eq.dec;meta.main' # Dec main_pos["dec"] = [field_count, field] end end field_count = field_count + 1 end return main_pos end # Find positional fields in a votable using UCDs. This should work # for both version 1 and 1+ UCDs. # Returns a hash of the positions of the fields and their # corresponding Meta::Field object. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def positional_fields(res=0, tbl=0) pos = {} field_count = 0 fields(res, tbl).each do |field| if field.ucd() != nil ucd = field.ucd() # Determine if the UCD is of interest if ucd.value() =~ /^POS.*/ or ucd.value() =~ /^pos\..*/ pos[field_count] = field end end field_count = field_count + 1 end return pos end # Find the date of observation fields in a votable using UCDs. # This should work for both version 1 and 1+ UCDs, including # experimental (e.g. VOX) ones. # Returns a hash of the positions of the fields and their # corresponding Meta::Field object. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def date_of_observation_fields(res=0, tbl=0) date_obs = {} field_count = 0 fields(res, tbl).each do |field| if field.ucd() != nil ucd = field.ucd() dt = field.datatype() # Determine if the UCD is of interest. if ucd.value() == 'VOX:Image_MJDateObs' or (dt.value() == 'double' and (ucd.value() == 'TIME_DATE' or ucd.value() == 'time.epoch')) date_obs[field_count] = field end end field_count = field_count + 1 end return date_obs end # Find time fields in a votable using UCDs. This should work # for both version 1 and 1+ UCDs, including experimental (e.g. VOX) ones. # Returns a hash of the positions of the fields and their # corresponding Meta::Field object. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def time_fields(res=0, tbl=0) times = {} field_count = 0 fields(res, tbl).each do |field| if field.ucd() != nil ucd = field.ucd() if ucd.value() =~ /^TIME.*/ or ucd.value() =~ /time\..*/ or ucd.value() == 'VOX:Image_MJDateObs' times[field_count] = field end end field_count = field_count + 1 end return times end # Find the column number(s) associated with a UCD. # Returns a list of column positions. # [_res_:] # The resource from which to extract the table in question. # [_tbl_:] # The resource from which to extract the table in question. def find_columns(ucd, res=0, tbl=0) columns = [] col_count = 0 fields = fields(res, tbl) if fields fields.each do |field| if field.ucd() != nil tbl_ucd = field.ucd() columns.push(col_count) if tbl_ucd.value() == ucd end col_count += 1 end end return columns end # Find the column number(s) associated with the # VOX:imageAccessReference UCD. # Returns a list of column positions. def image_access_reference_columns # The following is correct # find_columns('VOX:Image_AccessReference').first # The following is cause XMM ticks me off (not valid SIAP). if find_columns('VOX:Image_AccessReference').first != nil find_columns('VOX:Image_AccessReference').first else find_columns('DATA_LINK').first end end def image_ra_columns if find_columns('POS_EQ_RA_MAIN').first != nil find_columns('POS_EQ_RA_MAIN').first elsif find_columns('pos.eq.ra;meta.main').first != nil find_columns('pos.eq.ra;meta.main').first end end def image_dec_columns if find_columns('POS_EQ_DEC_MAIN').first != nil find_columns('POS_EQ_DEC_MAIN').first elsif find_columns('pos.eq.dec;meta.main').first != nil find_columns('pos.eq.dec;meta.main').first end end def image_filter_columns if find_columns('VOX:BandPass_ID').first != nil find_columns('VOX:BandPass_ID').first elsif find_columns('instr.filter').first != nil find_columns('instr.filter').first end end def image_date_obs_columns if find_columns('time.obs.start').first != nil find_columns('time.obs.start').first end end def image_telescope_columns if find_columns('instr.tel').first != nil find_columns('instr.tel').first end end def image_survey_columns if find_columns('VOX:Image_Title').first != nil find_columns('VOX:Image_Title').first elsif find_columns('ID_SURVEY').first != nil find_columns('ID_SURVEY').first end end def image_instrument_columns if find_columns('VOX:INST_ID').first != nil find_columns('VOX:INST_ID').first elsif find_columns('INST_ID').first != nil find_columns('INST_ID').first elsif find_columns('instr').first != nil find_columns('instr').first end end def image_sky_columns if find_columns('instr.skyLevel').first != nil find_columns('instr.skyLevel').first end end def image_zeropoint_columns if find_columns('arith.zp;phot').first != nil find_columns('arith.zp;phot').first end end def image_seeing_columns if find_columns('instr.obsty.seeing').first != nil find_columns('instr.obsty.seeing').first end end def image_depth_columns #if find_columns('').first != nil # find_columns('').first #end end def image_exptime_columns if find_columns('time.expo').first != nil find_columns('time.expo').first end end # Creates the header for the cart links # [_options_:] # def create_header_cart_links(options) thead_2row = Array.new thead = "\n" thead << "\n" if options[:infer_add_to_cart_ref] js = "function addSelectedRowsToCart(){" + " tbody = $$('#' + '#{options[:id]}' + ' tbody').first();" + " $A(tbody.rows).each(function(row){" + " checkbox = row.cells[0].childNodes[0];" + " if(checkbox.checked){" + " new Ajax.Request(" + " '#{options[:add_to_cart_url]}?' + checkbox.value," + " {method: 'post', " + " onLoading: function(request){" + " $('#{options[:throbber_id]}').style.display = 'inline';" + " $('#{options[:flash_notice_id]}').innerHTML = 'Adding selected rows to cart...';" + " }.bind(this), " + " onComplete: function(request){" + " $('#{options[:throbber_id]}').style.display = 'none';" + " $('#{options[:flash_notice_id]}').innerHTML = 'Selected rows added to cart.';" + " }.bind(this)" + " });" + " }" + " });" js << " new #{options[:on_complete]};" if options[:on_complete] js << "}" thead << "#{options[:add_to_cart_header_label]}\n" js = "function selectAllRows(){" + " tbody = $$('#' + '#{options[:id]}' + ' tbody').first();" + " $A(tbody.rows).each(function(row){" + " checkbox = row.cells[0].childNodes[0];" + " if(!checkbox.checked){" + " checkbox.checked = true;" + " }" + " });" + "}" buttons_text = " + " js = "function deselectAllRows(){" + " tbody = $$('#' + '#{options[:id]}' + ' tbody').first();" + " $A(tbody.rows).each(function(row){" + " checkbox = row.cells[0].childNodes[0];" + " if(checkbox.checked){" + " checkbox.checked = false;" + " }" + " });" + "}" buttons_text << " - " thead_2row.push(buttons_text) end return [thead, thead_2row] end # Create the headers for HTML table # [_access_ref_index_:] # A valid SIA VOTable will only ever have one VOX:Image_AccessReference. # [_options_:] # def create_headers(access_ref_index, options) thead, thead_2row = create_header_cart_links(options) if options[:infer_access_ref] and access_ref_index thead << "#{options[:access_ref_header_label]}\n" thead_2row.push(' ') end col_count = 0 fields(options[:res], options[:tbl]).each do |field| ucd_value = ' ' ucd_value = field.ucd.value if field.ucd if options[:infer_access_ref] and access_ref_index and col_count == access_ref_index thead_2row[1] = ucd_value else thead << "#{field.name}\n" thead_2row.push(ucd_value) end col_count += 1 end thead << " \n" thead << "\n" thead_2row.each do |h| thead << "#{h}\n" end thead << "\n" thead << "" return thead end # Convert hms of right ascension to decimal degrees. # [_ra:_] # def convert_ra_to_degrees(ra) hours, min, sec = ra.split(':') degrees = (hours.to_f() * 15.0) + (min.to_f() * 15.0 / 60.0) + (sec.to_f() * 15.0 / (60.0 * 60.0)) end # Convert dms of declination to degrees # [_dec:_] # def convert_dec_to_degrees(dec) degree, arcmin, arcsec = dec.split(':') degree_f = degree.to_f() arcmin_f = (arcmin.to_f() / 60.0) arcsec_f = (arcsec.to_f() / (60.0 * 60.0)) return (degree_f - arcmin_f - arcsec_f) if degree_f < 0 return degree_f + arcmin_f + arcsec_f end # Creates the cart parameters # [_cart_params:_] # # [_columns:_] # def create_item_cart_params(cart_params, columns) link_ref_array = [] cart_params.each do |key, value| link_ref_array.push("#{key}=#{value}") end access_ref_index = image_access_reference_columns() link_ref_array.push("resource=#{CGI.escape(columns[access_ref_index].value).to_s}") if access_ref_index ra_index = image_ra_columns() link_ref_array.push("rac=#{columns[ra_index].value.to_s}") if ra_index dec_index = image_dec_columns() link_ref_array.push("decc=#{columns[dec_index].value.to_s}") if dec_index filter_index = image_filter_columns() link_ref_array.push("filter=#{columns[filter_index].value.to_s}") if filter_index date_obs_index = image_date_obs_columns() link_ref_array.push("date_obs=#{columns[date_obs_index].value.to_s}") if date_obs_index teles_index = image_telescope_columns() link_ref_array.push("telescop=#{columns[teles_index].value.to_s}") if teles_index survey_index = image_survey_columns() link_ref_array.push("survey=#{columns[survey_index].value.to_s}") if survey_index instrum_index = image_instrument_columns() link_ref_array.push("instrument=#{columns[instrum_index].value.to_s}") if instrum_index sky_index = image_sky_columns() link_ref_array.push("sky=#{columns[sky_index].value.to_s}") if sky_index zerop_index = image_zeropoint_columns() link_ref_array.push("zeropoint=#{columns[zerop_index].value.to_s}") if zerop_index seeing_index = image_seeing_columns() link_ref_array.push("seeing=#{columns[seeing_index].value.to_s}") if seeing_index depth_index = image_depth_columns() link_ref_array.push("depth=#{columns[depth_index].value.to_s}") if depth_index exptime_index = image_exptime_columns() link_ref_array.push("exptime=#{columns[exptime_index].value.to_s}") if exptime_index return link_ref_array.join('&') end # Create body for HTML table # [_access_ref_index_:] # A valid SIA VOTable will only ever have one VOX:Image_AccessReference. # [_options_:] # def create_body(access_ref_index, options) tbody = "\n" row_count = 0 rows_data = rows(options[:res], options[:tbl]) if rows_data rows_data.each do |tr| tbody << "\n" # Specially mark up the first column to link to the image. columns = tr.tds() if options[:infer_add_to_cart_ref] and access_ref_index tbody << "\n" end if options[:infer_access_ref] and access_ref_index tbody << "#{options[:access_ref_link_label]}\n" end col_count = 0 #ra_index = image_ra_columns() #dec_index = image_dec_columns() columns.each do |td| if col_count != access_ref_index #if ra_index and col_count == ra_index # tbody << "#{convert_ra_to_degrees(td.value)}\n" #elsif dec_index and col_count == dec_index # tbody << "#{convert_dec_to_degrees(td.value)}\n" #else tbody << "#{td.value}\n" #end end col_count += 1 end tbody << "\n" row_count += 1 end end tbody << "" return tbody end # Create table for HTML VOTable # [_thead_:] # The class to assign the HTML table thead. # [_tbody_:] # The class to assign the HTML table tbody. # [_options_:] # The options for this VOTable. def create_votable(thead, tbody, options) votable = " " + "
\n" votable << "\n" votable << "#{thead}\n#{tbody}\n
" return votable end # Convert the specified table in the specified resource into an HTML # table. # [_options_:] # The options for this VOTable. def to_html(options={}) # The ID to assign to the HTML table as a whole. options[:id] = options[:id] || "#{votable}_#{Time.now.to_i}_#{rand(10000)}" options[:infer_add_to_cart_ref] = true if options[:infer_add_to_cart_ref] == nil options[:add_to_cart_header_label] = options[:add_to_cart_header_label] || 'Add to Cart' options[:cart_params] = {} if options[:cart_params] == nil #options[:add_to_cart_url] = options[:add_to_cart_url] || nil options[:throbber_src] = options[:throbber_src] || '/images/general/indicator.gif' options[:throbber_size] = options[:throbber_size] || '16x16' options[:throbber_class] = options[:throbber_class] || 'throbber' options[:throbber_id] = options[:throbber_id] || "#{options[:id]}_throbber_id" options[:flash_notice_class] = options[:flash_notice_class] || 'flash_notice' options[:flash_notice_id] = options[:flash_notice_id] || "#{options[:id]}_flash_notice_id" # Link the access reference URL associated with a row. options[:infer_access_ref] = true if options[:infer_access_ref] == nil #options[:retrieve_link_ref] = options[:retrieve_link_ref] || nil # For the access reference column, place this value in the header. options[:access_ref_header_label] = options[:access_ref_header_label] || 'URL' # For the access reference column, link this word. options[:access_ref_link_label] = options[:access_ref_link_label] || 'Retrieve' options[:ssl] = false if options[:ssl] == nil #options[:resource_link] = options[:resource_link] # The resource from which to extract the table in question. options[:res] = options[:res] || 0 # The table inside the resource from which to extract the rows in question. options[:tbl] = options[:tbl] || 0 # The boolean value to show HTML table border options[:show_border] = false if options[:show_border] == nil # The class to assign the HTML table as a whole. options[:table_class] = options[:table_class] || 'votable' # The class to assign the header of the HTML table. options[:header_class] = options[:header_class] || 'header' # The class to assign the body of the HTML table. options[:body_class] = options[:body_class] || 'body' # The class to assign the HTML table body rows. options[:row_classes] = options[:row_classes] || ['row1', 'row2'] begin # A valid SIA VOTable will only ever have one VOX:Image_AccessReference. access_ref_index = image_access_reference_columns() return create_votable(create_headers(access_ref_index, options), create_body(access_ref_index, options), options) rescue Exception => e title = 'Error...' message = "VORuby error: #{e.message}
#{e.backtrace}" message << "
#{@resources[0].info[0].text().to_s()}" if @resources[0].info[0] create_message(title, message, options) end end # Creates a message # [_title_:] # # [_message_:] # # [_options_:] # The options for this VOTable. def create_message(title, message, options) table = "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
#{title}
" return table end # # [_attributes_:] # # [_name_:] # def self._find_attr_value(attributes, name) attributes.each do |qname, value| return value if qname.name == name end return nil end end end end end