ruby-grads 1.0.0 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,735 @@
1
+ #
2
+ #
3
+ #
4
+ # A library to read GrADS gridded data into CArray.
5
+ #
6
+ # TODO
7
+ # * options
8
+ #
9
+ # NOTE
10
+ # * 'dset' should be a file
11
+ #
12
+ # --- supported -------
13
+ # dset
14
+ # dtype
15
+ # title
16
+ # undef
17
+ # options
18
+ # pdef
19
+ # xdef
20
+ # ydef
21
+ # zdef
22
+ # tdef
23
+ # vectorpairs
24
+ # * comment
25
+ #
26
+ # --- not supported ---
27
+ # chsub
28
+ # fileheader
29
+ # theader
30
+ # tailerbytes
31
+ # xyheader
32
+ #
33
+
34
+ require "carray"
35
+ require "strscan"
36
+
37
+ module GrADS
38
+ end
39
+
40
+ class GrADS::Gridded
41
+ end
42
+
43
+ class GrADS::Gridded::Variable
44
+
45
+ def initialize (name, ctl)
46
+ @name = name
47
+ @dim = ctl.vardims[name]
48
+ @tsize = ctl.tsize
49
+ @dset = ctl.dset
50
+ @chunksize = ctl.chunksize
51
+ @offset = ctl.varoffsets[name]
52
+ @undef = ctl.undef
53
+ end
54
+
55
+ attr_reader :name, :dim, :size, :dset, :offset
56
+
57
+ def each_trange (trng=nil)
58
+ case trng
59
+ when NilClass, FalseClass
60
+ (0...@tsize).each do |t|
61
+ yield(t)
62
+ end
63
+ when Range
64
+ trng.each do |t|
65
+ yield(t)
66
+ end
67
+ else
68
+ t = trng
69
+ yield(t)
70
+ end
71
+ end
72
+
73
+ def tsize (trng=nil)
74
+ case trng
75
+ when NilClass, FalseClass
76
+ return @tsize
77
+ when Range
78
+ return trng.to_a.size
79
+ else
80
+ return 1
81
+ end
82
+ end
83
+
84
+ def [] (*argv)
85
+ trng = argv.shift
86
+ buf = CArray.float(*@dim)
87
+ out = nil
88
+ open(@dset) { |io|
89
+ if tsize(trng) == 1
90
+ each_trange(trng) { |t|
91
+ io.seek(t * @chunksize + offset)
92
+ buf.load_binary(io)
93
+ out = buf[*argv]
94
+ }
95
+ else
96
+ outlist = []
97
+ i = 0
98
+ bind_ok = false
99
+ each_trange(trng) { |t|
100
+ t * @chunksize + offset
101
+ io.seek(t * @chunksize + offset)
102
+ buf.load_binary(io)
103
+ obj = buf[*argv].to_ca
104
+ if obj.is_a?(CArray)
105
+ outlist << obj
106
+ else
107
+ outlist << CA_FLOAT(obj)
108
+ bind_ok = true
109
+ end
110
+ i += 1
111
+ }
112
+ if bind_ok
113
+ out = CArray.bind(:float, outlist, 0)
114
+ else
115
+ out = CArray.merge(:float, outlist, 0)
116
+ end
117
+ end
118
+ }
119
+ out[:eq, @undef] = UNDEF
120
+ return out
121
+ end
122
+
123
+ def to_ca
124
+ return self[]
125
+ end
126
+
127
+ end
128
+
129
+ class GrADS::Gridded
130
+
131
+ def initialize (ctl_file, radius: nil)
132
+ @ctl_file = ctl_file
133
+ @entries = scan_ctl_file(ctl_file)
134
+ @radius = radius
135
+ parse_entries(@entries)
136
+ end
137
+
138
+ attr_reader :ctl_file, :entries
139
+ attr_reader :dset, :title, :undef
140
+ attr_reader :pdef, :proj
141
+ attr_reader :isize, :jsize
142
+ attr_reader :xsize, :ysize, :zsize, :tsize
143
+ attr_reader :xaxis, :yaxis, :zaxis, :taxis
144
+ attr_reader :vectorpair
145
+ attr_reader :varnames, :vars, :vardims, :varoffsets
146
+ attr_reader :chunksize
147
+
148
+ private
149
+
150
+ def scan_ctl_file (ctl_file)
151
+ io = open(ctl_file, "r:ASCII-8BIT")
152
+ edefs = ""
153
+ vdefs = ""
154
+ headers = ""
155
+ while line = io.gets
156
+ case line
157
+ when /\A\s*vars/i
158
+ vdefs << line
159
+ while line = io.gets
160
+ vdefs << line
161
+ if line =~ /\A\s*endvars/i
162
+ break
163
+ end
164
+ end
165
+ when /\A\s*edef/i
166
+ raise "edef entry is not supported, sorry!"
167
+ edefs << line
168
+ while line = io.gets
169
+ ddefs << line
170
+ if line =~ /\A\s*endedef/i
171
+ break
172
+ end
173
+ end
174
+ else
175
+ headers << line
176
+ end
177
+ end
178
+ entries = scan_headers(headers)
179
+ entries["vars"] = scan_vars(vdefs)
180
+ return entries
181
+ end
182
+
183
+ def scan_headers (headers)
184
+ items = {}
185
+ buffer = ""
186
+ headers.each_line do |line|
187
+ case line
188
+ when /\A\s*\*/, /\A\s*\Z/
189
+ next
190
+ when /A\s*(dtype)\s*(.*)\Z/i
191
+ raise "data file is not GrADS gridded binary"
192
+ when /\A\s*(dset|title|undef|options|pdef|xdef|ydef|zdef|tdef|vectorpairs)\s*(.*)\Z/i
193
+ name = $1.downcase
194
+ if items[name]
195
+ items[name] = [items[name]]
196
+ items[name] << $2.rstrip
197
+ buffer = items[name].last
198
+ else
199
+ items[name] = $2.rstrip
200
+ buffer = items[name]
201
+ end
202
+ when /\A\s*(chsub|fileheader|theader|tailerbyts|xyheader)\s*(.*)\Z/i
203
+ raise "#{$1} entry is not supported, sorry"
204
+ else
205
+ line = line.chomp.sub(/\\\z/,'').rstrip
206
+ buffer.replace(buffer + " " + line)
207
+ end
208
+ end
209
+ return items
210
+ end
211
+
212
+ def scan_vars (vars)
213
+ items = {:namelist=>[]}
214
+ nvars = nil
215
+ vars.each_line do |line|
216
+ case line
217
+ when /\A\s*\*/, /\A\s*\Z/
218
+ next
219
+ when /\A\s*vars\s+(\d+)/i
220
+ nvars = $1.to_i
221
+ when /\A\s*endvars/i
222
+ break
223
+ when /\A\s*(\w+)\s*(.*)\Z/
224
+ name = $1
225
+ list = $2
226
+ items[:namelist].push(name)
227
+ items[name] = list
228
+ end
229
+ end
230
+ if items[:namelist].size != nvars
231
+ raise "invalid vars number"
232
+ end
233
+ return items
234
+ end
235
+
236
+ def parse_entries (entries)
237
+ parse_dset(entries["dset"])
238
+ if entries.has_key?("title")
239
+ parse_title(entries["title"])
240
+ end
241
+ parse_undef(entries["undef"])
242
+ parse_options(*entries["options"])
243
+ if entries.has_key?("pdef")
244
+ parse_pdef(entries["pdef"])
245
+ parse_xdef(entries["xdef"], with_pdef = true)
246
+ parse_ydef(entries["ydef"], with_pdef = true)
247
+ else
248
+ parse_xdef(entries["xdef"], with_pdef = false)
249
+ parse_ydef(entries["ydef"], with_pdef = false)
250
+ end
251
+ parse_zdef(entries["zdef"])
252
+ parse_tdef(entries["tdef"])
253
+ if entries.has_key?("vectorpairs")
254
+ parse_vectorpairs(entries["vectorpairs"])
255
+ end
256
+ parse_vars(entries["vars"][:namelist], entries["vars"])
257
+ end
258
+
259
+ def parse_dset (line)
260
+ line.strip!
261
+ if line =~ /\A\^/
262
+ cwd = File.dirname(File.expand_path(@ctl_file))
263
+ @dset = File.join(cwd, line[1..-1])
264
+ else
265
+ @dset = line
266
+ end
267
+ end
268
+
269
+ def parse_title (line)
270
+ @title = line
271
+ end
272
+
273
+ def parse_undef (line)
274
+ @undef = Float(line)
275
+ end
276
+
277
+ def parse_options (*lines)
278
+ # yrev o
279
+ # zrev o
280
+ # template x
281
+ # squential o
282
+ # 365_day_calendar ?
283
+ # byteswapped o
284
+ # big_endian o
285
+ # little_endian o
286
+ # cray_32bit_ieee ?
287
+ end
288
+
289
+ def parse_pdef (line)
290
+ list = line.split(/\s+/)
291
+ @pdef = list
292
+ @isize = list[0].to_i
293
+ @jsize = list[1].to_i
294
+ case list[2].downcase
295
+ when "nps", "sps"
296
+ parse_pdef_xps(list[2..-1])
297
+ when "lcc", "lccr"
298
+ parse_pdef_lcc(list[2..-1])
299
+ when "eta.u"
300
+ parse_pdef_eta_u(list[2..-1])
301
+ when "pse"
302
+ parse_pdef_pse(list[2..-1])
303
+ when "ops"
304
+ parse_pdef_ops(list[2..-1])
305
+ when "rotll", "rotllr"
306
+ parse_pdef_rotll(list[2..-1])
307
+ when "bilin"
308
+ parse_pdef_bilin(list[2..-1])
309
+ when "general"
310
+ parse_pdef_general(list[2..-1])
311
+ when "file"
312
+ parse_pdef_file(list[2..-1])
313
+ else
314
+ raise "pdef #{list[2].downcase} is not supported"
315
+ end
316
+ end
317
+
318
+ def parse_pdef_xps (args)
319
+ raise NotImplementedError, "not implemented"
320
+ end
321
+
322
+ def parse_pdef_lcc (args)
323
+ require "proj4r"
324
+ rlat = args[1].to_f
325
+ rlon = args[2].to_f
326
+ xi = args[3].to_f
327
+ yi = args[4].to_f
328
+ slat1 = args[5].to_f
329
+ slat2 = args[6].to_f
330
+ slon = args[7].to_f
331
+ dx = args[8].to_f
332
+ dy = args[9].to_f
333
+ slat0 = slat1 > 0 ? 90 : -90
334
+ if @radius
335
+ p @radius
336
+ proj = PROJ4::Proj.new %{
337
+ +ellps=sphere +a=#{@radius} +b=#{@radius} +proj=lcc \
338
+ +lat_1=#{slat1} +lat_2=#{slat2} +lon_0=#{slon}
339
+ }
340
+ else
341
+ proj = PROJ4::Proj.new %{
342
+ +ellps=sphere +proj=lcc \
343
+ +lat_1=#{slat1} +lat_2=#{slat2} +lon_0=#{slon}
344
+ }
345
+ end
346
+ rx, ry = proj.forward(rlon, rlat)
347
+ rx0 = rx - (xi-1)*dx
348
+ ry0 = ry - (yi-1)*dy
349
+ @forward = lambda { |lon, lat|
350
+ mx, my = proj.forward(lon, lat)
351
+ [(mx - rx0)/dx, (my - ry0)/dy]
352
+ }
353
+ @inverse = lambda { |fi, fj|
354
+ mx = fi*dx + rx0
355
+ my = fj*dy + ry0
356
+ proj.inverse(mx, my)
357
+ }
358
+ @xy = lambda {
359
+ x = CArray.float(xsize).seq(rx0, dx)
360
+ y = CArray.float(ysize).seq(ry0, dy)
361
+ [x[ysize,:%].to_ca, y[:%,xsize].to_ca]
362
+ }
363
+ end
364
+
365
+ def parse_pdef_eta_u (args)
366
+ raise NotImplementedError, "not implemented"
367
+ end
368
+
369
+ def parse_pdef_pse (args)
370
+ raise NotImplementedError, "not implemented"
371
+ end
372
+
373
+ def parse_pdef_ops (args)
374
+ raise NotImplementedError, "not implemented"
375
+ end
376
+
377
+ def parse_pdef_rotll (args)
378
+ raise NotImplementedError, "not implemented"
379
+ end
380
+
381
+ def parse_pdef_general (args)
382
+ raise NotImplementedError, "not implemented"
383
+ end
384
+
385
+ def parse_pdef_file (args)
386
+ raise NotImplementedError, "not implemented"
387
+ end
388
+
389
+ def parse_xdef (line, with_pdef=false)
390
+ list = line.split(/\s+/)
391
+ if with_pdef
392
+ @xsize = list[0].to_i
393
+ else
394
+ @isize = list[0].to_i
395
+ @xsize = @isize
396
+ end
397
+ case list[1]
398
+ when /linear/i
399
+ @xaxis = []
400
+ @xsize.times do |i|
401
+ @xaxis << list[2].to_f + i*list[3].to_f
402
+ end
403
+ when /levels/i
404
+ @xaxis = list[2..-1].map{|x| x.to_f}
405
+ end
406
+ end
407
+
408
+ def parse_ydef (line, with_pdef=false)
409
+ list = line.split(/\s+/)
410
+ if with_pdef
411
+ @ysize = list[0].to_i
412
+ else
413
+ @jsize = list[0].to_i
414
+ @ysize = @jsize
415
+ end
416
+ case list[1]
417
+ when /linear/i
418
+ @yaxis = []
419
+ @ysize.times do |i|
420
+ @yaxis << list[2].to_f + i*list[3].to_f
421
+ end
422
+ when /levels/i
423
+ @yaxis = list[2..-1].map{|y| y.to_f}
424
+ end
425
+ end
426
+
427
+ def parse_zdef (line)
428
+ list = line.split(/\s+/)
429
+ @zsize = list[0].to_i
430
+ case list[1]
431
+ when /linear/i
432
+ @zaxis = []
433
+ @zsize.times do |i|
434
+ @zaxis << list[2].to_f + i*list[3].to_f
435
+ end
436
+ when /levels/i
437
+ @zaxis = list[2..-1].map{|z| z.to_f}
438
+ end
439
+ end
440
+
441
+ def parse_tdef (line)
442
+ list = line.split(/\s+/)
443
+ @tsize = list[0].to_i
444
+ @inittime = DateTime.parse(list[2].sub(/z/i,' '))
445
+ @taxis = [@inittime]
446
+ case list[3]
447
+ when /\A(\d+)(mn|hr|dy|mo|yr)/
448
+ case $2
449
+ when "mn"
450
+ (1..@tsize-1).each do |i|
451
+ @taxis[i] = @taxis[i-1] + ($1.to_i).quo(1440)
452
+ end
453
+ when "hr"
454
+ (1..@tsize-1).each do |i|
455
+ @taxis[i] = @taxis[i-1] + ($1.to_i).quo(24)
456
+ end
457
+ when "dy"
458
+ (1..@tsize-1).each do |i|
459
+ @taxis[i] = @taxis[i-1] + $1.to_i
460
+ end
461
+ when "mn"
462
+ (1..@tsize-1).each do |i|
463
+ @taxis[i] = @taxis[i-1] >> $1.to_i
464
+ end
465
+ when "yr"
466
+ (1..@tsize-1).each do |i|
467
+ @taxis[i] = @taxis[i-1] >> (12*$1.to_i)
468
+ end
469
+ end
470
+ else
471
+ raise "invalid time increment"
472
+ end
473
+ end
474
+
475
+ def parse_vectorpairs (line)
476
+ list = line.split(/\s+/)
477
+ @vectorpair = list.map{|pair| pair.downcase.split(/\s*,\s*/)}
478
+ end
479
+
480
+ def parse_vars (namelist, hash)
481
+ @varnames = namelist
482
+ @vars = {}
483
+ @vardims = {}
484
+ @varoffsets = {}
485
+ @chunksize = 0
486
+ namelist.each do |name|
487
+ line = hash[name]
488
+ ss = StringScanner.new(line)
489
+ znum = ss.scan(/\A.*?\s+/).rstrip.to_i
490
+ units = ss.scan(/\A.*?\s+/).rstrip
491
+ desc = ss.rest
492
+ @vars[name] = [znum, units, desc]
493
+ if znum == 0
494
+ @vardims[name] = [@jsize,@isize]
495
+ else
496
+ @vardims[name] = [znum, @jsize,@isize]
497
+ end
498
+ @varoffsets[name] = @chunksize
499
+ @chunksize += 4 * @vardims[name].inject(1) {|s,d| s * d}
500
+ end
501
+ end
502
+
503
+ public
504
+
505
+ def inverse (fi, fj)
506
+ return @inverse[fi,fj]
507
+ end
508
+
509
+ def forward (rlon, rlat)
510
+ return @forward[rlon, rlat]
511
+ end
512
+
513
+ def lonlat
514
+ fi = CArray.float(xsize).seq
515
+ fj = CArray.float(ysize).seq
516
+ ii = fi[ysize,:%]
517
+ jj = fj[:%,xsize]
518
+ return @inverse[ii, jj]
519
+ end
520
+
521
+ def xy
522
+ return @xy[]
523
+ end
524
+
525
+ def var (name)
526
+ unless @varnames.include?(name)
527
+ raise "invalid variable name '#{name}'"
528
+ end
529
+ return Variable.new(name, self)
530
+ end
531
+
532
+ def template (ctl_file, &block)
533
+ asm = Writer.new(self)
534
+ asm.define(&block)
535
+ asm.write(ctl_file)
536
+ end
537
+
538
+ end
539
+
540
+ class GrADS::Gridded::Writer
541
+
542
+ def initialize (ctl = nil)
543
+ @dset = nil
544
+ @title = nil
545
+ @options = []
546
+ @vectorpairs = nil
547
+ @vars = []
548
+ @nt = 1
549
+ if ctl
550
+ e = ctl.entries
551
+ @undef = e["undef"]
552
+ @pdef = e["pdef"]
553
+ @xdef = e["xdef"]
554
+ @ydef = e["ydef"]
555
+ @zdef = e["zdef"]
556
+ @tdef = e["tdef"]
557
+ else
558
+ @undef = nil
559
+ @pdef = nil
560
+ @xdef = nil
561
+ @ydef = nil
562
+ @zdef = nil
563
+ @tdef = "1 linear 0z1jan2000"
564
+ end
565
+ end
566
+
567
+ private
568
+
569
+ def dset (arg)
570
+ @dset = arg
571
+ end
572
+
573
+ def title (arg)
574
+ @title = arg
575
+ end
576
+
577
+ def undef! (arg)
578
+ @undef = arg
579
+ end
580
+
581
+ def options (*args)
582
+ @options.push(*args)
583
+ end
584
+
585
+ def pdef (*args)
586
+ @pdef = args.join(" ")
587
+ end
588
+
589
+ def xdef (*args)
590
+ if args.size == 1 and
591
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
592
+ ary = args.first
593
+ @xdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
594
+ else
595
+ @xdef = args.join(" ")
596
+ end
597
+ end
598
+
599
+ def ydef (*args)
600
+ if args.size == 1 and
601
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
602
+ ary = args.first
603
+ @ydef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
604
+ else
605
+ @ydef = args.join(" ")
606
+ end
607
+ end
608
+
609
+ def zdef (*args)
610
+ if args.size == 1 and
611
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
612
+ ary = args.first
613
+ @zdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
614
+ else
615
+ @zdef = args.join(" ")
616
+ end
617
+ end
618
+
619
+ def tdef (*args)
620
+ if args.size == 1 and
621
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
622
+ ary = args.first
623
+ @tdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
624
+ else
625
+ @tdef = args.join(" ")
626
+ end
627
+ end
628
+
629
+ def vectorpairs (args)
630
+ @vectorpairs = args.join(" ")
631
+ end
632
+
633
+ def var2d (name, obj, desc = "x")
634
+ @vars.push([name, 2, obj, desc])
635
+ if obj.rank == 3 and obj.dim0 > @nt
636
+ @nt = obj.dim0
637
+ end
638
+ end
639
+
640
+ def var3d (name, obj, desc = "x")
641
+ @vars.push([name, 3, obj, desc])
642
+ if obj.rank == 4 and obj.dim0 > @nt
643
+ @nt = obj.dim0
644
+ end
645
+ end
646
+
647
+ public
648
+
649
+ def define (&block)
650
+ instance_eval(&block)
651
+ end
652
+
653
+ def write (ctl_file)
654
+ open(ctl_file, "w") { |io|
655
+ if @dset
656
+ io.puts "dset #{@dset}"
657
+ else
658
+ raise "dset is required"
659
+ end
660
+ if @title
661
+ io.puts "title #{@title}"
662
+ end
663
+ io.puts "undef #{@undef}"
664
+ @options.each do |opt|
665
+ io.puts %{options #{opt}}
666
+ end
667
+ if @pdef
668
+ io.puts %{pdef #{@pdef}}
669
+ end
670
+ io.puts %{xdef #{@xdef}}
671
+ io.puts %{ydef #{@ydef}}
672
+ io.puts %{zdef #{@zdef}}
673
+ io.puts %{tdef #{@tdef}}
674
+ if @vectorpairs
675
+ io.puts %{vectorpairs #{@vectorpairs}}
676
+ end
677
+
678
+ if @dset =~ /\A\^/
679
+ cwd = File.dirname(File.expand_path(ctl_file))
680
+ dset = File.join(cwd, @dset[1..-1])
681
+ else
682
+ dset = @dset
683
+ end
684
+
685
+ io.puts %{vars #{@vars.size}}
686
+ @vars.each do |name, d, obj, desc|
687
+ case d
688
+ when 2
689
+ io.puts %{#{name} 1 99 #{desc}}
690
+ when 3
691
+ case obj.rank
692
+ when 3
693
+ io.puts %{#{name} #{obj.dim0} 99 #{desc} }
694
+ when 4
695
+ io.puts %{#{name} #{obj.dim1} 99 #{desc} }
696
+ else
697
+ raise "invalid size"
698
+ end
699
+ end
700
+ end
701
+ io.puts "endvars"
702
+
703
+ open(dset, "w") { |dat|
704
+ @nt.times do |i|
705
+ @vars.each do |name, d, obj, desc|
706
+ case d
707
+ when 2
708
+ case obj.rank
709
+ when 2
710
+ obj.float.dump_binary(dat)
711
+ when 3
712
+ obj[i,false].float.dump_binary(dat)
713
+ end
714
+ when 3
715
+ case obj.rank
716
+ when 3
717
+ obj.float.dump_binary(dat)
718
+ when 4
719
+ obj[i,false].float.dump_binary(dat)
720
+ end
721
+ end
722
+ end
723
+ end
724
+ }
725
+ }
726
+
727
+
728
+ end
729
+
730
+ def template (ctlfile, &block)
731
+ define(&block)
732
+ write(ctlfile)
733
+ end
734
+
735
+ end