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 << " \n"
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_2row.each do |h|
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 = "#{h} \n"
end
thead << "
#{title} | \n" + "
---|
\n" + " |