forecaster 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc80587e750da30fa99b3a4b1309c3a67d792f86
4
+ data.tar.gz: 42f6e928fc8c09c02ea0de356642884e16e10f30
5
+ SHA512:
6
+ metadata.gz: aeb8bf4f69a32551e3c2e8cbebb96378979644eefcb8bb239c7d8035c79d489c4031d1a451de812b592140cd37daeee006bfe826578e5b381aa69ed742b42dd1
7
+ data.tar.gz: 4ec0004fc10aad0a25b675c041eb975636dda8f8978952173da92f0a8f41938fa382730c60aa573747a1b3d04c688e1229ec7f4c24c1683f22ab321228552a3c
@@ -0,0 +1,24 @@
1
+ module Forecaster
2
+ class Configuration
3
+ attr_accessor :server, :wgrib2, :cache_dir, :records
4
+
5
+ def initialize
6
+ @server = "http://www.ftp.ncep.noaa.gov/data/nccf/com/gfs/prod"
7
+ @wgrib2 = "/usr/bin/wgrib2"
8
+ @cache_dir = "/tmp/forecaster"
9
+ @records = {
10
+ prate: "PRATE:surface",
11
+ tmp: "TMP:2 m above ground",
12
+ ugrd: "UGRD:10 m above ground",
13
+ vgrd: "VGRD:10 m above ground",
14
+ tcdc: "TCDC:entire atmosphere"
15
+ }
16
+ end
17
+
18
+ def self.configure(options)
19
+ options.each do |option, value|
20
+ self[option] = value
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,99 @@
1
+ require "fileutils"
2
+
3
+ module Forecaster
4
+ class Forecast
5
+ def initialize(year, month, day, hour_of_run, hour_of_forecast)
6
+ @year = year
7
+ @month = month
8
+ @day = day
9
+ @hour_of_run = hour_of_run
10
+ @hour_of_forecast = hour_of_forecast
11
+ end
12
+
13
+ def read(field, latitude: 0.0, longitude: 0.0)
14
+ wgrib2 = Forecaster.configuration.wgrib2
15
+ record = Forecaster.configuration.records[field]
16
+ cachename = Forecaster.configuration.cache_dir
17
+
18
+ filename = "gfs.t%02dz.pgrb2.0p25.f%03d" % [
19
+ @hour_of_run, @hour_of_forecast
20
+ ]
21
+ pathname = "%04d%02d%02d%02d%" % [
22
+ @year, @month, @day, @hour_of_run
23
+ ]
24
+ path = File.join(cachename, pathname, filename)
25
+
26
+ raise "'#{path}' not found" unless File.exists?(path)
27
+
28
+ out = `#{wgrib2} #{path} -lon #{longitude} #{latitude} -match "#{record}"`
29
+ lines = out.split("\n")
30
+ fields = lines.first.split(":")
31
+ params = Hash[*fields.last.split(",").map { |s| s.split("=") }.flatten]
32
+
33
+ params["val"]
34
+ end
35
+
36
+ def fetched?
37
+ cachename = Forecaster.configuration.cache_dir
38
+ pathname = "%04d%02d%02d%02d%" % [
39
+ @year, @month, @day, @hour_of_run
40
+ ]
41
+ filename = "gfs.t%02dz.pgrb2.0p25.f%03d" % [
42
+ @hour_of_run, @hour_of_forecast
43
+ ]
44
+ File.exist?(File.join(cachename, pathname, filename))
45
+ end
46
+
47
+ def fetch
48
+ return self if fetched?
49
+
50
+ server = Forecaster.configuration.server
51
+ cachename = Forecaster.configuration.cache_dir
52
+ curl = "curl -f -s -S"
53
+
54
+ pathname = "%04d%02d%02d%02d%" % [
55
+ @year, @month, @day, @hour_of_run
56
+ ]
57
+ FileUtils.mkpath(File.join(cachename, pathname))
58
+
59
+ filename = "gfs.t%02dz.pgrb2.0p25.f%03d" % [
60
+ @hour_of_run, @hour_of_forecast
61
+ ]
62
+ url = "%s/gfs.%04d%02d%02d%02d/%s" % [
63
+ server, @year, @month, @day, @hour_of_run, filename
64
+ ]
65
+ path = File.join(cachename, pathname, filename)
66
+
67
+ # puts "Downloading '#{url}.idx' ..."
68
+ cmd = "#{curl} -o #{path}.idx #{url}.idx"
69
+ return self unless system(cmd)
70
+
71
+ lines = IO.readlines("#{path}.idx")
72
+ n = lines.count
73
+ ranges = lines.each_index.reduce([]) do |r, i|
74
+ records = Forecaster.configuration.records
75
+ if records.values.any? { |record| lines[i].include?(record) }
76
+ first = lines[i].split(":")[1].to_i
77
+ last = ""
78
+
79
+ j = i
80
+ while (j += 1) < n
81
+ last = lines[j].split(":")[1].to_i - 1
82
+ break if last != first - 1
83
+ end
84
+
85
+ r << "#{first}-#{last}" # cURL syntax for a range
86
+ else
87
+ r
88
+ end
89
+ end
90
+ system("rm #{path}.idx")
91
+
92
+ # puts "Downloading '#{url}' ..."
93
+ cmd = "#{curl} -r #{ranges.join(",")} -o #{path} #{url}"
94
+ return self unless system(cmd)
95
+
96
+ self
97
+ end
98
+ end
99
+ end
data/lib/forecaster.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "forecaster/configuration"
2
+ require "forecaster/forecast"
3
+
4
+ module Forecaster
5
+ class << self
6
+ attr_accessor :configuration
7
+ end
8
+
9
+ def self.configure
10
+ self.configuration ||= Forecaster::Configuration.new
11
+ yield(configuration)
12
+ end
13
+
14
+ def self.fetch(year, month, day, hour_of_run, hour_of_forecast)
15
+ Forecaster::Forecast.new(year, month, day, hour_of_run, hour_of_forecast).fetch
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: forecaster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vincent Ollivier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Wrapper around curl and wgrib2 to fetch and read GFS data
14
+ email: v@vinc.cc
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/forecaster.rb
20
+ - lib/forecaster/configuration.rb
21
+ - lib/forecaster/forecast.rb
22
+ homepage: https://github.com/vinc/forecaster
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.2.2
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Wrapper around curl and wgrib2 to fetch and read GFS data
46
+ test_files: []