forecaster 0.0.1

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.
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: []