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: VOTABLE::META::Description).
# [_definitions_:]
# The VOTable DEFINITIONS element (type: VOTABLE::META::Definitions).
# [_coosys_:]
# The coordinate system (COOSYS) definitions for the the VOTable
# (type: VOTABLE::META::CooSys).
# [_params_:]
# The PARAM elements for the VOTable (type: VOTABLE::META::Param).
# [_info_:]
# The INFO elements for the VOTable (type: VOTABLE::META::Info).
# [_resources_:]
# The RESOURCE elements for the VOTable (type: VOTABLE::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 VOTABLE::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 VOTABLE::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()
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 VOTABLE::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 VOTABLE::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 VOTABLE::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
# Create headers for HTML table
# [_res_:]
# The resource from which to extract the table in question.
# [_tbl_:]
# The table inside the resource from which to extract the rows in question.
# [_infer_add_to_cart_ref_:]
#
# [_add_to_cart_header_value_:]
#
# [_infer_access_ref_:]
# Link the access reference URL associated with a row.
# [_access_ref_header_value_:]
# For the access reference column, place this value in the header.
# [_access_ref_col_:]
# A valid SIA VOTable will only ever have one VOX:Image_AccessReference.
# [_header_class_:]
# The class to assign the header of the HTML table.
def create_headers(res, tbl,
infer_add_to_cart_ref, add_to_cart_header_value,
infer_access_ref, access_ref_header_value, access_ref_col,
header_class, id=nil)
headers = Array.new
thead = "\n"
thead << "\n"
if infer_add_to_cart_ref
thead << " \n"
thead << "#{add_to_cart_header_value} \n"
headers.push("*")
end
if infer_access_ref
thead << "#{access_ref_header_value} \n"
headers.push(' ')
end
col_count = 0
fields(res, tbl).each do |field|
if infer_access_ref and col_count == access_ref_col
headers[1] = field.ucd.value
else
thead << "#{field.name} \n"
headers.push(field.ucd.value)
end
col_count += 1
end
thead << " \n"
headers.each do |h|
thead << " \n"
thead << ""
return thead
end
def create_add_to_cart_link(link_ref, columns)
access_ref_col = image_access_reference_columns()
link_ref << '&resource=' + CGI.escape(columns[access_ref_col].value).to_s if access_ref_col
ra_col = image_ra_columns()
link_ref << '&rac=' + columns[ra_col].value.to_s if ra_col
dec_col = image_dec_columns()
link_ref << '&decc=' + columns[dec_col].value.to_s if dec_col
filter_col = image_filter_columns()
link_ref << '&filter=' + columns[filter_col].value.to_s if filter_col
date_obs_col = image_date_obs_columns()
link_ref << '&date_obs=' + columns[date_obs_col].value.to_s if date_obs_col
teles_col = image_telescope_columns()
link_ref << '&telescop=' + columns[teles_col].value.to_s if teles_col
survey_col = image_survey_columns()
link_ref << '&survey=' + columns[survey_col].value.to_s if survey_col
instrum_col = image_instrument_columns()
link_ref << '&instrument=' + columns[instrum_col].value.to_s if instrum_col
sky_col = image_sky_columns()
link_ref << '&sky=' + columns[sky_col].value.to_s if sky_col
zerop_col = image_zeropoint_columns()
link_ref << '&zeropoint=' + columns[zerop_col].value.to_s if zerop_col
seeing_col = image_seeing_columns()
link_ref << '&seeing=' + columns[seeing_col].value.to_s if seeing_col
depth_col = image_depth_columns()
link_ref << '&depth=' + columns[depth_col].value.to_s if depth_col
exptime_col = image_exptime_columns()
link_ref << '&exptime=' + columns[exptime_col].value.to_s if exptime_col
return link_ref
end
# Create body for HTML table
# [_res_:]
# The resource from which to extract the table in question.
# [_tbl_:]
# The table inside the resource from which to extract the rows in question.
# [_infer_add_to_cart_ref_:]
#
# [_add_to_cart_link_value_:]
#
# [_add_to_cart_link_ref_:]
#
# [_infer_access_ref_:]
# Link the access reference URL associated with a row.
# [_access_ref_link_value_:]
# For the access reference column, link this word.
# [_access_ref_col_:]
# A valid SIA VOTable will only ever have one VOX:Image_AccessReference.
# [_body_class_:]
# The class to assign the body of the HTML table.
# [_row_classes_:]
# The class to assign the HTML table body rows.
def create_body(res, tbl,
infer_add_to_cart_ref, add_to_cart_link_value, add_to_cart_link_ref,
infer_access_ref, access_ref_link_value, access_ref_col,
body_class, row_classes)
tbody = "#{h} \n"
end
thead << "
#{title} | \n" table << "
---|
\n" table << " |