ruby-grads 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,709 @@
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)
132
+ @ctl_file = ctl_file
133
+ @entries = scan_ctl_file(ctl_file)
134
+ parse_entries(@entries)
135
+ end
136
+
137
+ attr_reader :ctl_file, :entries
138
+ attr_reader :dset, :title, :undef
139
+ attr_reader :pdef, :proj
140
+ attr_reader :isize, :jsize
141
+ attr_reader :xsize, :ysize, :zsize, :tsize
142
+ attr_reader :xaxis, :yaxis, :zaxis, :taxis
143
+ attr_reader :vectorpair
144
+ attr_reader :varnames, :vars, :vardims, :varoffsets
145
+ attr_reader :chunksize
146
+
147
+ private
148
+
149
+ def scan_ctl_file (ctl_file)
150
+ io = open(ctl_file, "r:ASCII-8BIT")
151
+ edefs = ""
152
+ vdefs = ""
153
+ headers = ""
154
+ while line = io.gets
155
+ case line
156
+ when /\A\s*vars/i
157
+ vdefs << line
158
+ while line = io.gets
159
+ vdefs << line
160
+ if line =~ /\A\s*endvars/i
161
+ break
162
+ end
163
+ end
164
+ when /\A\s*edef/i
165
+ raise "edef entry is not supported, sorry!"
166
+ edefs << line
167
+ while line = io.gets
168
+ ddefs << line
169
+ if line =~ /\A\s*endedef/i
170
+ break
171
+ end
172
+ end
173
+ else
174
+ headers << line
175
+ end
176
+ end
177
+ entries = scan_headers(headers)
178
+ entries["vars"] = scan_vars(vdefs)
179
+ return entries
180
+ end
181
+
182
+ def scan_headers (headers)
183
+ items = {}
184
+ buffer = ""
185
+ headers.each_line do |line|
186
+ case line
187
+ when /\A\s*\*/, /\A\s*\Z/
188
+ next
189
+ when /A\s*(dtype)\s*(.*)\Z/i
190
+ raise "data file is not GrADS gridded binary"
191
+ when /\A\s*(dset|title|undef|options|pdef|xdef|ydef|zdef|tdef|vectorpairs)\s*(.*)\Z/i
192
+ name = $1.downcase
193
+ if items[name]
194
+ items[name] = [items[name]]
195
+ items[name] << $2.rstrip
196
+ buffer = items[name].last
197
+ else
198
+ items[name] = $2.rstrip
199
+ buffer = items[name]
200
+ end
201
+ when /\A\s*(chsub|fileheader|theader|tailerbyts|xyheader)\s*(.*)\Z/i
202
+ raise "#{$1} entry is not supported, sorry"
203
+ else
204
+ line = line.chomp.sub(/\\\z/,'').rstrip
205
+ buffer.replace(buffer + " " + line)
206
+ end
207
+ end
208
+ return items
209
+ end
210
+
211
+ def scan_vars (vars)
212
+ items = {:namelist=>[]}
213
+ nvars = nil
214
+ vars.each_line do |line|
215
+ case line
216
+ when /\A\s*\*/, /\A\s*\Z/
217
+ next
218
+ when /\A\s*vars\s+(\d+)/i
219
+ nvars = $1.to_i
220
+ when /\A\s*endvars/i
221
+ break
222
+ when /\A\s*(\w+)\s*(.*)\Z/
223
+ name = $1
224
+ list = $2
225
+ items[:namelist].push(name)
226
+ items[name] = list
227
+ end
228
+ end
229
+ if items[:namelist].size != nvars
230
+ raise "invalid vars number"
231
+ end
232
+ return items
233
+ end
234
+
235
+ def parse_entries (entries)
236
+ parse_dset(entries["dset"])
237
+ if entries.has_key?("title")
238
+ parse_title(entries["title"])
239
+ end
240
+ parse_undef(entries["undef"])
241
+ parse_options(*entries["options"])
242
+ if entries.has_key?("pdef")
243
+ parse_pdef(entries["pdef"])
244
+ parse_xdef(entries["xdef"], with_pdef = true)
245
+ parse_ydef(entries["ydef"], with_pdef = true)
246
+ else
247
+ parse_xdef(entries["xdef"], with_pdef = false)
248
+ parse_ydef(entries["ydef"], with_pdef = false)
249
+ end
250
+ parse_zdef(entries["zdef"])
251
+ parse_tdef(entries["tdef"])
252
+ if entries.has_key?("vectorpairs")
253
+ parse_vectorpairs(entries["vectorpairs"])
254
+ end
255
+ parse_vars(entries["vars"][:namelist], entries["vars"])
256
+ end
257
+
258
+ def parse_dset (line)
259
+ line.strip!
260
+ if line =~ /\A\^/
261
+ cwd = File.dirname(File.expand_path(@ctl_file))
262
+ @dset = File.join(cwd, line[1..-1])
263
+ else
264
+ @dset = line
265
+ end
266
+ end
267
+
268
+ def parse_title (line)
269
+ @title = line
270
+ end
271
+
272
+ def parse_undef (line)
273
+ @undef = Float(line)
274
+ end
275
+
276
+ def parse_options (*lines)
277
+ # yrev o
278
+ # zrev o
279
+ # template x
280
+ # squential o
281
+ # 365_day_calendar ?
282
+ # byteswapped o
283
+ # big_endian o
284
+ # little_endian o
285
+ # cray_32bit_ieee ?
286
+ end
287
+
288
+ def parse_pdef (line)
289
+ list = line.split(/\s+/)
290
+ @pdef = list
291
+ @isize = list[0].to_i
292
+ @jsize = list[1].to_i
293
+ case list[2].downcase
294
+ when "nps", "sps"
295
+ parse_pdef_xps(list[2..-1])
296
+ when "lcc", "lccr"
297
+ parse_pdef_lcc(list[2..-1])
298
+ when "eta.u"
299
+ parse_pdef_eta_u(list[2..-1])
300
+ when "pse"
301
+ parse_pdef_pse(list[2..-1])
302
+ when "ops"
303
+ parse_pdef_ops(list[2..-1])
304
+ when "rotll", "rotllr"
305
+ parse_pdef_rotll(list[2..-1])
306
+ when "bilin"
307
+ parse_pdef_bilin(list[2..-1])
308
+ when "general"
309
+ parse_pdef_general(list[2..-1])
310
+ when "file"
311
+ parse_pdef_file(list[2..-1])
312
+ else
313
+ raise "pdef #{list[2].downcase} is not supported"
314
+ end
315
+ end
316
+
317
+ def parse_pdef_xps (args)
318
+ raise NotImplementedError, "not implemented"
319
+ end
320
+
321
+ def parse_pdef_lcc (args)
322
+ require "proj4r"
323
+ rlat = args[1].to_f
324
+ rlon = args[2].to_f
325
+ xi = args[3].to_f
326
+ yi = args[4].to_f
327
+ slat1 = args[5].to_f
328
+ slat2 = args[6].to_f
329
+ slon = args[7].to_f
330
+ dx = args[8].to_f
331
+ dy = args[9].to_f
332
+ slat0 = slat1 > 0 ? 90 : -90
333
+ proj = PROJ4::Proj.new %{
334
+ +ellps=sphere +proj=lcc \
335
+ +lat_1=#{slat1} +lat_2=#{slat2} +lat_0=90 +lon_0=#{slon}
336
+ }
337
+ rx, ry = proj.forward(rlon, rlat)
338
+ rx0 = rx - (xi-1)*dx
339
+ ry0 = ry - (yi-1)*dy
340
+ @forward = lambda { |lon, lat|
341
+ mx, my = proj.forward(lon, lat)
342
+ [(mx - rx0)/dx, (my - ry0)/dy]
343
+ }
344
+ @inverse = lambda { |fi, fj|
345
+ mx = fi*dx + rx0
346
+ my = fj*dy + ry0
347
+ proj.inverse(mx, my)
348
+ }
349
+ end
350
+
351
+ def parse_pdef_eta_u (args)
352
+ raise NotImplementedError, "not implemented"
353
+ end
354
+
355
+ def parse_pdef_pse (args)
356
+ raise NotImplementedError, "not implemented"
357
+ end
358
+
359
+ def parse_pdef_ops (args)
360
+ raise NotImplementedError, "not implemented"
361
+ end
362
+
363
+ def parse_pdef_rotll (args)
364
+ raise NotImplementedError, "not implemented"
365
+ end
366
+
367
+ def parse_pdef_general (args)
368
+ raise NotImplementedError, "not implemented"
369
+ end
370
+
371
+ def parse_pdef_file (args)
372
+ raise NotImplementedError, "not implemented"
373
+ end
374
+
375
+ def parse_xdef (line, with_pdef=false)
376
+ list = line.split(/\s+/)
377
+ if with_pdef
378
+ @xsize = list[0].to_i
379
+ else
380
+ @isize = list[0].to_i
381
+ @xsize = @isize
382
+ end
383
+ case list[1]
384
+ when /linear/i
385
+ @xaxis = []
386
+ @xsize.times do |i|
387
+ @xaxis << list[2].to_f + i*list[3].to_f
388
+ end
389
+ when /levels/i
390
+ @xaxis = list[2..-1].map{|x| x.to_f}
391
+ end
392
+ end
393
+
394
+ def parse_ydef (line, with_pdef=false)
395
+ list = line.split(/\s+/)
396
+ if with_pdef
397
+ @ysize = list[0].to_i
398
+ else
399
+ @jsize = list[0].to_i
400
+ @ysize = @jsize
401
+ end
402
+ case list[1]
403
+ when /linear/i
404
+ @yaxis = []
405
+ @ysize.times do |i|
406
+ @yaxis << list[2].to_f + i*list[3].to_f
407
+ end
408
+ when /levels/i
409
+ @yaxis = list[2..-1].map{|y| y.to_f}
410
+ end
411
+ end
412
+
413
+ def parse_zdef (line)
414
+ list = line.split(/\s+/)
415
+ @zsize = list[0].to_i
416
+ case list[1]
417
+ when /linear/i
418
+ @zaxis = []
419
+ @zsize.times do |i|
420
+ @zaxis << list[2].to_f + i*list[3].to_f
421
+ end
422
+ when /levels/i
423
+ @zaxis = list[2..-1].map{|z| z.to_f}
424
+ end
425
+ end
426
+
427
+ def parse_tdef (line)
428
+ list = line.split(/\s+/)
429
+ @tsize = list[0].to_i
430
+ @inittime = DateTime.parse(list[2].sub(/z/i,' '))
431
+ @taxis = [@inittime]
432
+ case list[3]
433
+ when /\A(\d+)(mn|hr|dy|mo|yr)/
434
+ case $2
435
+ when "mn"
436
+ (1..@tsize-1).each do |i|
437
+ @taxis[i] = @taxis[i-1] + ($1.to_i).quo(1440)
438
+ end
439
+ when "hr"
440
+ (1..@tsize-1).each do |i|
441
+ @taxis[i] = @taxis[i-1] + ($1.to_i).quo(24)
442
+ end
443
+ when "dy"
444
+ (1..@tsize-1).each do |i|
445
+ @taxis[i] = @taxis[i-1] + $1.to_i
446
+ end
447
+ when "mn"
448
+ (1..@tsize-1).each do |i|
449
+ @taxis[i] = @taxis[i-1] >> $1.to_i
450
+ end
451
+ when "yr"
452
+ (1..@tsize-1).each do |i|
453
+ @taxis[i] = @taxis[i-1] >> (12*$1.to_i)
454
+ end
455
+ end
456
+ else
457
+ raise "invalid time increment"
458
+ end
459
+ end
460
+
461
+ def parse_vectorpairs (line)
462
+ list = line.split(/\s+/)
463
+ @vectorpair = list.map{|pair| pair.downcase.split(/\s*,\s*/)}
464
+ end
465
+
466
+ def parse_vars (namelist, hash)
467
+ @varnames = namelist
468
+ @vars = {}
469
+ @vardims = {}
470
+ @varoffsets = {}
471
+ @chunksize = 0
472
+ namelist.each do |name|
473
+ line = hash[name]
474
+ ss = StringScanner.new(line)
475
+ znum = ss.scan(/\A.*?\s+/).rstrip.to_i
476
+ units = ss.scan(/\A.*?\s+/).rstrip
477
+ desc = ss.rest
478
+ @vars[name] = [znum, units, desc]
479
+ if znum == 0
480
+ @vardims[name] = [@jsize,@isize]
481
+ else
482
+ @vardims[name] = [znum, @jsize,@isize]
483
+ end
484
+ @varoffsets[name] = @chunksize
485
+ @chunksize += 4 * @vardims[name].inject(1) {|s,d| s * d}
486
+ end
487
+ end
488
+
489
+ public
490
+
491
+ def inverse (fi, fj)
492
+ return @inverse[fi,fj]
493
+ end
494
+
495
+ def forward (rlon, rlat)
496
+ return @forward[rlon, rlat]
497
+ end
498
+
499
+ def var (name)
500
+ unless @varnames.include?(name)
501
+ raise "invalid variable name '#{name}'"
502
+ end
503
+ return Variable.new(name, self)
504
+ end
505
+
506
+ def template (ctl_file, &block)
507
+ asm = Writer.new(self)
508
+ asm.define(&block)
509
+ asm.write(ctl_file)
510
+ end
511
+
512
+ end
513
+
514
+ class GrADS::Gridded::Writer
515
+
516
+ def initialize (ctl = nil)
517
+ @dset = nil
518
+ @title = nil
519
+ @options = []
520
+ @vectorpairs = nil
521
+ @vars = []
522
+ @nt = 1
523
+ if ctl
524
+ e = ctl.entries
525
+ @undef = e["undef"]
526
+ @pdef = e["pdef"]
527
+ @xdef = e["xdef"]
528
+ @ydef = e["ydef"]
529
+ @zdef = e["zdef"]
530
+ @tdef = e["tdef"]
531
+ else
532
+ @undef = nil
533
+ @pdef = nil
534
+ @xdef = nil
535
+ @ydef = nil
536
+ @zdef = nil
537
+ @tdef = "1 linear 0z1jan2000"
538
+ end
539
+ end
540
+
541
+ private
542
+
543
+ def dset (arg)
544
+ @dset = arg
545
+ end
546
+
547
+ def title (arg)
548
+ @title = arg
549
+ end
550
+
551
+ def undef! (arg)
552
+ @undef = arg
553
+ end
554
+
555
+ def options (*args)
556
+ @options.push(*args)
557
+ end
558
+
559
+ def pdef (*args)
560
+ @pdef = args.join(" ")
561
+ end
562
+
563
+ def xdef (*args)
564
+ if args.size == 1 and
565
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
566
+ ary = args.first
567
+ @xdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
568
+ else
569
+ @xdef = args.join(" ")
570
+ end
571
+ end
572
+
573
+ def ydef (*args)
574
+ if args.size == 1 and
575
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
576
+ ary = args.first
577
+ @ydef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
578
+ else
579
+ @ydef = args.join(" ")
580
+ end
581
+ end
582
+
583
+ def zdef (*args)
584
+ if args.size == 1 and
585
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
586
+ ary = args.first
587
+ @zdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
588
+ else
589
+ @zdef = args.join(" ")
590
+ end
591
+ end
592
+
593
+ def tdef (*args)
594
+ if args.size == 1 and
595
+ ( args.first.is_a?(CArray) or args.first.is_a?(Array) )
596
+ ary = args.first
597
+ @tdef = [ary.length, "levels", ary.to_a.join("\n")].join(" ")
598
+ else
599
+ @tdef = args.join(" ")
600
+ end
601
+ end
602
+
603
+ def vectorpairs (args)
604
+ @vectorpairs = args.join(" ")
605
+ end
606
+
607
+ def var2d (name, obj, desc = "x")
608
+ @vars.push([name, 2, obj, desc])
609
+ if obj.rank == 3 and obj.dim0 > @nt
610
+ @nt = obj.dim0
611
+ end
612
+ end
613
+
614
+ def var3d (name, obj, desc = "x")
615
+ @vars.push([name, 3, obj, desc])
616
+ if obj.rank == 4 and obj.dim0 > @nt
617
+ @nt = obj.dim0
618
+ end
619
+ end
620
+
621
+ public
622
+
623
+ def define (&block)
624
+ instance_eval(&block)
625
+ end
626
+
627
+ def write (ctl_file)
628
+ open(ctl_file, "w") { |io|
629
+ if @dset
630
+ io.puts "dset #{@dset}"
631
+ else
632
+ raise "dset is required"
633
+ end
634
+ if @title
635
+ io.puts "title #{@title}"
636
+ end
637
+ io.puts "undef #{@undef}"
638
+ @options.each do |opt|
639
+ io.puts %{options #{opt}}
640
+ end
641
+ if @pdef
642
+ io.puts %{pdef #{@pdef}}
643
+ end
644
+ io.puts %{xdef #{@xdef}}
645
+ io.puts %{ydef #{@ydef}}
646
+ io.puts %{zdef #{@zdef}}
647
+ io.puts %{tdef #{@tdef}}
648
+ if @vectorpairs
649
+ io.puts %{vectorpairs #{@vectorpairs}}
650
+ end
651
+
652
+ if @dset =~ /\A\^/
653
+ cwd = File.dirname(File.expand_path(ctl_file))
654
+ dset = File.join(cwd, @dset[1..-1])
655
+ else
656
+ dset = @dset
657
+ end
658
+
659
+ io.puts %{vars #{@vars.size}}
660
+ @vars.each do |name, d, obj, desc|
661
+ case d
662
+ when 2
663
+ io.puts %{#{name} 1 99 #{desc}}
664
+ when 3
665
+ case obj.rank
666
+ when 3
667
+ io.puts %{#{name} #{obj.dim0} 99 #{desc} }
668
+ when 4
669
+ io.puts %{#{name} #{obj.dim1} 99 #{desc} }
670
+ else
671
+ raise "invalid size"
672
+ end
673
+ end
674
+ end
675
+ io.puts "endvars"
676
+
677
+ open(dset, "w") { |dat|
678
+ @nt.times do |i|
679
+ @vars.each do |name, d, obj, desc|
680
+ case d
681
+ when 2
682
+ case obj.rank
683
+ when 2
684
+ obj.float.dump_binary(dat)
685
+ when 3
686
+ obj[i,false].float.dump_binary(dat)
687
+ end
688
+ when 3
689
+ case obj.rank
690
+ when 3
691
+ obj.float.dump_binary(dat)
692
+ when 4
693
+ obj[i,false].float.dump_binary(dat)
694
+ end
695
+ end
696
+ end
697
+ end
698
+ }
699
+ }
700
+
701
+
702
+ end
703
+
704
+ def template (ctlfile, &block)
705
+ define(&block)
706
+ write(ctlfile)
707
+ end
708
+
709
+ end