=begin
= NumRu::GrADS_Gridded -- a class for GrADS gridded datasets
by T Horinouchi and R Mizuta
==Overview
a GrADS_Gridded object corresponds to a GrADS control file,
through which the users can also access its binary data file(s).
==Current Limitations
* option 365_day_calendar is not interpreted
* Partial support of the "template" option
(only %y2,%y4,%m1,%m2,%d1,%d2,%h1,%h2,%n2)
(Time is assumed to be increasing monotonically).
==Class Methods
---GrADS_Gridded.new(ctlfilename, mode="r")
same as GrADS_Gridded.open
---GrADS_Gridded.open(ctlfilename, mode="r")
make a new GrADS_Gridded object.
ARGUMENTS
* ctlfilename (String): name of the control file to open
* mode (String): IO mode. "r" (read only) or "w" (write only).
REMARK
* You can modify the object through instance methods even if mode=="r".
In that case, the modification will not be written in the original
control file.
---GrADS_Gridded.create(ctlfilename, noclobber=false, share=false)
make a new GrADS_Gridded object with creating a new control file
REMARK
* It is used with writing methods, but currently writing methods
does not work.
==Methods
---ndims
returns the number of dimensions in the file (always 4).
---nvars
returns the number of variables in the file.
---natts
returns the number of attributes of the variable.
---path
returns the path of the control file.
---put_att( attname, value )
set global attribute
ARGUMENTS
* attrname (String): name of an attribute
* value (String): value of an attribute
---def_var(name="noname",nlev=0,option="99",description="")
define a variable
ARGUMENTS
* name (String): name of the variable
* nlev (Integer): number of vertical levels
* option (String): variable placement option
("99": normal, "-1,30": transpose lon and lat)
---var( varname=nil )
opens an existing variable in the file.
ARGUMENTS
* varname (String): name of the variable to open
RETURN VALUE
* a GrADSVar object.
---vars( names=nil )
opens existing variables in the file.
ARGUMENTS
* names (Array): names(String) of the variable to open
RETURN VALUE
* Array of GrADSVar objects.
returns all variables if names==nil
---get_att( key=nil )
returns tha value of the global attribute
---dim_names
returns the names of all dimensions in the control file.
---var_names
returns the names of all variables in the control file.
---att_names
returns the names of all the global attributes.
---to_ctl
returns the contents of the corresponding control file as a String.
REMARK
* The contents is reconstructed from the internal data of the object.
Therefore, even when the object is based on a read-only control file,
it is not necessarily the same as the original one. It is especially
true when the object was modified after it is opened.
---get(name, z, t)
reads the binary data and returns as a NArray.
ARGUMENTS
* name (String): name of the variable to read
* z (Integer, currently): vertical level to read (0,1,2,...; starting
from 0). Currently only one vertical levels must be chosen, but in the
future, it is planned to support multiple levels.
* t (Integer, currently): time to read (0,1,2,...; starting
from 0). Currently only one time must be chosen, but in the
future, it is planned to support multiple times.
---put(ary)
writes the NArray on the binary data file.
ARGUMENTS
* ary (NArray): data to write.
---varnames
Returns names of the variable in the GrADS file as an Array in the order
placed.
---dimensions
Returns info on the four dimensions.
RETURN VALUE
* an Array of 4 elements: dimension[0] for x, dimension[1] for y,
dimension[2] for z, and dimension[3] for t. Each of them is a
Hash like the following:
{:name=>"x",
:len=>132,
:flag=>"LINEAR",
:spec=>"-0.7500 1.5000",
:start=>-0.75, :increment=>1.5,
:description=>"longitude",
:units=>"degrees_east"}
Here, :len, :flag, and :spec are directly from the control file, while
others are derived properties for internal use.
WARNING
* Each elements of the return value is not a clone but is a direct
association to an internal object of the object. Therefore, to
modify it is to modify the object. That is, dimensions[0][:len]=10
would actually change the internal variable, while dimensions[0]=nil
has no effect (the former is a substitution IN a Hash, while the latter
is a substitution OF the Hash).
---get_dim(dim)
returns positions of a dimension as an NArray.
ARGUMENTS
* dim (String): a dimension name
RETURN VALUE
* an NArray
---title
---title=
get/set the title
---undef
---undef=
get/set the undef value
---dset
---dset=
get/set the dset string
= GrADSVar -- a class for a variable of GrADS gridded datasets
by R Mizuta
==Overview
a GrADSVar object corresponds to one variable in a GrADS control file.
It is intended to behave as a correspondent of a NetCDFVar object.
==Current Limitations
* Only a part of the methods can work.
* Writing methods are not supported.
==Class Methods
---GrADSVar.new(file, varname)
make a new GrADSVar object.
ARGUMENTS
* file (GrADS_Gridded or String): a GrADS_Gridded object or
a name of the control file to open
* varname (String): name of the variable to open
==Methods
---shape_ul0
returns the shape of the variable, but the length of the unlimited
dimension is set to zero.
RETURN VALUE
* Array. [length of 0th dim, length of 1st dim,.. ]
---shape_current
returns the current shape of the variable.
RETURN VALUE
* Array. [length of 0th dim, length of 1st dim,.. ]
---dim_names
returns the names of all dimensions of the variable.
---att_names
returns the names of all attributes of the variable.
---name
returns the name of the variable.
---ndims
returns the number of dimensions in the file (always 4).
---rank
alias of ndims
---vartype
returns "sfloat" in order to behave as NetCDFVar#vartype.
---natts
returns the number of attributes of the variable.
---file
returns the file name that controls the variable.
---get_att( name=nil )
returns tha value of the attribute of the variable.
---put_att( name, value )
set an attribute of the variable.
ARGUMENTS
* name (String): name of an attribute
* value (String): value of an attribute
---get(hash=nil)
returns values of the variable.
ARGUMENTS
* hash (Hash) : Optional argument to limit the portion of the
variable to output values. If omitted, the whole variable is
subject to the output. This argument accepts a Hash whose keys
contain either "index" or a combination of "start","end", and
"stride". The value of "index" points the index of a scalar
portion of the variable. The other case is used to designate a
regularly ordered subset, where "start" and "end" specifies
bounds in each dimension and "stride" specifies intervals in
it. As in Array "start", "end", and "index" can take negative
values to specify index backward from the end. However,
"stride" has to be positive, so reversing the array must be
done afterwards if you like.
RETURN VALUE
* an NArray object
REMARK
"stride","index" is not supported yet.
---[]
Same as GrADSVar#get but a subset is specified as in the method []
of NArray.
=end
require "date"
require "narray_miss"
require "numru/gphys/attribute"
module NumRu
class GrADS_Gridded
class << self
alias open new
def create(ctlfilename,noclobber=false,share=false)
#if(noclobber)
# raise "noclobber = true is not supported."
#end
if(share)
raise "share = true is not supported."
end
#if (File.exists?(ctlfilename))
if(noclobber && File.exists?(ctlfilename))
print "#{ctlfilename} already exists.\n"
print "overwrite #{ctlfilename} (y/n)? "
ans = gets[0].chr
if ans != "y"
raise "#{ctlfilename} already exists."
end
end
# GrADS_Gridded.new(ctlfilename, "w+")
GrADS_Gridded.new(ctlfilename, "w")
end
end
def initialize(ctlfilename, mode="r")
case(mode)
when /^r/
@mode = 'r'
when /^w/
@mode = 'w'
else
raise ArgumentError, "Unsupported IO mode: #{mode}"
end
# @ctlfile = File.open(ctlfilename, mode)
@options = { # initialization
"yrev"=>nil,
"zrev"=>nil,
"sequential"=>nil,
"byteswapped"=>nil,
"template"=>nil,
"big_endian"=>nil,
"little_endian"=>nil,
"cray_32bit_ieee"=>nil,
}
case(@mode)
when('r')
if (File.exists?(ctlfilename))
@ctlfile = File.open(ctlfilename, mode)
parse_ctl
else
raise "File #{ctlfilename} does not exist."
end
when('w')
@ctlfile = File.open(ctlfilename, mode)
@dimensions = []
@variables = []
#<attributes>
@dset = nil
# @title = nil
@title = ""
@undef = nil
@fileheader_len = 0
#<internal control parameters>
@define_mode = true
@ctl_dumped = false
else
raise ArgumentError, "Unsupported IO mode: #{@mode}"
end
end
def close
@ctlfile.close
end
def path
@ctlfile.path
end
def ndims
4
end
def nvars
@variables.length
end
def natts
2
end
def put_att(key,value)
case key
when "dset"
@dset = value
when "title"
@title = value
when "undef"
@undef = value
else
if ! (value.is_a?(TrueClass) || value.is_a?(NilClass) )
raise ArgumentError, "2nd arg: not a true nor nil"
end
if (@options.has_key?(key))
@options[key] = value
else
raise "Invalid/unsupported option: "+key
end
end
end
def def_var(name="noname",nlev=0,option="99",description="")
@variables.push({:name=>name.to_s, :nlev=>nlev.to_s,
:option=>option.to_s, :description=>description.to_s})
end
def var( varname=nil )
GrADSVar.new(self,varname)
end
def vars( names=nil ) # return all if names==nil
# if names == nil
# vars = (0..nvars()-1).collect{ |varid| id2var(varid) }
# else
raise TypeError, "names is not an array" if ! names.is_a?(Array)
vars = names.collect{|name| var(name)}
raise ArgumentError, "One or more variables do not exist" if vars.include?(nil)
# end
vars
end
# def att
def get_att( key=nil )
case(key)
when("dset")
att = @dset
when("title")
att = @title
when "undef"
att = @undef
else
raise "Invalid/unsupported option: "+key
end
att
end
# def fill=
# def each_dim
# def each_var
# def each_att
def dim_names
ary = Array.new()
@dimensions.each{|dim| ary.push(dim[:name])}
ary
end
def var_names
ary = Array.new()
@variables.each{|dim| ary.push(dim[:name])}
ary
end
def att_names
ary = ["dset","title","undef"]
ary
end
def to_ctl
if( !@dimensions[3][:spec] )
start = generate_starttime(@dimensions[3][:startdatetime])
increment = generate_timeincrement(@dimensions[3][:increment],@dimensions[3][:increment_units])
@dimensions[3][:spec] = "#{start} #{increment}"
end
@title = "<no title>" if @title==""
@undef = -999.0 if @undef==nil
return <<EOS
DSET #{if @dset[0]=="/" then @dset else "^"+@dset end}
TITLE #{@title}
UNDEF #{@undef}
OPTIONS #{op=""; @options.each{|key,val| op += key+" " if(val)}; op}
XDEF #{@dimensions[0][:len]} #{@dimensions[0][:flag]} #{@dimensions[0][:spec]}
YDEF #{@dimensions[1][:len]} #{@dimensions[1][:flag]} #{@dimensions[1][:spec]}
ZDEF #{@dimensions[2][:len]} #{@dimensions[2][:flag]} #{@dimensions[2][:spec]}
TDEF #{@dimensions[3][:len]} #{@dimensions[3][:flag]} #{@dimensions[3][:spec]}
VARS #{nvars}
#{@variables.collect{|i| i[:name]+" "+i[:nlev].to_s+" "+i[:option].to_s+
" "+i[:description]}.join("\n")}
ENDVARS
EOS
end
def inspect
return <<EOS
#{self.class}
file: #{@ctlfile.path}
DSET #{@dset}
OPTIONS #{@options.inspect}
XDIM #{@dimensions[0].inspect}
YDIM #{@dimensions[1].inspect}
ZDIM #{@dimensions[2].inspect}
TDIM #{@dimensions[3].inspect}
VARS
#{@variables.collect{|i| " "+i[:name]+"\t"+i[:nlev].to_s+"\t"+i[:option].to_s+
"\t"+i[:description]}.join("\n")}
ENDVARS
EOS
end
def put(ary)
if( ary.class == NArrayMiss )
raise "UNDEF is not specified" if @undef == nil
ary = ary.to_na(@undef)
end
ary = convert_endian_write(ary)
raise "DSET is not specified" if @dset == nil
putfile = File.open(@dset,"wb")
putfile << ary.to_s
putfile.close
end
def get(name, z, t, lonlat=nil)
# t: [0,1,2,..] : record number
# time: [0,1/24,2/24,...] days since 00:00Z01jan2000 : Numeric with units
# date: [00:00Z01jan2000,01:00Z01jan2000,...] : DateTime class
if ( @options["template"] )
ary_time = get_dim(@dimensions[3])
start_date = @dimensions[3][:startdatetime]
case dimensions[3][:increment_units]
when 'years'
target_date = start_date >> (ary_time[t]*12)
when 'months'
target_date = start_date >> ary_time[t]
when 'days'
target_date = start_date + ary_time[t]
when 'hours'
target_date = start_date + Rational((ary_time[t]*60).to_i,24*60)
when 'minutes'
target_date = start_date + Rational(ary_time[t].to_i,24*60)
end
# substitute DSET by the target file name
# and determine first record in the target file (="init")
init_year = target_date.year
init_month = target_date.month
init_day = target_date.day
init_hour = target_date.hour
init_min = target_date.min
@dset_r = @dset.dup
if ( @dset_r =~ /%y[24]/ )
init_month = 1 if !( @dset_r =~ /%m[12]/ )
init_day = 1 if !( @dset_r =~ /%d[12]/ )
init_hour = 0 if !( @dset_r =~ /%h[12]/ )
init_min = 0 if !( @dset_r =~ /%n2/ )
@dset_r.gsub!( /%y2/, sprintf("%02d",target_date.year%100) )
@dset_r.gsub!( /%y4/, sprintf("%04d",target_date.year) )
end
if ( @dset_r =~ /%m[12]/ )
init_day = 1 if !( @dset_r =~ /%d[12]/ )
init_hour = 0 if !( @dset_r =~ /%h[12]/ )
init_min = 0 if !( @dset_r =~ /%n2/ )
@dset_r.gsub!( /%m1/, sprintf("%d",target_date.month) )
@dset_r.gsub!( /%m2/, sprintf("%02d",target_date.month) )
end
if ( @dset_r =~ /%d[12]/ )
init_hour = 0 if !( @dset_r =~ /%h[12]/ )
init_min = 0 if !( @dset_r =~ /%n2/ )
@dset_r.gsub!( /%d1/, sprintf("%d",target_date.day) )
@dset_r.gsub!( /%d2/, sprintf("%02d",target_date.day) )
end
if ( @dset_r =~ /%h[12]/ )
init_min = 0 if !( @dset_r =~ /%n2/ )
@dset_r.gsub!( /%h1/, sprintf("%d",target_date.hour) )
@dset_r.gsub!( /%h2/, sprintf("%02d",target_date.hour) )
end
if ( @dset_r =~ /%n2/ )
@dset_r.gsub!( /%n2/, sprintf("%02d",target_date.min) )
end
init_date = DateTime.new(init_year,init_month,init_day,init_hour,init_min)
init_t = 0
ary_time[0..t].to_a.each_with_index{ |time, idx|
case dimensions[3][:increment_units]
when 'years'
before_target_file = ((start_date >> (time*12)) < init_date)
when 'months'
before_target_file = ((start_date >> time) < init_date)
when 'days'
before_target_file = ((start_date + time) < init_date)
when 'hours'
before_target_file = ((start_date + Rational((time*60).to_i,24*60)) < init_date)
when 'minutes'
before_target_file = ((start_date + Rational(time.to_i,24*60)) < init_date)
end
init_t = idx + 1 if before_target_file
}
t_in_target_file = t - init_t
start_byte = start_byte(name, z, t_in_target_file)
@datafile = File.open(@dset_r,"rb")
else
start_byte = start_byte(name, z, t)
@datafile = File.open(@dset,"rb")
end
@x_len = @dimensions[0][:len]
@y_len = @dimensions[1][:len]
if(lonlat)
if !(lonlat.is_a?(Array) || lonlat.is_a?(NArray))
raise "lonlat must be given Array or NArray"
end
lon_str = lonlat[0]
lon_end = lonlat[1]
lat_str = lonlat[2]
lat_end = lonlat[3]
if( @map[name][:xytranspose] )
@datafile.pos = start_byte + @y_len*lon_str*@map[name][:byte]
readdata = @datafile.read(@y_len*(lon_end-lon_str+1)*@map[name][:byte])
if( readdata == nil )
raise "File Read Error: #{@datafile.path}, " +
"#{@x_len*(lat_end-lat_str+1)*@map[name][:byte]} bytes " +
"at #{@datafile.pos} bytes\n" +
"specified by #{@ctlfile.path}, #{name} ( z=#{z}, t=#{t} ) (Perhaps there is a mismatch between the control and data files)"
end
ary = NArray.to_na(readdata, @map[name][:type], @y_len, lon_end-lon_str+1)
ary = convert_endian_read(ary)
ary = ary[lat_str..lat_end,true].transpose
else
@datafile.pos = start_byte + @x_len*lat_str*@map[name][:byte]
readdata = @datafile.read(@x_len*(lat_end-lat_str+1)*@map[name][:byte])
if( readdata == nil )
raise "File Read Error: #{@datafile.path}, " +
"#{@x_len*(lat_end-lat_str+1)*@map[name][:byte]} bytes " +
"at #{@datafile.pos} bytes\n" +
"specified by #{@ctlfile.path}, #{name} ( z=#{z}, t=#{t} ) (Perhaps there is a mismatch between the control and data files)"
end
ary = NArray.to_na(readdata, @map[name][:type], @x_len, lat_end-lat_str+1)
ary = convert_endian_read(ary)
ary = ary[lon_str..lon_end,true]
end
else
@datafile.pos = start_byte
readdata = @datafile.read(@x_len*@y_len*@map[name][:byte])
if( readdata == nil )
raise "File Read Error: #{@datafile.path}, " +
"#{@x_len*(lat_end-lat_str+1)*@map[name][:byte]} bytes " +
"at #{@datafile.pos} bytes\n" +
"specified by #{@ctlfile.path}, #{name} ( z=#{z}, t=#{t} ) (Perhaps there is a mismatch between the control and data files)"
end
ary = NArray.to_na(readdata, @map[name][:type], @dimensions[0][:len],@dimensions[1][:len])
ary = convert_endian_read(ary)
ary = ary.transpose if ( @map[name][:xytranspose] )
end
@datafile.close
ary
end
attr_accessor(:title, :undef, :dset, :dimensions, :variables)
attr_reader(:ctlfile)
def get_dim(dim)
name = dim[:name]
if (dim[:levels])
var_dim = NArray.to_na(dim[:levels])
elsif (dim[:start] && dim[:increment] && dim[:len])
var_dim = NArray.float(dim[:len]).indgen!*dim[:increment]+dim[:start]
else
raise "cannot define dimension "+name
end
var_dim = var_dim[-1..0] if (name == 'y' && @options["yrev"])
var_dim = var_dim[-1..0] if (name == 'z' && @options["zrev"])
return var_dim
end
def varnames
@variables.collect{|i| i[:name]}
end
def get_alldim
ax = Hash.new
@dimensions.each{|dim|
name = dim[:name]
ax[name] = get_dim(dim)
}
return ax
end
def ctlfilename # obsolete
@ctlfile.path
end
########## private methods #############
private
def parse_ctl
@ctlfile.rewind
@fileheader_len = 0 # defalut value
@title = ""
@variables = [] # initalization
@dimensions = [] # initalization
while ( line = @ctlfile.gets )
case(line)
when /^\s*\*/,/^\s*$/
# do nothing
when /^\s*DSET\s*(\S*)/i
if ($1)
# @dset = $1
# @dset = $1.gsub(/(\^)/,"./") # replace "^" with "./"
@dset = $1.gsub(/(^\^|^([^\/]))/,File.dirname(@ctlfile.path)+'/\2')
# relative -> absolute path (e.g. '^a' or 'a' -> '/hoge/a')
# @datafile = File.open(@dset,@mode)
else
raise "Invalid line: "+line
end
when /^\s*TITLE\s*(\S+.*)$/i
if ($1)
@title = $1
else
raise "Invalid line: "+line
end
when /^\s*UNDEF\s*(\S*)/i
if ($1)
@undef = $1.to_f
else
raise "Invalid line: "+line
end
when /^\s*FILEHEADER\s*(\S*)/i
if ($1)
@fileheader_len = $1.to_i
else
raise "Invalid line: "+line
end
when /^\s*BYTESWAPPED\s*$/i
@options["byteswapped"] = true
when /^\s*OPTIONS\s*(\S+.*)$/i
if ($1)
# $1.split.each{ |opt|
$1.downcase.split.each{ |opt|
if (@options.has_key?(opt))
@options[opt] = true
else
raise "Invalid/unsupported option: "+opt
end
}
else
raise "Invalid line: "+line
end
when /^\s*[XYZT]DEF/i
# /^\s*([XYZT])DEF\s+(\d+)\s+(\S+)\s+(.*)$/i =~ line
/^\s*([XYZT])DEF\s+(\d+)\s+(\S+)+(.*)$/i =~ line
if ( (len=$2) && (flag=$3) && (spec=$4))
dim = {:len=>len.to_i, :flag=>flag, :spec=>spec}
case $1
when /X/i
idim=0
dim[:name] = 'x'
dim[:description] = 'longitude'
dim[:units] = 'degrees_east'
when /Y/i
idim=1
dim[:name] = 'y'
dim[:description] = 'latitude'
dim[:units] = 'degrees_north'
when /Z/i
idim=2
dim[:name] = 'z'
dim[:description] = 'pressure level'
dim[:units] = 'hPa'
when /T/i
dim[:name] = 't'
dim[:description] = 'time'
idim=3
end
if (idim!=3)
if (dim[:flag] =~ /LINEAR/i)
begin
dim[:start],dim[:increment] = spec.split.collect!{|i| i.to_f}
rescue NameError,StandardError
raise $!.to_s+"\nCannot read start and increment from: "+spec
end
elsif (dim[:flag] =~ /LEVELS/i)
dim[:levels] = []
pos = @ctlfile.pos # back up for a one-line rewind
# dim[:spec] = "\n"
dim[:spec] += levs = spec+"\n"
if( /^\s*[\d\-\.]/ =~ levs )
dim[:levels] += levs.split.collect!{|i| i.to_f}
end
# while (dim[:spec] += levs = @ctlfile.gets)
while (levs = @ctlfile.gets)
if( /^\s*[\d\-\.]/ =~ levs )
dim[:spec] += levs
dim[:levels] += levs.split.collect!{|i| i.to_f}
#p '### levels',dim[:levels].length," ",levs.split
pos = @ctlfile.pos # back up for a one-line rewind
else
@ctlfile.pos = pos # one-line rewind (note: IO#lineno= doesn't work for this purpose)
break
end
end
else
raise "invalid or not-yet-supported dimension flag: "+dim[:flag]
end
else
# idim = 3 --- time
if (dim[:flag] =~ /LINEAR/i)
start,increment= spec.split
dim[:start] = 0.0
dim[:startdatetime] = parse_starttime(start)
dim[:increment],dim[:increment_units] = parse_timeincrement(increment)
dim[:units] = dim[:increment_units]+" since "+dim[:startdatetime].strftime("%Y-%m-%d %H:%MZ")
else
raise "invalid dimension flag(only LINEAR is available for time)"
end
end
@dimensions[idim]=dim
else
raise "Invalid line: "+line
end
when /^\s*VARS/i
total_lev_bytes=0
while ( vline = @ctlfile.gets )
case(vline)
when /^\s*\*/,/^\s*$/
# do nothing
when /^\s*ENDVARS/i
break
else
vline =~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+.*?)\s*$/
# vline =~ /^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(\S+.*?)\s*$/
if( !($1 && $2 && $3 && $4) )
raise "Something is wrong with this line: "+vline
end
name = $1; option = $3; description = $4; units = $5
nlev = max($2.to_i,1)
# total_levs += nlev
# case ($3)
case (option)
when /^0/,/^99/,/^-1,30/
byte=4; type="sfloat"
when /^-1,40,4/
byte=4; type="int"
when /^-1,40,2,-1/
byte=2; type="sint"
when /^-1,40,1/
byte=1; type="byte"
else
p option
raise "invalid or unsupported variable placement option: "
end
total_lev_bytes += nlev*byte
# @variables.push({:name=>$1,:nlev=>nlev,:option=>$3,
# :description=>$4})
if( ! $5 )
@variables.push({:name=>name,:nlev=>nlev,:option=>$3,:type=>type,:byte=>byte,
:description=>description})
else
@variables.push({:name=>$1,:nlev=>nlev,:option=>$3,:type=>type,:byte=>byte,
:description=>description, :units=>$5})
end
end
end
@map=Hash.new
cum_lev_bytes = 0
@variables.each{|i|
varname = i[:name]
@map[varname]={:offset=>@fileheader_len,:nlev=>i[:nlev],
:start=>cum_lev_bytes,:zstep=>i[:byte],:tstep=>total_lev_bytes,
:type=>i[:type],:byte=>i[:byte]}
if i[:option] =~ /^-1,30/
@map[varname][:xytranspose]=true
end
cum_lev_bytes += i[:nlev]*i[:byte]
}
end
end
#<check whether all the mandatory specifications are done>
for i in 0..3
raise "#{i}-th dimension is not found " if( ! @dimensions[i] )
end
raise "UNDEF field is not found" if(!@undef)
raise "DSET field is not found" if(!@dset)
raise "VARS field is not found" if(!@variables)
# #<post processing>
# @xybytes = 4 * @dimensions[0][:len] * @dimensions[1][:len]
# @xy = @dimensions[0][:len] * @dimensions[1][:len]
end
def start_byte(name, level, time)
# offset to read an xy section of the variable with NAME
# at LEVEL(counted from 0) at TIME(conted from 0)
if (map = @map[name] )
if (level<0 || level>=map[:nlev])
raise "Level #{level} is out of the range of the variable #{name}"
end
if (time<0 || time>=@dimensions[3][:len])
raise "Time #{time} is not in the data period"
end
iblock = map[:start]+level*map[:zstep]+time*map[:tstep]
str_byte = map[:offset] + @dimensions[0][:len] * @dimensions[1][:len] * iblock
if( @options["sequential"] )
str_byte += iblock*8 + 4
end
else
raise "Variable does not exist: "+name
end
str_byte
end
def parse_starttime(string)
## interpret the hh:mmZddmmmyyyy format for grads
if (/([\d:]*)Z(.*)/i =~ string)
stime = $1
sdate = $2
else
# must be date, not time, since month and year are mandatory
sdate = string
stime = ''
end
if ( /(\d\d):(\d\d)/ =~ stime )
begin
shour = $1
smin = $2
hour = shour.to_i
min = smin.to_i
rescue StandardError,NameError
raise "Cannot convert hour or time into interger: "+stime
end
else
hour = 0
min = 0
end
if ( /(\d*)(\w\w\w)(\d\d\d\d)/ =~ sdate )
sday = $1
smon = $2
syear = $3
begin
if(sday == "")
day = 1
else
day = sday.to_i
end
year = syear.to_i
mon=['jan','feb','mar','apr','may','jun','jul','aug','sep',
'oct','nov','dec'].index(smon.downcase) + 1
rescue StandardError, NameError
raise "Could not parse the date string: "+sdate+"\n"+$!
end
else
raise "The date part must be [dd]mmmyyyy, but what was given is: "+sdate
end
return DateTime.new(year,mon,day,hour,min)
end
def parse_timeincrement(string)
if ( /(\d+)(\w\w)/ =~ string )
sincrement = $1.to_i
sunits = $2
case sunits
when /mn/i
# fact = 1.0/1440.0 # factor to convert into days
# units = 'days'
fact = 1.0
units = 'minutes'
increment = sincrement * fact
when /hr/i
# fact = 1.0/24.0
# units = 'days'
fact = 1.0
units = 'hours'
increment = sincrement * fact
when /dy/i
fact = 1.0
units = 'days'
increment = sincrement * fact
when /mo/i
fact = 1.0
units = 'months'
increment = sincrement * fact
when /yr/i
fact = 1.0
units = 'years'
increment = sincrement * fact
else
raise "invalid units: "+sunits
end
else
raise "invalid time-increment string: #{sunits}"
end
return [increment, units]
end
def max(a,b)
a>b ? a : b
end
def generate_starttime(datetime)
mon = Date::ABBR_MONTHNAMES[datetime.mon]
return datetime.strftime("%H:%MZ%d#{mon}%Y")
end
def generate_timeincrement(increment,units)
increment = increment[0] if(increment.class==NArray)
sincrement = increment
case units
when /min/i
sunits = "mn"
when /hour/i
sunits = "hr"
when /day/i
sunits = "dy"
when /mon/i
sunits = "mo"
when /year/i
sunits = "yr"
else
raise "invalid time increment units: #{units}"
end
if(sunits == "dy")
if( increment < 0.0 )
raise "invalid time increment: #{increment}"
elsif( increment >= 365 && (increment.to_f/365) / (increment.to_i/365) < 1.003 )
sincrement = (increment/365).to_i
sunits = "yr"
elsif( increment >= 28 && (increment.to_f/28) / (increment.to_i/28) < 1.11 )
sincrement = (increment/28).to_i
sunits = "mo"
elsif( increment < 1.0 )
if( increment >= 0.0416 && (increment*1440)%60 < 5 )
sincrement = (increment*24).to_i
sunits = "hr"
else
sincrement = increment*1440
sunits = "mn"
end
end
end
return sprintf("%2d",sincrement)+sunits
end
def convert_endian_read(ary)
if( (@options["big_endian"] || @options["cray_32bit_ieee"]) &&
@options["little_endian"] )
raise "endian specification error"
end
if( @options["big_endian"] || @options["cray_32bit_ieee"] )
ary = ary.ntoh
elsif( @options["little_endian"] )
ary = ary.vtoh
elsif( @options["byteswapped"] )
ary = ary.swap_byte
end
ary
end
def convert_endian_write(ary)
if( (@options["big_endian"] || @options["cray_32bit_ieee"]) &&
@options["little_endian"] )
raise "endian specification error"
end
if( @options["big_endian"] || @options["cray_32bit_ieee"] )
ary = ary.hton
elsif( @options["little_endian"] )
ary = ary.htov
elsif( @options["byteswapped"] )
ary = ary.swap_byte
end
ary
end
end
class GrADSVar
def initialize(file,varname)
@varname = varname
if file.is_a?(String)
file = GrADS_Gridded.open(file)
elsif ! file.is_a?(GrADS_Gridded)
raise ArgumentError, "1st arg must be a GrADS_Gridded or a file name"
end
@ctl = file # control file name
# @ctl = ctl
@idim = -1
for i in 0...@ctl.dimensions.length
# p @ctl.dimensions[i][:name]
if @varname == @ctl.dimensions[i][:name]
@idim = i
end
end
@attr = NumRu::Attribute.new
if( @idim != -1 )
@dimensions = [ @ctl.dimensions[@idim] ]
@rank = 1
@attr[:name] = @dimensions[0][:name]
@attr[:long_name] = @dimensions[0][:description]
@attr[:units] = @dimensions[0][:units]
@shape = [@dimensions[0][:len]]
else
@dimensions = @ctl.dimensions
@rank = @dimensions.length
found = false
for i in 0...@ctl.variables.length
if @varname == @ctl.variables[i][:name]
@attr[:long_name] = @ctl.variables[i][:description]
@attr[:nlev] = @ctl.variables[i][:nlev].to_s
@shape = [@dimensions[0][:len],@dimensions[1][:len],
@dimensions[2][:len],@dimensions[3][:len]]
@shape[2] = @ctl.variables[i][:nlev].to_i if( @ctl.variables[i][:nlev] )
@attr[:missing_value] = NArray.sfloat(1).fill!(@ctl.undef)
found = true
end
end
raise "variable #{@varname} is not found" if !found
end
end
attr_accessor (:rank)
alias :ndims :rank
# def dim(name)
# def dims(names)
def dim_names
ary = Array.new()
@dimensions.each{|dim| ary.push(dim[:name])}
ary
end
def name
@varname
end
def name=
raise "name= not supported"
end
def ndims
@rank
end
def vartype
if( @idim == -1 )
for i in 0...@ctl.variables.length
if @varname == @ctl.variables[i][:name]
type = @ctl.variables[i][:type]
end
end
else
type = "float"
end
type
end
alias ntype vartype
def typecode
if( @idim == -1 )
for i in 0...@ctl.variables.length
if @varname == @ctl.variables[i][:name]
byte = @ctl.variables[i][:byte]
end
end
else
byte = 4
end
byte
end
def att(name)
raise "use get_att instead of att"
end
def get_att(name)
@attr[name]
end
def natts
@attr.length
end
def att_names
@attr.keys
end
def each_att
raise "each_att not supported"
end
def put_att(name,value)
@attr[name] = value
end
def attr # obsolete
@attr
end
def file
@ctl.ctlfile
end
# def shape_ul0
# sh = []
# @dimensions.each{|dim|
## if d.unlimited? then
## sh.push(0)
## else
# sh.push(dim[:len])
## end
# }
# sh
# end
def shape_current
# sh = []
# @dimensions.each{|dim|
# sh.push(dim[:len])
# }
# sh
@shape
end
alias shape_ul0 shape_current
def scaled_put(var,hash=nil)
raise "scaled_put not supported"
end
def scaled_get(hash=nil)
raise "scaled_get not supported"
end
def put(var,hash=nil)
raise "put not supported"
end
def get(hash=nil)
if @idim != -1
na = @ctl.get_dim(@dimensions[0])
if hash == nil
elsif hash.key?("start")==true
h_sta = hash["start"]
endq = hash.key?("end")
strq = hash.key?("stride")
if endq == false && strq == false
na = na[h_sta[0]..-1]
elsif endq == true && strq == false
h_end = hash["end"]
na = na[h_sta[0]..h_end[0]]
elsif endq == false && strq == true
h_str = hash["stride"]
raise "sorry, stride is not supported yet."
else endq == true && strq == true
h_end = hash["end"]
h_str = hash["stride"]
raise "sorry, stride is not supported yet."
end
end
else
h_sta = [0, 0, 0, 0]
h_end = [-1, -1, -1, -1]
if hash == nil
# elsif hash.key?("index")==true
else
if hash.key?("start")==true
h_sta = hash["start"]
if hash.key?("end")==true
h_end = hash["end"]
end
end
if hash.key?("stride")==true
raise "sorry, stride is not supported yet."
end
end
idx_x = NArray.int(@shape[0]).indgen!
idx_y = NArray.int(@shape[1]).indgen!
idx_z = NArray.int(@shape[2]).indgen!
idx_t = NArray.int(@shape[3]).indgen!
str_x = idx_x[h_sta[0]]; end_x = idx_x[h_end[0]]
str_y = idx_y[h_sta[1]]; end_y = idx_y[h_end[1]]
str_z = idx_z[h_sta[2]]; end_z = idx_z[h_end[2]]
str_t = idx_t[h_sta[3]]; end_t = idx_t[h_end[3]]
na = NArrayMiss.new(vartype,end_x-str_x+1,end_y-str_y+1,end_z-str_z+1,end_t-str_t+1)
for t in str_t..end_t
for z in str_z..end_z
na_xy = @ctl.get(@varname,z,t,[str_x,end_x,str_y,end_y])
mask = ( na_xy.ne @ctl.undef )
na[true,true,z-str_z,t-str_t] = NArrayMiss.to_nam(na_xy,mask)
end
end
end
na
end
alias simple_put put
alias simple_get get
def __rubber_expansion( args ) # copied from NetCDFVar class
if (id = args.index(false)) # substitution into id
# false is incuded
alen = args.length
if args.rindex(false) != id
raise ArguemntError,"only one rubber dimension is permitted"
elsif alen > rank+1
raise ArgumentError, "too many args"
end
ar = ( id!=0 ? args[0..id-1] : [] )
args = ar + [true]*(rank-alen+1) + args[id+1..-1]
elsif args.length == 0 # to support empty [], []=
args = [true]*rank
end
args
end
private :__rubber_expansion
def [](*a) # copied from NetCDFVar class
a = __rubber_expansion(a)
first = Array.new
last = Array.new
stride = Array.new
set_stride = false
a.each{|i|
if(i.is_a?(Fixnum))
first.push(i)
last.push(i)
stride.push(1)
elsif(i.is_a?(Range))
first.push(i.first)
last.push(i.exclude_end? ? i.last-1 : i.last)
stride.push(1)
elsif(i.is_a?(Hash))
r = (i.to_a[0])[0]
s = (i.to_a[0])[1]
if ( !( r.is_a?(Range) ) || ! ( s.is_a?(Integer) ) )
raise TypeError, "Hash argument must be {a_Range, step}"
end
first.push(r.first)
last.push(r.exclude_end? ? r.last-1 : r.last)
stride.push(s)
set_stride = true
elsif(i.is_a?(TrueClass))
first.push(0)
last.push(-1)
stride.push(1)
elsif( i.is_a?(Array) || i.is_a?(NArray))
a_new = a.dup
at = a.index(i)
i = NArray.to_na(i) if i.is_a?(Array)
for n in 0..i.length-1
a_new[at] = i[n]..i[n]
na_tmp = self[*a_new]
if n==0 then
k = at
if at > 0
a[0..at-1].each{|x| if x.is_a?(Fixnum) then k -= 1 end}
end
shape_tmp = na_tmp.shape
shape_tmp[k] = i.length
na = na_tmp.class.new(na_tmp.typecode,*shape_tmp)
index_tmp = Array.new(shape_tmp.length,true)
end
index_tmp[k] = n..n
na[*index_tmp] = na_tmp
end
return na
else
raise TypeError, "argument must be Fixnum, Range, Hash, TrueClass, Array, or NArray"
end
}
if(set_stride)
na = self.get({"start"=>first, "end"=>last, "stride"=>stride})
else
na = self.get({"start"=>first, "end"=>last})
end
shape = na.shape
(a.length-1).downto(0){ |i|
shape.delete_at(i) if a[i].is_a?(Fixnum)
}
na.reshape!( *shape )
na
end
def []=(*a) # copied from NetCDFVar class
val = a.pop
a = __rubber_expansion(a)
first = Array.new
last = Array.new
stride = Array.new
set_stride = false
a.each{|i|
if(i.is_a?(Fixnum))
first.push(i)
last.push(i)
stride.push(1)
elsif(i.is_a?(Range))
first.push(i.first)
last.push(i.exclude_end? ? i.last-1 : i.last)
stride.push(1)
elsif(i.is_a?(Hash))
r = (i.to_a[0])[0]
s = (i.to_a[0])[1]
if ( !( r.is_a?(Range) ) || ! ( s.is_a?(Integer) ) )
raise ArgumentError, "Hash argument must be {first..last, step}"
end
first.push(r.first)
last.push(r.exclude_end? ? r.last-1 : r.last)
stride.push(s)
set_stride = true
elsif(i.is_a?(TrueClass))
first.push(0)
last.push(-1)
stride.push(1)
elsif(i.is_a?(Array) || i.is_a?(NArray))
a_new = a.dup
at = a.index(i)
i = NArray.to_na(i) if i.is_a?(Array)
val = NArray.to_na(val) if val.is_a?(Array)
rank_of_subset = a.dup.delete_if{|v| v.is_a?(Fixnum)}.length
if val.rank != rank_of_subset
raise "rank of the rhs (#{val.rank}) is not equal to the rank "+
"of the subset specified by #{a.inspect} (#{rank_of_subset})"
end
k = at
a[0..at-1].each{|x| if x.is_a?(Fixnum) then k -= 1 end}
if i.length != val.shape[k]
raise "length of the #{k+1}-th dim of rhs is incorrect "+
"(#{i.length} for #{val.shape[k]})"
end
index_tmp = Array.new(val.rank,true) if !val.is_a?(Numeric) #==>Array-like
for n in 0..i.length-1
a_new[at] = i[n]..i[n]
if !val.is_a?(Numeric) then
index_tmp[k] = n..n
self[*a_new] = val[*index_tmp]
else
self[*a_new] = val
end
end
return self
else
raise TypeError, "argument must be Fixnum, Range, Hash, TrueClass, Array, or NArray"
end
}
if(set_stride)
self.put(val, {"start"=>first, "end"=>last, "stride"=>stride})
else
self.put(val, {"start"=>first, "end"=>last})
end
end
end
end
if __FILE__ == $0 then
include NumRu
begin
gr = GrADS_Gridded.open("../../testdata/T.jan.ctl")
rescue
gr = GrADS_Gridded.open("../../../testdata/T.jan.ctl")
end
print gr.to_ctl
p gr
p gr.path
p gr.var_names
p gr.get_dim(gr.dimensions[2])
p gr.get('T',0,0)
begin
grvar = GrADSVar.new("../../testdata/T.jan.ctl","T")
rescue
grvar = GrADSVar.new("../../../testdata/T.jan.ctl","T")
end
p grvar.dim_names
p grvar.shape_ul0
t = grvar[3..9,5..15,0,0]
p t
end
syntax highlighted by Code2HTML, v. 0.9.1