seis_ruby 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.rdoc +23 -4
  2. data/Rakefile +10 -0
  3. data/bin/seis_ruby +1 -1
  4. data/lib/seis_ruby/application.rb +76 -0
  5. data/lib/seis_ruby/command.rb +15 -0
  6. data/lib/seis_ruby/core_ext/file.rb +7 -0
  7. data/lib/seis_ruby/core_ext.rb +5 -0
  8. data/lib/seis_ruby/data/cmtsolution.rb +84 -64
  9. data/lib/seis_ruby/data/sac/ascii/head.rb +49 -0
  10. data/lib/seis_ruby/data/sac/ascii.rb +61 -0
  11. data/lib/seis_ruby/data/sac/binary/head.rb +5 -0
  12. data/lib/seis_ruby/data/sac/binary.rb +26 -0
  13. data/lib/seis_ruby/data/sac/body.rb +62 -0
  14. data/lib/seis_ruby/data/sac/head.rb +162 -0
  15. data/lib/seis_ruby/data/sac.rb +99 -3
  16. data/lib/seis_ruby/data.rb +6 -5
  17. data/lib/seis_ruby/database/global_cmt_catalog_search/cmtsolution_format.rb +30 -0
  18. data/lib/seis_ruby/database/global_cmt_catalog_search.rb +44 -0
  19. data/lib/seis_ruby/database.rb +3 -0
  20. data/lib/seis_ruby/version.rb +1 -1
  21. data/lib/seis_ruby.rb +12 -3
  22. data/seis_ruby.gemspec +8 -14
  23. data/test/data/000000001.SAC +0 -0
  24. data/test/data/000000001.SAC_ascii +32 -0
  25. data/test/seis_ruby/command_test.rb +19 -0
  26. data/test/seis_ruby/core_ext/file_test.rb +7 -0
  27. data/test/seis_ruby/data/cmtsolution_test.rb +63 -0
  28. data/test/seis_ruby/data/sac/ascii_test.rb +15 -0
  29. data/test/seis_ruby/data/sac/binary_test.rb +15 -0
  30. data/test/seis_ruby/data/sac/body_test.rb +27 -0
  31. data/test/seis_ruby/data/sac/head_test.rb +78 -0
  32. data/test/seis_ruby/data/sac_test.rb +52 -0
  33. data/test/seis_ruby/database/global_cmt_catalog_search/cmtsolution_format_test.rb +142 -0
  34. data/test/seis_ruby/database/global_cmt_catalog_search_test.rb +10 -0
  35. data/test/seis_ruby_test.rb +0 -0
  36. data/test/test_helper.rb +7 -0
  37. data/test.watchr +13 -0
  38. metadata +79 -54
  39. data/lib/seis_ruby/bin/seis_ruby_runner.rb +0 -58
  40. data/lib/seis_ruby/bin.rb +0 -6
  41. data/lib/seis_ruby/io/gcmt_catalog/custom_html_parser.rb +0 -16
  42. data/lib/seis_ruby/io/gcmt_catalog.rb +0 -50
  43. data/lib/seis_ruby/io.rb +0 -6
  44. data/rakefile +0 -8
  45. data/spec/seis_ruby/data/cmtsolution_spec.rb +0 -83
  46. data/spec/seis_ruby/io/gcmt_catalog_spec.rb +0 -19
  47. data/spec/seis_ruby/io/scrape.yaml +0 -3972
  48. data/spec/spec_helper.rb +0 -18
data/README.rdoc CHANGED
@@ -1,5 +1,24 @@
1
1
  = SeisRuby
2
- == Executables
3
- === seis_ruby
4
- Please see details by typing:
5
- > seis_ruby help
2
+ == What is this?
3
+ Ruby library and command line tools for earthquake science.
4
+
5
+ == Command line tool
6
+ seis_ruby help
7
+
8
+ == Library
9
+ Please check {SeisRuby::Command} which provides handy methods via {::SeisRuby}.
10
+
11
+ == Quick start
12
+ require 'seis_ruby'
13
+ sac = SeisRuby.load_file('test_read.SAC') # Read a SAC file
14
+ sac.head[:a] += 3.0 # Shift Tp 3.0 seconds
15
+ sac.dump_file('test_dump_shifted.SAC') # Output a SAC file
16
+
17
+ == License
18
+ {http://www.gnu.org/licenses/gpl.html GPL v3}
19
+
20
+ == Author
21
+ Kasahara Amato
22
+
23
+ == Semantic Versioning
24
+ This software basically follow the {http://semver.org/ Semantic Versioning}.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+ require 'ruby_patch'
3
+
4
+ task default: [:test]
5
+
6
+ desc "Run tests"
7
+ ::Rake::TestTask.new do |t|
8
+ t.libs = ['lib', 'test'].map{|dir| File.join(__DIR__, dir)}
9
+ t.pattern = "test/**/*_test.rb"
10
+ end
data/bin/seis_ruby CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'seis_ruby'
4
- ::SeisRuby::Bin::SeisRubyRunner.start
4
+ ::SeisRuby::Application.start
@@ -0,0 +1,76 @@
1
+ require 'thor'
2
+
3
+ class ::SeisRuby::Application < Thor
4
+ require 'yaml'
5
+ require 'pry'
6
+ require 'fileutils'
7
+ require 'ruby_patch'
8
+
9
+ COMMAND = 'seis_ruby'
10
+ COMPLETION_FILE = File.join(ENV['HOME'], ".config", COMMAND, "completion.bash")
11
+
12
+ desc "repl", "Start interactive mode."
13
+ def repl
14
+ Pry.start(
15
+ ::TOPLEVEL_BINDING,
16
+ prompt: [
17
+ lambda{|obj, nest_level, pry| "[#{pry.input_array.size}] SeisRuby(#{obj}):#{nest_level}> "},
18
+ lambda{|obj, nest_level, pry| "[#{pry.input_array.size}] SeisRuby(#{obj}):#{nest_level}* "}])
19
+ end
20
+
21
+ desc "yamlize URI", "Read data from URI, parse it and output to STDOUT in YAML format."
22
+ def yamlize(uri)
23
+ puts YAML.dump(::SeisRuby.load_file(uri))
24
+ end
25
+
26
+ desc "generate_completion", "Generate bash completion of seis_ruby's commands."
27
+ def generate_completion
28
+ FileUtils.mkdir_p(File.dirname(COMPLETION_FILE))
29
+ FileUtils.mv(COMPLETION_FILE, "#{COMPLETION_FILE}.#{Time.now.ymdhms}.bak") if File.exist?(COMPLETION_FILE)
30
+ File.write(COMPLETION_FILE, completion_function_str)
31
+
32
+ puts <<-EOS
33
+ Please add following code to your ~/.bashrc if necessary.
34
+
35
+ # enable seis_ruby completion.
36
+ if [ -f #{COMPLETION_FILE} ]; then
37
+ source #{COMPLETION_FILE}
38
+ fi
39
+ EOS
40
+ end
41
+
42
+ desc 'version', 'Print seis_ruby version'
43
+ def version
44
+ puts ::SeisRuby::VERSION
45
+ end
46
+
47
+ private
48
+
49
+ def completion_function_str
50
+ <<-EOS
51
+ _#{COMMAND}()
52
+ {
53
+ local sub_commands current previous
54
+ sub_commands="#{sub_commands_str()}"
55
+ current="${COMP_WORDS[COMP_CWORD]}"
56
+ previous="${COMP_WORDS[COMP_CWORD-1]}"
57
+
58
+ case "${previous}" in
59
+ #{COMMAND}|help)
60
+ COMPREPLY=( $(compgen -W "${sub_commands}" ${current}) );;
61
+ * )
62
+ COMPREPLY=( $(compgen -f ${current}) );;
63
+ esac
64
+ }
65
+ complete -F _#{COMMAND} #{COMMAND}
66
+ EOS
67
+ end
68
+
69
+ def sub_commands_str
70
+ `#{COMMAND} --help`\
71
+ .split("\n")\
72
+ .select{|l| l =~ / *#{COMMAND} /}\
73
+ .map{|l| l.split[1]}\
74
+ .join(' ')
75
+ end
76
+ end
@@ -0,0 +1,15 @@
1
+ module ::SeisRuby::Command
2
+ require 'yaml'
3
+
4
+ # @param [String] uri URI of file or database query.
5
+ # @return [Array, Hash, ::SeisRuby::Data::*]
6
+ def load_file(uri)
7
+ if (klass = ::SeisRuby::Data::Sac).uri_for_self?(uri)
8
+ klass.load_file(uri)
9
+ elsif (klass = ::SeisRuby::Database::GlobalCmtCatalogSearch).uri_for_self?(uri)
10
+ klass.load_file(uri)
11
+ else
12
+ raise ArgumentError, "File type undetectable: #{uri}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class ::File
2
+ require 'open-uri'
3
+
4
+ def self.read_uri(uri)
5
+ ::Kernel.open(uri){|io| io.read}
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ::SeisRuby::CoreExt
2
+ %w[
3
+ file
4
+ ].each{|lib| require "seis_ruby/core_ext/#{lib}"}
5
+ end
@@ -1,77 +1,97 @@
1
- module SeisRuby
2
- module Data
3
- module Cmtsolution
4
- require 'ruby_patch'
5
- extend ::RubyPatch::AutoLoad
1
+ class ::SeisRuby::Data::Cmtsolution < ::SeisRuby::Data
2
+ require 'ostruct'
6
3
 
7
- ParseError = Class.new(StandardError)
4
+ def self.load_file(file)
5
+ load(File.read_uri(file), file: file)
6
+ end
8
7
 
9
- module_function
8
+ def self.load(raw_data, meta_data)
9
+ new(raw_data, meta_data)
10
+ end
10
11
 
11
- def _preprocess_for_parse(text_for_one_event)
12
- text_for_one_event\
13
- .sub(/\A\n*/, '')\
14
- .sub(/\n*\z/, '')\
15
- .split("\n")
16
- end
12
+ def initialize(raw_data, meta_data)
13
+ @raw_data = raw_data
14
+ @meta_data = meta_data
15
+ parse!
16
+ end
17
+ attr_accessor :hypocenter
18
+ attr_accessor :centroid
17
19
 
18
- def _parse_lines(lines)
19
- h = {hypocenter: {}, centroid: {}}
20
+ private
20
21
 
21
- line0 = lines[0]
22
- h[:hypocenter][:data_source] = line0[0..4].strip
23
- line0_rest = line0[5..-1].split
24
- [:year, :month, :day, :hour, :minute].each{|k|
25
- raise ParseError if line0_rest.empty?
26
- h[:hypocenter][k] = line0_rest.shift.to_i
27
- }
28
- [:second, :latitude, :longitude, :depth, :mb, :ms].each{|k|
29
- raise ParseError if line0_rest.empty?
30
- h[:hypocenter][k] = line0_rest.shift.to_f
31
- }
32
- h[:hypocenter][:region_name] = line0_rest.join(' ')
33
- h[:event_name] = lines[1].split(':')[1].strip
34
- values = lines[2..-1].map{|line| line.split(':')[1].to_f}
35
- [
36
- :time_shift, :half_duration,
37
- :latitude, :longitude, :depth,
38
- :mrr, :mtt, :mpp, :mrt, :mrp, :mtp,
39
- ].zip(values).each{|k, v| h[:centroid][k] = v}
22
+ def parse!
23
+ lines\
24
+ = @raw_data\
25
+ .split("\n")\
26
+ .map(&:strip)\
27
+ .delete_if(&:empty?)
28
+ @hypocenter = Hypocenter.new(lines[0])
29
+ @centroid = Centroid.new(lines[1..-1])
30
+ end
40
31
 
32
+ class Hypocenter < OpenStruct
33
+ FIELDS = [
34
+ # name, column, converter
35
+ [:data_source, ( 0..3 ), :strip],
36
+ [:year, ( 4..8 ), :to_i ],
37
+ [:month, ( 9..11), :to_i ],
38
+ [:day, (12..14), :to_i ],
39
+ [:hour, (15..17), :to_i ],
40
+ [:minute, (18..20), :to_i ],
41
+ [:second, (21..26), :to_f ],
42
+ [:latitude, (27..35), :to_f ],
43
+ [:longitude, (36..45), :to_f ],
44
+ [:depth, (46..51), :to_f ],
45
+ [:mb, (52..55), :to_f ],
46
+ [:ms, (56..59), :to_f ],
47
+ [:region_name, (60..-1), :strip],
48
+ ]
41
49
 
42
- h
43
- end
50
+ def initialize(str)
51
+ h = {}
52
+ FIELDS.each{|name, column, converter|
53
+ h[name] = str[column].__send__(converter)
54
+ }
55
+ super(h)
56
+ end
57
+ attr_accessor :table
44
58
 
45
- def _parse_one_event(text_for_one_event)
46
- begin
47
- lines = _preprocess_for_parse(text_for_one_event)
48
- _parse_lines(lines)
49
- rescue
50
- raise ParseError, text_for_one_event
51
- end
52
- end
59
+ # Origin time
60
+ def origin_time
61
+ sec = self.second
62
+ int_sec = sec.to_i
63
+ decimal_sec = sec - int_sec
64
+ micro_sec = (decimal_sec*(10**6)).round
65
+ Time.gm(
66
+ self.year, self.month, self.day,
67
+ self.hour, self.minute, int_sec, micro_sec)
68
+ end
69
+ end
53
70
 
54
- # @param [String, Array<String>] events List of CMTSOLUTION format texts.
55
- # @return [Array<Hash>] You can access :event_name, :hypocenter, :centroid.
56
- def parse(*events)
57
- h = events.flatten.map{|ev|
58
- begin
59
- _parse_one_event(ev)
60
- rescue ParseError => e
61
- e
62
- end
63
- }.group_by{|o| o.exception?}
64
- h.default_proc = lambda{|h, k| h[k] = []}
65
- unless (error_list = h[true]).empty?
66
- error_message = error_list.map{|e| e.message}.join("\n\n")
67
- raise ParseError, <<-EOS
68
- Could not parse following events:
69
- #{error_message}
70
- EOS
71
- end
71
+ class Centroid < OpenStruct
72
+ FIELDS = [
73
+ # name, converter
74
+ [:event_name, :strip],
75
+ [:time_shift, :to_f ],
76
+ [:half_duration, :to_f ],
77
+ [:latitude, :to_f ],
78
+ [:longitude, :to_f ],
79
+ [:depth, :to_f ],
80
+ [:mrr, :to_f ],
81
+ [:mtt, :to_f ],
82
+ [:mpp, :to_f ],
83
+ [:mrt, :to_f ],
84
+ [:mrp, :to_f ],
85
+ [:mtp, :to_f ],
86
+ ]
72
87
 
73
- h[false]
74
- end
88
+ def initialize(lines)
89
+ h = {}
90
+ FIELDS.zip(lines){|(name, converter), str|
91
+ h[name] = str.split(':', 2).last.__send__(converter)
92
+ }
93
+ super(h)
75
94
  end
95
+ attr_accessor :table
76
96
  end
77
97
  end
@@ -0,0 +1,49 @@
1
+ module ::SeisRuby::Data::Sac::Ascii::Head
2
+ N_LINES = 30
3
+
4
+ RANGES_15_BY_5 = [[(0..14), (15..29), (30..44), (45..59), (60..-1)]]
5
+ RANGE_10_BY_5 = [[(0..9), (10..19), (20..29), (30..39), (40..-1)]]
6
+ RANGES_8_BY_3 = [[(0..7), (8..15), (16..-1)]]
7
+ RANGES = [
8
+ *(RANGES_15_BY_5*14),
9
+ *(RANGE_10_BY_5*8),
10
+ [(0..7), (8..-1)],
11
+ *(RANGES_8_BY_3*7),
12
+ ]
13
+
14
+ FORMATS_15_7G_BY_5 = ["%#15.7g", "%#15.7g", "%#15.7g", "%#15.7g", "%#15.7g\n"]
15
+ FORMATS_10I_BY_5 = ["%10i", "%10i", "%10i", "%10i", "%10i\n"]
16
+ FORMATS_8S_BY_3 = ["%-8s", "%-8s", "%-8s\n"]
17
+ FORMATS = [
18
+ *(FORMATS_15_7G_BY_5*14),
19
+ *(FORMATS_10I_BY_5*8),
20
+ "%-8s", "%-16s\n",
21
+ *(FORMATS_8S_BY_3*7),
22
+ ]
23
+
24
+ class << self
25
+ def convert_from_head_str(head_str)
26
+ ::SeisRuby::Data::Sac::Head\
27
+ .convert_from_head(split_head_str(head_str))
28
+ end
29
+
30
+ def convert_to_head_str(head)
31
+ array = ::SeisRuby::Data::Sac::Head\
32
+ .convert_to_head(head)
33
+ sprintf(FORMATS.join, *array)
34
+ end
35
+
36
+ private
37
+
38
+ def split_head_str(head_str)
39
+ head_lines = head_str.split("\n")
40
+ raise ArgumentError unless head_lines.size == N_LINES
41
+
42
+ head_lines\
43
+ .zip(RANGES)\
44
+ .map{|line, ranges| ranges\
45
+ .map{|range| line[range]}}\
46
+ .flatten
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,61 @@
1
+ class ::SeisRuby::Data::Sac::Ascii < ::SeisRuby::Data::Sac
2
+ require 'seis_ruby/data/sac/ascii/head'
3
+
4
+ EXT = '.sac_ascii'
5
+
6
+ DATA_COLUMN_SIZE = 5
7
+ DATA_FORMAT_STRING = '%#15.7g'
8
+
9
+ class << self
10
+ def parse(str)
11
+ head_str, body_str = separate_head_body(str)
12
+
13
+ head = ::SeisRuby::Data::Sac::Ascii::Head\
14
+ .convert_from_head_str(head_str)
15
+ body = ::SeisRuby::Data::Sac::Body\
16
+ .shape_body(body_str.split.map(&:to_f), head)
17
+
18
+ [head, body]
19
+ end
20
+
21
+ def dump(head, body)
22
+ ::SeisRuby::Data::Sac::Ascii::Head.convert_to_head_str(head)\
23
+ + format_body(::SeisRuby::Data::Sac::Body.body_for_dump(body, head))
24
+ end
25
+
26
+ def uri_for_self?(uri)
27
+ uri =~ /#{Regexp.escape(EXT)}\z/i
28
+ end
29
+
30
+ private
31
+
32
+ def format_body(body)
33
+ body\
34
+ .each_slice(DATA_COLUMN_SIZE)\
35
+ .map{|line| line\
36
+ .map{|v| sprintf(DATA_FORMAT_STRING, v)}\
37
+ .join << "\n"}\
38
+ .join
39
+ end
40
+
41
+ def separate_head_body(str)
42
+ # Use <tt>nth_index</tt> to deal with bloken SAC_ASCII file produced by SAC's <tt>write alpha</tt> command.
43
+ last_of_head = nth_index(str, "\n", ::SeisRuby::Data::Sac::Ascii::Head::N_LINES)
44
+ head_str = str[0..last_of_head]
45
+ body_str = str[(last_of_head + 1)..-1]
46
+
47
+ [head_str, body_str]
48
+ end
49
+
50
+ def nth_index(str, pattern, nth = 1, pos = 0)
51
+ raise ArgumentError, "nth should >= 1: #{nth}" if nth < 1
52
+
53
+ index = str.index(pattern, pos)
54
+ if index.nil? || nth == 1
55
+ index
56
+ else
57
+ nth_index(str, pattern, nth - 1, index + 1)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,5 @@
1
+ module ::SeisRuby::Data::Sac::Binary::Head
2
+ HEAD_FORMAT_STRING = "f70i40A8A16" + "A8"*21
3
+ BODY_FORMAT_STRING = "f*"
4
+ FORMAT_STRING = HEAD_FORMAT_STRING + BODY_FORMAT_STRING
5
+ end
@@ -0,0 +1,26 @@
1
+ module ::SeisRuby::Data::Sac::Binary
2
+ require 'seis_ruby/data/sac/binary/head'
3
+
4
+ EXT = '.sac'
5
+
6
+ def self.parse(raw_data)
7
+ raw_data_array = raw_data.unpack(::SeisRuby::Data::Sac::Binary::Head::FORMAT_STRING)
8
+ head = ::SeisRuby::Data::Sac::Head\
9
+ .convert_from_head(raw_data_array[0...::SeisRuby::Data::Sac::Head::NAMES.size])
10
+ body = ::SeisRuby::Data::Sac::Body\
11
+ .shape_body(raw_data_array[head.size..-1], head)
12
+
13
+ [head, body]
14
+ end
15
+
16
+ def self.dump(head, body)
17
+ (
18
+ ::SeisRuby::Data::Sac::Head.convert_to_head(head)\
19
+ + ::SeisRuby::Data::Sac::Body.body_for_dump(body, head))\
20
+ .pack(::SeisRuby::Data::Sac::Binary::Head::FORMAT_STRING)
21
+ end
22
+
23
+ def self.uri_for_self?(uri)
24
+ uri =~ /#{Regexp.escape(EXT)}\z/i
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module ::SeisRuby::Data::Sac::Body
2
+ module_function
3
+
4
+ def shape_body(body, head)
5
+ case head[:iftype]
6
+ when :itime
7
+ # [y0, y1, ...]
8
+ body
9
+ when :ixy, :iamph
10
+ # ixy: [[x0, y0], [x1, y1], ...]
11
+ # iamph: [[theta0, amp0], [theta1, amp1], ...]
12
+ body\
13
+ .each_slice(body.size/2)\
14
+ .to_a\
15
+ .reverse\
16
+ .transpose
17
+ when :irlim
18
+ # [(real0, imaginary0), (real1, imaginary1), ...]
19
+ body\
20
+ .each_slice(body.size/2)\
21
+ .to_a\
22
+ .transpose\
23
+ .map{|real, imaginary| Complex(real, imaginary)}
24
+ when :ixyz
25
+ # z[ix][iy]
26
+ body\
27
+ .each_slice(head[:nxsize])\
28
+ .to_a\
29
+ .transpose
30
+ else
31
+ raise ArgumentError, "Unknown iftype: #{head[:iftype].inspect}"
32
+ end
33
+ end
34
+
35
+ def body_for_dump(body, head)
36
+ case head[:iftype]
37
+ when :itime
38
+ # [y0, y1, ...]
39
+ body
40
+ when :ixy, :iamph
41
+ # ixy: [[x0, y0], [x1, y1], ...]
42
+ # iamph: [[theta0, amp0], [theta1, amp1], ...]
43
+ body\
44
+ .transpose\
45
+ .reverse\
46
+ .flatten
47
+ when :irlim
48
+ # [(real0, imaginary0), (real1, imaginary1), ...]
49
+ body\
50
+ .map{|c| [c.real, c.imaginary]}\
51
+ .transpose\
52
+ .flatten
53
+ when :ixyz
54
+ # z[ix][iy]
55
+ body\
56
+ .transpose\
57
+ .flatten
58
+ else
59
+ raise ArgumentError, "Unknown iftype: #{head[:iftype].inspect}"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,162 @@
1
+ module ::SeisRuby::Data::Sac::Head
2
+
3
+ # SAC header field names.
4
+ NAMES = [
5
+ :delta, :depmin, :depmax, :scale, :odelta,
6
+ :b, :e, :o, :a, :internal1,
7
+ :t0, :t1, :t2, :t3, :t4,
8
+ :t5, :t6, :t7, :t8, :t9,
9
+ :f, :resp0, :resp1, :resp2, :resp3,
10
+ :resp4, :resp5, :resp6, :resp7, :resp8,
11
+ :resp9, :stla, :stlo, :stel, :stdp,
12
+ :evla, :evlo, :evel, :evdp, :mag,
13
+ :user0, :user1, :user2, :user3, :user4,
14
+ :user5, :user6, :user7, :user8, :user9,
15
+ :dist, :az, :baz, :gcarc, :internal2,
16
+ :internal3, :depmen, :cmpaz, :cmpinc, :xminimum,
17
+ :xmaximum, :yminimum, :ymaximum, :adjtm, :unused1,
18
+ :unused2, :unused3, :unused4, :unused5, :unused6,
19
+ :nzyear, :nzjday, :nzhour, :nzmin, :nzsec,
20
+ :nzmsec, :nvhdr, :norid, :nevid, :npts,
21
+ :nsnpts, :nwfid, :nxsize, :nysize, :unused7,
22
+ :iftype, :idep, :iztype, :unused8, :iinst,
23
+ :istreg, :ievreg, :ievtyp, :iqual, :isynth,
24
+ :imagtyp, :imagsrc, :unused9, :unused10, :unused11,
25
+ :unused12, :unused13, :unused14, :unused15, :unused16,
26
+ :leven, :lpspol, :lovrok, :lcalda, :unused17,
27
+ :kstnm, :kevnm,
28
+ :khole, :ko, :ka,
29
+ :kt0, :kt1, :kt2,
30
+ :kt3, :kt4, :kt5,
31
+ :kt6, :kt7, :kt8,
32
+ :kt9, :kf, :kuser0,
33
+ :kuser1, :kuser2, :kcmpnm,
34
+ :knetwk, :kdatrd, :kinst,
35
+ ]
36
+
37
+ # Enumerated values and their meanings.
38
+ ENUMERATED_VALUES = [
39
+ :itime, :irlim, :iamph, :ixy,
40
+ :iunkn, :idisp, :ivel, :iacc,
41
+ :ib, :iday, :io, :ia,
42
+ :it0, :it1, :it2, :it3,
43
+ :it4, :it5, :it6, :it7,
44
+ :it8, :it9, :iradnv, :itannv,
45
+ :iradev, :itanev, :inorth, :ieast,
46
+ :ihorza, :idown, :iup, :illlbb,
47
+ :iwwsn1, :iwwsn2, :ihglp, :isro,
48
+ :inucl, :ipren, :ipostn, :iquake,
49
+ :ipreq, :ipostq, :ichem, :iother,
50
+ :igood, :iglch, :idrop, :ilowsn,
51
+ :irldta, :ivolts, :ixyz, :imb,
52
+ :ims, :iml, :imw, :imd,
53
+ :imx, :ineic, :ipde, :iisc,
54
+ :ireb, :iusgs, :ibrk, :icaltech,
55
+ :illnl, :ievloc, :ijsop, :iuser,
56
+ :iunknown, :iqb, :iqb1, :iqb2,
57
+ :iqbx, :iqmt, :ieq, :ieq1,
58
+ :ieq2, :ime, :iex, :inu,
59
+ :inc, :io, :il, :ir,
60
+ :it, :iu,
61
+ ].map.with_index.with_object({}){|(v, i), h| h[i + 1] = v}
62
+
63
+ # Float
64
+ CONVERTER_FROM_HEAD_FLOAT = lambda{|x|
65
+ x_f = x.to_f
66
+ return nil if x_f == -12345.0
67
+ x_f
68
+ }
69
+ CONVERTER_TO_HEAD_FLOAT = lambda{|x|
70
+ return -12345.0 if x.nil?
71
+ x.to_f
72
+ }
73
+
74
+ # Integer
75
+ CONVERTER_FROM_HEAD_INTEGER = lambda{|n|
76
+ n_i = n.to_f.round
77
+ return nil if n_i == -12345
78
+ n_i
79
+ }
80
+ CONVERTER_TO_HEAD_INTEGER = lambda{|n|
81
+ return -12345 if n.nil?
82
+ n.to_f.round
83
+ }
84
+
85
+ # Enumerated value
86
+ CONVERTER_FROM_HEAD_ENUMERATED_VALUE = lambda{|n|
87
+ n_i = n.to_f.round
88
+ return nil if n_i == -12345
89
+ raise ArgumentError, "Enumerated value out of range: #{n.inspect}.to_f.round" unless ENUMERATED_VALUES.has_key?(n_i)
90
+ ENUMERATED_VALUES.fetch(n_i)
91
+ }
92
+ CONVERTER_TO_HEAD_ENUMERATED_VALUE = lambda{|sym|
93
+ return -12345 if sym.nil?
94
+ sym_sym = sym.to_sym
95
+ raise ArgumentError, "Unknown enumerated value: #{sym.inspect}.to_sym" unless ENUMERATED_VALUES.has_value?(sym_sym)
96
+ ENUMERATED_VALUES.key(sym_sym)
97
+ }
98
+
99
+ # Logical
100
+ CONVERTER_FROM_HEAD_LOGICAL = lambda{|n| n.to_f.round == 1}
101
+ CONVERTER_TO_HEAD_LOGICAL = lambda{|v|
102
+ if v
103
+ 1
104
+ else
105
+ 0
106
+ end
107
+ }
108
+
109
+ # String
110
+ CONVERTER_FROM_HEAD_STRING = lambda{|str|
111
+ stripped_str = str.to_s.strip
112
+ return nil if stripped_str == "-12345"
113
+ stripped_str
114
+ }
115
+ CONVERTER_TO_HEAD_STRING = lambda{|str|
116
+ return "-12345" if str.nil?
117
+ str.to_s.strip
118
+ }
119
+
120
+ CONVERTERS_FROM_HEAD\
121
+ = [CONVERTER_FROM_HEAD_FLOAT]*70\
122
+ + [CONVERTER_FROM_HEAD_INTEGER]*15\
123
+ + [CONVERTER_FROM_HEAD_ENUMERATED_VALUE]*20\
124
+ + [CONVERTER_FROM_HEAD_LOGICAL]*5\
125
+ + [CONVERTER_FROM_HEAD_STRING]*23
126
+
127
+ CONVERTERS_TO_HEAD\
128
+ = [CONVERTER_TO_HEAD_FLOAT]*70\
129
+ + [CONVERTER_TO_HEAD_INTEGER]*15\
130
+ + [CONVERTER_TO_HEAD_ENUMERATED_VALUE]*20\
131
+ + [CONVERTER_TO_HEAD_LOGICAL]*5\
132
+ + [CONVERTER_TO_HEAD_STRING]*23
133
+
134
+ Field = Struct.new(:name, :converter_from_head, :converter_to_head)
135
+ FIELDS = NAMES\
136
+ .zip(CONVERTERS_FROM_HEAD, CONVERTERS_TO_HEAD)\
137
+ .map{|name, from, to| Field.new(name, from, to)}
138
+
139
+ class << self
140
+ # @param [Array] array
141
+ # @return [Hash]
142
+ def convert_from_head(array)
143
+ name_val = FIELDS\
144
+ .zip(array)\
145
+ .map{|field, val|
146
+ begin
147
+ [field.name, field.converter_from_head.call(val)]
148
+ rescue ArgumentError => e
149
+ $stderr.puts "#{field.name.inspect}"
150
+ raise e
151
+ end
152
+ }
153
+ Hash[name_val]
154
+ end
155
+
156
+ # @param [Hash] hash
157
+ # @return [Array]
158
+ def convert_to_head(hash)
159
+ FIELDS.map{|field| field.converter_to_head.call(hash[field.name])}
160
+ end
161
+ end
162
+ end