Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # WORK IN PROGRESS (c) A.R.R. JSTN 2017
- class RadarImageParser
- attr_reader :data
- CODES_WITH_ELEVATION_ANGLES = [ 19, 20, 32, 37, 38, 65, 66, 67, 90, 94, 159 ]
- CODES_WITH_NONSTANDARD_THRESHOLDS = [ 32, 81, 93, 94, 99, 134, 135, 138, 153, 154, 155, 159, 161, 163, 177, 195 ]
- def initialize(io_or_string)
- @io = io_or_string.is_a?(String) ? StringIO.new(io_or_string, "rb") : io
- parse
- end
- private
- def parse
- @data = Hash.new
- # BLOCK: Message Header (ICD 3-7)
- @data[:message_header] = {
- wmo_header: string, # like "SDUS55 KABQ 031229"
- awips_header: string, # like "N0RABX"
- message_code: halfword, # -131 to -16, 0 to 211 (ICD Table II)
- message_time: unixtime,
- length_of_message: fullword, # number of bytes including header
- source_id: halfword, # 0-999
- destination_id: halfword, # 0-999
- number_of_blocks: halfword, #1-51, including this header
- }
- # BLOCK: Product Description (ICD 3-27, 3-32)
- raise "no product description block" unless halfword == -1
- @data[:product_description] = {
- radar_latitude: fullword.to_f / 1000,
- radar_longitude: fullword.to_f / 1000,
- radar_height: halfword, # -100 to 11000, feet above mean sea level
- product_code: halfword, # 16 to 299, -16 to -299 (ICD Table III)
- operational_mode: halfword, # 0-2
- volume_coverage_pattern: halfword, # 1-767
- sequence_number: halfword, # -13, 0 to 32767
- volume_scan_number: halfword, # 1 to 80
- volume_scan_time: unixtime,
- generation_time: unixtime,
- product_dependent_param_1: halfword, # ICD Table V
- product_dependent_param_2: halfword, # ICD Table V
- elevation_number: halfword, # 0-20 (for elevation based products)
- product_dependent_param_3: halfword # ICD Table V, NOTE 3
- }
- if CODES_WITH_ELEVATION_ANGLES.include? @data[:product_description][:product_code]
- param = @data[:product_description].delete(:product_dependent_param_3)
- @data[:product_description][:elevation_angle] = param.to_f / 10
- end
- if CODES_WITH_NONSTANDARD_THRESHOLDS.include? @data[:product_description][:product_code]
- raise "nonstandard thresholds TKTKTK"
- else
- 16.times do |i|
- msb = @io.getbyte
- lsb = @io.getbyte
- threshold = ""
- if msb & 0b10000000 > 0
- threshold += case lsb
- when 1 then "TH"
- when 2 then "ND"
- when 3 then "RF"
- when 4 then "BI" # Biological
- when 5 then "GC" # AP/Ground Clutter
- when 6 then "IC" # Ice Crystals
- when 7 then "GR" # Graupel
- when 8 then "WS" # Wet Snow
- when 9 then "DS" # Dry Snow
- when 10 then "RA" # Light and Moderate Rain
- when 11 then "HR" # Heavy Rain
- when 12 then "BD" # Big Drops
- when 13 then "HA" # Hail and Rain Mixed
- when 14 then "UK" # Unknown
- when 15 then "LH" # Large Hail
- when 16 then "GH" # Giant Hail
- else ""
- end
- else
- if msb & 0b00001000 > 0 # bit 4
- threshold += ">"
- elsif msb & 0b00000100 > 0 # bit 5
- threshold += "<"
- elsif msb & 0b00000010 > 0 # bit 6
- threshold += "+"
- elsif msb & 0b00000001 > 0 # bit 7
- threshold += "-"
- end
- if msb & 0b01000000 > 0 # bit 1
- threshold += lsb.to_f / 100
- elsif msb & 0b00100000 > 0 # bit 2
- threshold += lsb.to_f / 20
- elsif msb & 0b00010000 > 0 # bit 3
- threshold += lsb.to_f / 10
- else
- threshold += "#{lsb}"
- end
- end
- @data[:product_description]["threshold_#{i + 1}".to_sym] = threshold
- end
- end
- @data
- end
- def string
- @io.gets.strip
- end
- def halfword
- # 16 bit signed integer, big-endian (INT*2)
- @io.read(2).unpack("s>").first
- end
- def fullword
- # 32 bit signed integer, big-endian (INT*4)
- @io.read(4).unpack("l>").first
- end
- def unixtime
- # modified julian, offset by 1 day
- days = halfword.days - 1.day
- seconds = fullword.seconds
- Time.zone.at(days + seconds).iso8601
- end
- end
- # HC SVNT DRACONES
Add Comment
Please, Sign In to add comment