ruby-grads 1.0.0

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,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
+