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,751 @@
1
+ require "tempfile"
2
+ require "open3"
3
+ require "date"
4
+ require "carray"
5
+
6
+ class GrADS::Command
7
+
8
+ class Expression
9
+ def initialize (expr)
10
+ @expr = expr
11
+ end
12
+ def to_s
13
+ return @expr
14
+ end
15
+ def paren
16
+ return ["(",@expr,")"].join
17
+ end
18
+ alias to_str to_s
19
+ def -@
20
+ return Expression.new(["-", self.paren].join)
21
+ end
22
+ def +@
23
+ return Expression.new(["+", self.paren].join)
24
+ end
25
+ def + (other)
26
+ case other
27
+ when Expression
28
+ return Expression.new([self.paren,"+",other.paren].join)
29
+ else
30
+ return Expression.new([self.paren,"+",other.to_s].join)
31
+ end
32
+ end
33
+ def - (other)
34
+ case other
35
+ when Expression
36
+ return Expression.new([self.paren,"-",other.paren].join)
37
+ else
38
+ return Expression.new([self.paren,"-",other.to_s].join)
39
+ end
40
+ end
41
+ def / (other)
42
+ case other
43
+ when Expression
44
+ return Expression.new([self.paren,"/",other.paren].join)
45
+ else
46
+ return Expression.new([self.paren,"/",other.to_s].join)
47
+ end
48
+ end
49
+ def * (other)
50
+ case other
51
+ when Expression
52
+ return Expression.new([self.paren,"*",other.paren].join)
53
+ else
54
+ return Expression.new([self.paren,"*",other.to_s].join)
55
+ end
56
+ end
57
+ def method_missing (id, *argv)
58
+ if argv.size > 0
59
+ return Expression.new(format("#{id}(%s,%s)", @expr, argv.join(",")))
60
+ else
61
+ return Expression.new(format("#{id}(%s)", @expr))
62
+ end
63
+ end
64
+ end
65
+
66
+ class Variable < Expression
67
+ def initialize (file_id, name)
68
+ if file_id
69
+ @expr = [name,".",file_id].join("")
70
+ else
71
+ @expr = name.to_s
72
+ end
73
+ end
74
+ def to_s
75
+ return @expr
76
+ end
77
+ alias to_str to_s
78
+ def [] (hash)
79
+ spec = ["(",hash.map{ |k,v| [k,"=",v].join("") }.join(","),")"].join("")
80
+ return Expression.new([@expr,spec].join(""))
81
+ end
82
+ def method_missing (id, *argv)
83
+ if argv.size == 0
84
+ return Expression.new(format("#{id}(%s)", @expr))
85
+ else
86
+ return Expression.new(format("#{id}(%s,%s)", @expr, argv.join(",")))
87
+ end
88
+ end
89
+ end
90
+
91
+ class DataHandler
92
+ def initialize (file_id)
93
+ @file_id = file_id
94
+ end
95
+ attr_reader :file_id
96
+ def var (name)
97
+ return Variable.new(@file_id, name)
98
+ end
99
+ def method_missing (id, *argv)
100
+ if argv.empty?
101
+ return Variable.new(@file_id, id.to_s)
102
+ else
103
+ super
104
+ end
105
+ end
106
+ end
107
+
108
+ def initialize (*argv, &block)
109
+ if argv.last.is_a?(Hash)
110
+ options = argv.pop
111
+ else
112
+ options = {}
113
+ end
114
+ if argv.first
115
+ grads_command = argv.first
116
+ else
117
+ grads_command = "grads"
118
+ end
119
+ if options[:batch]
120
+ grads_command = grads_command + " -b"
121
+ end
122
+ if options[:portrait]
123
+ grads_command += " -p "
124
+ elsif options[:landscape]
125
+ grads_command += " -l "
126
+ else
127
+ grads_command += " -l "
128
+ end
129
+ begin
130
+ @io, @stdout, @stderr = Open3.popen3(grads_command)
131
+ rescue NotImplementedError
132
+ raise NotImplementedError, "system dosen't support Open3.popen3"
133
+ end
134
+ @debug = false
135
+ @log = ""
136
+ @imported = []
137
+ @echoback = false
138
+ @listen = Thread.start {
139
+ Thread.abort_on_exception = true
140
+ begin
141
+ while line = @stderr.gets ### read(1024)
142
+ STDERR << line
143
+ STDERR.flush
144
+ end
145
+ rescue IOError ### @stdout may externally closed
146
+ end
147
+ }
148
+ @io.puts "ECHOBACK_TEST_FOR_RUBY_GRADS"
149
+ while @stdout.gets !~ /Unknown command: ECHOBACK_TEST_FOR_RUBY_GRADS/
150
+ end
151
+ @io.puts "ECHOBACK_TEST_FOR_RUBY_GRADS"
152
+ if @stdout.gets =~ /ga-> ECHOBACK_TEST_FOR_RUBY_GRADS/
153
+ @echoback = true
154
+ @stdout.gets
155
+ else
156
+ @echoback = false
157
+ end
158
+ put "set grads off"
159
+ put "set timelab off"
160
+ if block_given?
161
+ begin
162
+ case block.arity
163
+ when 1
164
+ yield(self)
165
+ when -1, 0
166
+ instance_eval(&block)
167
+ else
168
+ raise "invalid # of block parameters"
169
+ end
170
+ ensure
171
+ self.quit
172
+ end
173
+ end
174
+ end
175
+
176
+ attr_reader :log
177
+
178
+ def method_missing (id, *argv)
179
+ return id, *argv
180
+ end
181
+
182
+ def quit
183
+ put("quit")
184
+ @io.close
185
+ @listen.join
186
+ @stdout.close
187
+ @stderr.close
188
+ end
189
+
190
+ alias exit quit
191
+
192
+ def put (*args)
193
+ command = args.join("\n")
194
+ @log << command << "\n"
195
+ if @debug
196
+ STDERR.puts "ga-> " + command
197
+ end
198
+ thread = Thread.start {
199
+ begin
200
+ size = command.size
201
+ s = 0
202
+ while s < size
203
+ s += @io.write(command[s, 1024])
204
+ end
205
+ @io.puts
206
+ @io.puts "SIGNAL_FOR_RUBY_GRADS"
207
+ @io.flush
208
+ rescue Errno::EPIPE
209
+ end
210
+ }
211
+ output = ""
212
+ while line = @stdout.gets
213
+ if @echoback and line =~ /^ga->\s+/
214
+ next
215
+ else
216
+ line.sub!(/^ga->\s+/,'')
217
+ end
218
+ case line
219
+ when /SIGNAL_FOR_RUBY_GRADS/
220
+ STDERR.print(output) if @debug and not output.empty?
221
+ return output.chomp
222
+ when /^(\w+) error: (.*?) /
223
+ output << line
224
+ STDERR.print output
225
+ raise "GRADS Error"
226
+ else
227
+ output << line
228
+ end
229
+ end
230
+ ensure
231
+ thread.join
232
+ end
233
+
234
+ def debug_on
235
+ @debug = true
236
+ end
237
+
238
+ def debug_off
239
+ @debug = false
240
+ end
241
+
242
+ def sublin (text, n)
243
+ return text.split(/\n/)[n-1]
244
+ end
245
+
246
+ def subwrd (text, n)
247
+ wrd = text.strip.split(/\s*\n\s*|\s+/)[n-1]
248
+ return wrd ? wrd.strip : ""
249
+ end
250
+
251
+ def open (*argv)
252
+ text = put "open " + argv.join(" ")
253
+ file_id = nil
254
+ text.each_line do |line|
255
+ if line =~ /is open as file/
256
+ file_id = subwrd(line, 8).to_i
257
+ break
258
+ end
259
+ end
260
+ if file_id
261
+ return DataHandler.new(file_id)
262
+ else
263
+ raise "failed to open file #{argv.first}"
264
+ end
265
+ end
266
+
267
+ def close (dh)
268
+ file_id = dh.file_id
269
+ put "close #{file_id}"
270
+ end
271
+
272
+ def xdfopen (*argv)
273
+ text = put "xdfopen " + argv.join(" ")
274
+ file_id = nil
275
+ text.each_line do |line|
276
+ if line =~ /is open as file/
277
+ file_id = subwrd(line, 8).to_i
278
+ break
279
+ end
280
+ end
281
+ if file_id
282
+ return DataHandler.new(file_id)
283
+ else
284
+ raise "failed to open file #{argv.first}"
285
+ end
286
+ end
287
+
288
+ def sdfopen (*argv)
289
+ text = put "sdfopen " + argv.join(" ")
290
+ file_id = nil
291
+ text.each_line do |line|
292
+ if line =~ /is open as file/
293
+ file_id = subwrd(line, 8).to_i
294
+ break
295
+ end
296
+ end
297
+ if file_id
298
+ return DataHandler.new(file_id)
299
+ else
300
+ raise "failed to open file #{argv.first}"
301
+ end
302
+ end
303
+
304
+ def scan (ctltext)
305
+ io = Tempfile.open("CA_GrADS_", ".")
306
+ io.write ctltext
307
+ io.flush
308
+ return open(io.path)
309
+ ensure
310
+ io.close
311
+ end
312
+
313
+ def xdfscan (ctltext)
314
+ io = Tempfile.open("CA_GrADS_", ".")
315
+ io.write ctltext
316
+ io.flush
317
+ return xdfopen(io.path)
318
+ ensure
319
+ io.close
320
+ end
321
+
322
+ def exec (name, *argv)
323
+ put "exec #{name} " + argv.flatten.join(" ")
324
+ end
325
+
326
+ def exec_string (definition, *argv)
327
+ io = Tempfile.open("CA_GrADS_", ".")
328
+ io.write definition
329
+ io.puts
330
+ io.flush
331
+ return exec(io.path, *argv)
332
+ ensure
333
+ io.close
334
+ end
335
+
336
+ def script (name, definition)
337
+ io = Tempfile.new("CA_GrADS_", ".")
338
+ io.write(definition)
339
+ io.flush
340
+ instance_eval %{
341
+ def #{name} (*args)
342
+ run "#{io.path}", *args
343
+ end
344
+ }
345
+ end
346
+
347
+ def set (*argv)
348
+ put "set " + argv.flatten.join(" ")
349
+ end
350
+
351
+ def display (*argv)
352
+ if argv.first.is_a?(Array)
353
+ put "display " + argv.first.map{|v| "("+v.to_s+")" }.join(";")
354
+ else
355
+ put "display " + argv.join(" ")
356
+ end
357
+ end
358
+
359
+ def define (name, expr)
360
+ put "define #{name} = " + expr
361
+ return Variable.new(nil, name)
362
+ end
363
+
364
+ def eval (expr)
365
+ put expr.to_s
366
+ end
367
+
368
+ def enable (id, *argv)
369
+ case id.to_s
370
+ when "print"
371
+ begin
372
+ put "enable print " + argv.join(" ")
373
+ yield
374
+ ensure
375
+ put "disable print"
376
+ end
377
+ else
378
+ put "enable #{id} " + argv.join(" ")
379
+ end
380
+ end
381
+
382
+ def draw (*argv)
383
+ put "draw " + argv.flatten.join(" ")
384
+ end
385
+
386
+ def redraw (*argv)
387
+ put "redraw " + argv.flatten.join(" ")
388
+ end
389
+
390
+ [
391
+ "buffrscan",
392
+ "collect",
393
+ "disable",
394
+ "flush",
395
+ "modify",
396
+ "outxwd",
397
+ "print",
398
+ "printim",
399
+ "gxprint",
400
+ "query",
401
+ "quit",
402
+ "reinit",
403
+ "reset",
404
+ "run",
405
+ "sdfwrite",
406
+ "swap",
407
+ "undefine",
408
+ ].each do |name|
409
+ class_eval %{
410
+ def #{name} (*argv)
411
+ put "#{name} " + argv.join(" ")
412
+ end
413
+ }
414
+ end
415
+
416
+ def clear (*argv)
417
+ @ccols = nil
418
+ @clevs = nil
419
+ put "clear " + argv.join(" ")
420
+ end
421
+
422
+ alias d display
423
+ alias c clear
424
+ alias q query
425
+
426
+ def set_time (*argv)
427
+ list = []
428
+ argv.each do |time|
429
+ timestr = nil
430
+ case time
431
+ when DateTime, Time
432
+ timestr = time.strftime("%H:%Mz%d%b%Y")
433
+ when Date
434
+ timestr = time.strftime("%d%b%Y")
435
+ when String
436
+ timestr = time
437
+ else
438
+ raise "unknown time variable #{time.inspect}"
439
+ end
440
+ list << timestr
441
+ end
442
+ set :time, *list
443
+ end
444
+
445
+ def get_time
446
+ list = query("time").split(/\s+/)
447
+ if list[2] == list[4]
448
+ return DateTime.parse(list[2].sub(/Z/," "))
449
+ else
450
+ return DateTime.parse(list[2].sub(/Z/," "))..DateTime.parse(list[4].sub(/Z/," "))
451
+ end
452
+ end
453
+
454
+ def get_time_start
455
+ list = query("time").split(/\s+/)
456
+ return DateTime.parse(list[2].sub(/Z/," "))
457
+ end
458
+
459
+ def get_time_end
460
+ list = query("time").split(/\s+/)
461
+ return DateTime.parse(list[4].sub(/Z/," "))
462
+ end
463
+
464
+ def get_level
465
+ return subwrd(sublin(query("dims"),4),6).to_f
466
+ end
467
+
468
+ def get_value (*argv)
469
+ var = argv.shift
470
+ if argv.empty?
471
+ list = query(:defval, var, 0, 0).split(/\s+/)
472
+ else
473
+ list = query(:defval, var, *argv).split(/\s+/)
474
+ end
475
+ return list[2].to_f
476
+ end
477
+
478
+ def get_mouse_click (trans = nil)
479
+ list = query("pos").split(/\s+/)
480
+ button = list[4].to_i
481
+ x, y = list[2].to_f, list[3].to_f
482
+ if trans
483
+ list = query(trans, x, y).split(/\s+/)
484
+ x, y = list[2].to_f, list[5].to_f
485
+ end
486
+ return x, y, button
487
+ end
488
+
489
+ def transform (kind, v1, v2)
490
+ result = query(kind, v1, v2)
491
+ r1 = subwrd(result, 3)
492
+ r2 = subwrd(result, 6)
493
+ return r1, r2
494
+ end
495
+
496
+ def w2xy (lat, lon)
497
+ return transform(:w2xy, lat, lon)
498
+ end
499
+
500
+ def w2gr (lat, lon)
501
+ return transform(:w2gr, lat, lon)
502
+ end
503
+
504
+ def gr2w (fi, fj)
505
+ return transform(:gr2w, fi, fj)
506
+ end
507
+
508
+ def gr2xy (fi, fj)
509
+ return transform(:gr2xy, fi, fj)
510
+ end
511
+
512
+ def xy2w (x, y)
513
+ return transform(:xy2w, x, y)
514
+ end
515
+
516
+ def xy2gr (x, y)
517
+ return transform(:xy2gr, x, y)
518
+ end
519
+
520
+ def set_clevs (*argv)
521
+ @clevs = argv
522
+ set :clevs, *argv
523
+ end
524
+
525
+ def set_ccols (*argv)
526
+ @ccols = argv
527
+ set :ccols, *argv
528
+ end
529
+
530
+ def import (*args)
531
+ args.each do |name|
532
+ if @imported.include?(name)
533
+ next
534
+ end
535
+ self.class.module_eval %{
536
+ def #{name} (*argv)
537
+ put "#{name} " + argv.join(" ")
538
+ end
539
+ }
540
+ @imported.push name
541
+ end
542
+ end
543
+
544
+ def imported? (arg)
545
+ return @imported.include?(arg)
546
+ end
547
+
548
+ def pause
549
+ get_mouse_click
550
+ end
551
+
552
+ def template2d (sd, *args, &block)
553
+ return __template__(2, sd, *args, &block)
554
+ end
555
+
556
+ def template3d (sd, *args, &block)
557
+ return __template__(3, sd, *args, &block)
558
+ end
559
+
560
+ def __template__ (d, sd, *args, &block)
561
+ if args.last.is_a?(Hash)
562
+ opts = args.pop
563
+ else
564
+ opts = {}
565
+ end
566
+ args = args.map{|v| CA_FLOAT(v) }
567
+ dataio = Tempfile.open("CA_GrADS_", ".")
568
+ dataname = dataio.path
569
+ datactl = dataname + ".ctl"
570
+ if sd
571
+ result = q :file, sd.file_id
572
+ ctlfile = subwrd(sublin(result, 2), 2)
573
+ ctl = GrADS::Gridded.new(ctlfile)
574
+ else
575
+ ref = args.first
576
+ ctl = GrADS::Gridded::Writer.new
577
+ case d
578
+ when 2
579
+ if ref.rank == 2
580
+ ctl.define {
581
+ tdef "1 linear 0z01jan2000 1dy"
582
+ zdef "1 linear 1 1"
583
+ ydef ref.dim0, "linear 1 1"
584
+ xdef ref.dim1, "linear 1 1"
585
+ }
586
+ else
587
+ ctl.define {
588
+ tdef ref.dim0, "linear 0z01jan2000 1dy"
589
+ zdef "1 linear 1 1"
590
+ ydef ref.dim1, "linear 1 1"
591
+ xdef ref.dim2, "linear 1 1"
592
+ }
593
+ end
594
+ when 3
595
+ if ref.rank == 3
596
+ ctl.define {
597
+ tdef "1 linear 0z01jan2000 1dy"
598
+ zdef ref.dim0, "linear 1 1"
599
+ ydef ref.dim1, "linear 1 1"
600
+ xdef ref.dim2, "linear 1 1"
601
+ }
602
+ else
603
+ ctl.define {
604
+ tdef ref.dim0, "linear 0z01jan2000 1dy"
605
+ zdef ref.dim1, "linear 1 1"
606
+ ydef ref.dim2, "linear 1 1"
607
+ xdef ref.dim3, "linear 1 1"
608
+ }
609
+ end
610
+ end
611
+ end
612
+ ctl.define {
613
+ if opts[:undef]
614
+ undef! opts[:undef]
615
+ else
616
+ undef! 1e30
617
+ end
618
+ }
619
+ if block
620
+ ctl.define(&block)
621
+ end
622
+ begin
623
+ names = []
624
+ ctl.template(datactl) do
625
+ dset dataname
626
+ args.each_with_index do |arg, i|
627
+ names << ( name = "tmpvar#{i}" )
628
+ case d
629
+ when 2
630
+ var2d(name, arg)
631
+ when 3
632
+ var3d(name, arg)
633
+ end
634
+ end
635
+ end
636
+ ss = open(datactl)
637
+ vars = []
638
+ names.each do |name|
639
+ vars << define(name, ss.send(name))
640
+ end
641
+ return vars
642
+ ensure
643
+ dataio.close
644
+ File.unlink(datactl)
645
+ end
646
+ end
647
+
648
+ begin
649
+ require "netcdflib"
650
+ def get_var (expr)
651
+ @var_id ||= 0
652
+ @var_id += 1
653
+ filename = "CA_GRADS_#{$$}_#{@var_id}.nc"
654
+ begin
655
+ define :grdvar, expr
656
+ set :sdfwrite, filename
657
+ sdfwrite :grdvar
658
+ nc_id = NC.open(filename, NC::NC_NOWRITE)
659
+ var_id = NC.inq_varid(nc_id, "grdvar")
660
+ rank = NC.inq_varndims(nc_id, var_id)
661
+ dim = Array.new(rank)
662
+ axis = Array.new(rank)
663
+ dim_ids = NC.inq_vardimid(nc_id, var_id)
664
+ dim_ids.each_with_index do |dim_id, i|
665
+ dim[i] = NC.inq_dimlen(nc_id,dim_id)
666
+ name = NC.inq_dimname(nc_id,dim_id)
667
+ axis_id = NC.inq_varid(nc_id,name)
668
+ axis[i] = CArray.double(dim[i])
669
+ NC.get_var(nc_id,axis_id,axis[i])
670
+ if name == "time"
671
+ NC.get_att(nc_id, axis_id, "units", str = "")
672
+ units, inittime = *str.split(/\s*since\s*/)
673
+ time0 = DateTime.parse(inittime)
674
+ case units
675
+ when "minutes"
676
+ axis[i] = axis[i].convert(:object) {|x| time0 + x.to_i * 1.quo(1440) }
677
+ else
678
+ raise "unknown time minutes"
679
+ end
680
+ end
681
+ end
682
+ data = CArray.double(*dim)
683
+ NC.get_var(nc_id, var_id, data)
684
+ miss = CScalar.double
685
+ NC.get_att(nc_id, var_id, "missing_value", miss)
686
+ NC.close(nc_id)
687
+ data[:eq,miss] = UNDEF
688
+ #ensure
689
+ File.unlink(filename)
690
+ end
691
+ return data, axis.reverse
692
+ end
693
+ rescue LoadError
694
+ begin
695
+ require "numru/netcdf"
696
+ def get_var (expr)
697
+ @var_id ||= 0
698
+ @var_id += 1
699
+ filename = "CA_GRADS_#{$$}_#{@var_id}.nc"
700
+ begin
701
+ define :grdvar, expr
702
+ set :sdfwrite, filename
703
+ sdfwrite :grdvar
704
+ nc = NumRu::NetCDF.open(filename)
705
+ var = nc.var("grdvar")
706
+ axis = []
707
+ var.dim_names.each_with_index do |name,i|
708
+ axis[i] = nc.var(name).get.ca
709
+ if name == "time"
710
+ str = nc.var(name).att("units").get
711
+ units, inittime = *str.split(/\s*since\s*/)
712
+ time0 = DateTime.parse(inittime)
713
+ case units
714
+ when "minutes"
715
+ axis[i] = axis[i].convert(:object) {|x| time0 + x.to_i * 1.quo(1440) }
716
+ else
717
+ raise "unknown time minutes"
718
+ end
719
+ end
720
+ end
721
+ miss = var.att("missing_value").get
722
+ data = var.get.ca
723
+ data[:eq,miss] = UNDEF
724
+ ensure
725
+ File.unlink(filename)
726
+ end
727
+ return data, axis.reverse
728
+ end
729
+ rescue LoadError
730
+ end
731
+ end
732
+
733
+ end
734
+
735
+ def GrADS.start (*argv, &block)
736
+ return GrADS::Command.new(*argv, &block)
737
+ end
738
+
739
+ def GrADS.script (name, definition)
740
+ io = Tempfile.new("CA_GrADS_", ".")
741
+ io.write(definition)
742
+ io.flush
743
+ GrADS::Command.class_eval %{
744
+ def #{name} (*args)
745
+ run "#{io.path}", *args
746
+ end
747
+ }
748
+ end
749
+
750
+
751
+