seis_ruby 0.1.2 → 0.2.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.
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