forecaster 0.1.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f918096d5bc3c84b8b1be9ded8a0e9597a3bf700
4
- data.tar.gz: 73a46ed9e59a3c3f05cc45c5a654d8548055fa58
3
+ metadata.gz: 95c34dc2a848deaba80e411a01e246237ed1f10a
4
+ data.tar.gz: 5d7c4597a21d4c9b703495c654406cd7e8ff12f6
5
5
  SHA512:
6
- metadata.gz: dac29049eb42eb4cee58446c28a15d090c9abdfe76426db8c732e499b2cd1dcf8f9405d7a0bfa3479496525291de9e417c54a3cbdc687b87b1d9d73aa5fbe05a
7
- data.tar.gz: 7406bb7e8d9de0afa5ff34854d826d71a34e5412a14871f82c0766d3f9793e0f212238473c32176518f8c45ed6402081efe43d7b3beee83f13dbd2a4f5221e71
6
+ metadata.gz: 04633f93c3228a300c0f88246cacb3a7a239b5e3d03da468531c89e41673d573675561a65a81b2f3307e2b01cb0d188f7fd7f2c18a91480fcc292891d18c57d1
7
+ data.tar.gz: ced4bf158fc21603cc21854f41dc4894df3e2b207d12760eecdd4b5f7d235ae6865e75c7e54e4d7d863127031c631ee63a58a16c73b9da01a8b9bf340b5c0872
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0 (2018-03-31)
4
+
5
+ - Colorize and refactor output
6
+ - Update dependencies
7
+ - Replace `Forecast#fetch_index` by `Forecast#fetch_ranges`
8
+ - Fix bug when time is not in UTC
9
+ - Drop support of ruby version < 2.1
10
+ - Increase code coverage
11
+
12
+ ## 0.1.0 (2016-06-11)
13
+
14
+ - Add forecast executable
15
+ - Cache geolocation requests
16
+ - Replace curl wrapper with excon gem
17
+ - Fix cache path
18
+
19
+ ## 0.0.2 (2016-06-09)
20
+
21
+ - Raise error when field could not be read in file
22
+
23
+ ## 0.0.1 (2015-05-04)
24
+
25
+ - Publish initial version
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2018 Vincent Ollivier
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,170 @@
1
+ Forecaster
2
+ ==========
3
+
4
+ [![Gem](https://img.shields.io/gem/v/forecaster.svg)](https://rubygems.org/gems/forecaster)
5
+ [![Build Status](https://api.travis-ci.org/vinc/forecaster.svg?branch=master)](http://travis-ci.org/vinc/forecaster)
6
+ [![Code Climate](https://codeclimate.com/github/vinc/forecaster.svg)](https://codeclimate.com/github/vinc/forecaster)
7
+ [![Code Coverage](https://codecov.io/gh/vinc/forecaster/branch/master/graph/badge.svg)](https://codecov.io/gh/vinc/forecaster)
8
+ [![Gemnasium](https://img.shields.io/gemnasium/vinc/forecaster.svg)](https://gemnasium.com/github.com/vinc/forecaster)
9
+
10
+ Ruby wrapper around `wgrib2` to read data directly from the Global Forecast
11
+ System (GFS).
12
+
13
+ [![asciicast](https://asciinema.org/a/146117.png)](https://asciinema.org/a/146117)
14
+
15
+
16
+ Installation
17
+ ------------
18
+
19
+ $ gem install forecaster
20
+
21
+ Alternatively you can build the gem from its repository:
22
+
23
+ $ git clone git://github.com/vinc/forecaster.git
24
+ $ cd forecaster
25
+ $ gem build forecaster.gemspec
26
+ $ gem install forecaster-0.1.1.gem
27
+
28
+ In both cases you need to make sure that you have `wgrib2` present in your
29
+ system.
30
+
31
+ To install the `wgrib2` from source:
32
+
33
+ $ wget http://www.ftp.cpc.ncep.noaa.gov/wd51we/wgrib2/wgrib2.tgz
34
+ $ tar -xzvf wgrib2.tgz
35
+ $ cd grib2
36
+ $ export CC=gcc
37
+ $ export FC=gfortran
38
+ $ make
39
+ $ sudo cp wgrib2/wgrib2 /usr/local/bin/
40
+
41
+ Usage
42
+ -----
43
+
44
+ ```ruby
45
+ require "forecaster"
46
+ ```
47
+
48
+ To configure the gem:
49
+
50
+ ```ruby
51
+ Forecaster.configure do |config|
52
+ config.wgrib2_path = "/usr/local/bin/wgrib2"
53
+ config.cache_dir = "/tmp/forecaster"
54
+ config.records = {
55
+ :temperature => ":TMP:2 m above ground:",
56
+ :humidity => ":RH:2 m above ground:",
57
+ :pressure => ":PRES:surface:"
58
+ }
59
+ end
60
+ ```
61
+
62
+ Forecaster saves large files containing the data of GFS runs from the NOAA
63
+ servers in the cache directory, but only the parts of the files containing
64
+ the records defined in the configuration will be downloaded.
65
+
66
+ You can find the list of available records [online][1] or by reading any
67
+ `.idx` files distributed along with the GFS files.
68
+
69
+ A record is identified by a variable and a layer separated by colon
70
+ characters. In the case of the temperature for example, those attributes
71
+ are `TMP` and `2 m above ground`. See the [documentation of wgrib2][2] for
72
+ more information.
73
+
74
+ To fetch a forecast:
75
+
76
+ ```ruby
77
+ t = Time.now.utc # All the dates should be expressed in UTC
78
+ y = t.year # year of GFS run
79
+ m = t.month # month of GFS run
80
+ d = t.day # day of GFS run
81
+ c = 0 # hour of GFS run (must be a multiple of 6)
82
+ h = 12 # hour of forecast (must be a multiple of 3)
83
+ forecast = Forecaster.fetch(y, m, d, c, h) # Forecaster::Forecast
84
+ ```
85
+
86
+ To read the [record][1] of a forecast:
87
+
88
+ ```ruby
89
+ res = forecast.read(:temperature, longitude: 48.1147, latitude: -1.6794) # String in Kelvin
90
+ val = res.to_f - 273.15 # Float in degree Celsius
91
+ ```
92
+
93
+ [1]: http://www.nco.ncep.noaa.gov/pmb/products/gfs/gfs_upgrade/gfs.t06z.pgrb2.0p25.f006.shtml
94
+ [2]: http://www.cpc.ncep.noaa.gov/products/wesley/wgrib2/
95
+
96
+
97
+ Command line
98
+ ------------
99
+
100
+ Forecaster has a command line tool that try to be smart:
101
+
102
+ $ forecast for tomorrow afternoon in auckland
103
+ GFS Weather Forecast
104
+
105
+ Date: 2016-05-13
106
+ Time: 12:00:00
107
+ Zone: +1200
108
+ Latitude: -36.8 °
109
+ Longitude: 174.8 °
110
+
111
+ Pressure: 1013.8 hPa
112
+ Temperature: 21.7 °C
113
+ Wind Direction: 163.5 °
114
+ Wind Speed: 8.0 m/s
115
+ Precipitation: 0.0 mm
116
+ Humidity: 65.1 %
117
+ Cloud Cover: 0.0 %
118
+
119
+ But you can use it in a more verbose way:
120
+
121
+ $ TZ=America/Los_Angeles forecast --time "2016-05-12 09:00:00" \
122
+ --latitude "37.7749295" \
123
+ --longitude "-122.4194155" \
124
+ --debug
125
+ Requested time: 2016-05-12 09:00:00 -0700
126
+ GFS Run time: 2016-05-11 23:00:00 -0700
127
+ Forecast time: 2016-05-12 08:00:00 -0700
128
+
129
+ Downloading: 'http://www.ftp.ncep.noaa.gov/data/nccf/com/gfs/prod/gfs.20160
130
+ 51200/gfs.t00z.pgrb2.0p25.f015'
131
+ Reading index file...
132
+ Length: 4992281 (4.76M)
133
+
134
+ 100% [===========================================>] 696 KB/s Time: 00:00:07
135
+
136
+ GFS Weather Forecast
137
+
138
+ Date: 2016-05-12
139
+ Time: 08:00:00
140
+ Zone: -0700
141
+ Latitude: 37.8 °
142
+ Longitude: -122.4 °
143
+
144
+ Pressure: 1013.5 hPa
145
+ Temperature: 13.4 °C
146
+ Wind Direction: 167.3 °
147
+ Wind Speed: 1.0 m/s
148
+ Precipitation: 0.0 mm
149
+ Humidity: 89.7 %
150
+ Cloud Cover: 0.0 %
151
+
152
+ To use automatically the timezone of a location you will need to create
153
+ a free [GeoNames account][3] and export your username in an environment
154
+ variable:
155
+
156
+ export GEONAMES_USERNAME=<username>
157
+
158
+ And while you're doing that, you can also export your favorite location
159
+ to avoid typing it every time:
160
+
161
+ export FORECAST_LATITUDE=<latitude>
162
+ export FORECAST_LONGITUDE=<longitude>
163
+
164
+ [3]: http://www.geonames.org/login
165
+
166
+
167
+ License
168
+ -------
169
+
170
+ Copyright (c) 2015-2018 Vincent Ollivier. Released under MIT.
@@ -4,6 +4,7 @@ require "chronic"
4
4
  require "timezone"
5
5
  require "geocoder"
6
6
  require "ruby-progressbar"
7
+ require "rainbow"
7
8
 
8
9
  require "forecaster"
9
10
 
@@ -13,8 +14,6 @@ module Forecaster
13
14
  class CLI
14
15
  include Singleton # TODO: Find how best to organize CLI class
15
16
 
16
- FORECAST_FORMAT = " %-15s % 7.1f %s".freeze
17
-
18
17
  def self.start(args, env)
19
18
  instance.start(args, env)
20
19
  end
@@ -31,7 +30,7 @@ module Forecaster
31
30
  cache_file = File.join(Forecaster.configuration.cache_dir, "forecast.yml")
32
31
  @store = YAML::Store.new(cache_file)
33
32
 
34
- puts "GFS Weather Forecast"
33
+ puts Rainbow("GFS Weather Forecast").bright
35
34
  puts
36
35
 
37
36
  lat, lon = get_location(opts, env)
@@ -95,10 +94,10 @@ module Forecaster
95
94
 
96
95
  # Get location
97
96
  def get_location(opts, env)
98
- if opts[:location]
97
+ if (opts[:location] || "").length > 0
99
98
  @store.transaction do
100
99
  if opts[:debug]
101
- puts format("%-15s '%s'", "Geolocalizing:", opts[:location])
100
+ putf_debug("Geolocalizing", opts[:location], "'%s'")
102
101
  end
103
102
 
104
103
  key = "geocoder:#{opts[:location]}"
@@ -106,9 +105,9 @@ module Forecaster
106
105
 
107
106
  if opts[:debug]
108
107
  if lat && lon
109
- puts format("%-15s %s, %s", "Found:", lat, lon)
108
+ putf_debug("Location", lat, "%05.2f, %05.2f", optional: lon)
110
109
  else
111
- puts "Not found"
110
+ puts Rainbow("Location not found").red
112
111
  end
113
112
  puts
114
113
  end
@@ -155,25 +154,26 @@ module Forecaster
155
154
  forecast = Forecast.at(time)
156
155
 
157
156
  if opts[:debug]
158
- puts format("%-15s %s", "Requested time:", time.localtime)
159
- puts format("%-15s %s", "GFS run time:", forecast.run_time.localtime)
160
- puts format("%-15s %s", "Forecast time:", forecast.time.localtime)
157
+ putf_debug("Requested time", time.localtime, "%s")
158
+ putf_debug("GFS run time", forecast.run_time.localtime, "%s")
159
+ putf_debug("Forecast time", forecast.time.localtime, "%s")
161
160
  puts
162
161
  end
163
162
 
164
163
  unless forecast.fetched?
165
164
  if opts[:debug]
166
- puts "Downloading: '#{forecast.url}'"
165
+ putf_debug("Downloading", forecast.url, "'%s'")
167
166
 
168
- puts "Reading index file..."
169
- ranges = forecast.fetch_index
167
+ putf_debug("Reading index file", "", "")
168
+ records = Forecaster.configuration.records.values
169
+ ranges = forecast.fetch_ranges
170
+ ranges = records.map { |k| ranges[k] } # Filter ranges
170
171
 
171
- filesize = ranges.reduce(0) do |acc, range|
172
- first, last = range.split("-").map(&:to_i)
173
- acc + last - first
172
+ filesize = ranges.reduce(0) do |acc, (first, last)|
173
+ acc + last - first # FIXME: `last == nil` on last range of index file
174
174
  end
175
- filesize_in_megabytes = (filesize.to_f / (1 << 20)).round(2)
176
- puts "Length: #{filesize} (#{filesize_in_megabytes}M)"
175
+ n = (filesize.to_f / (1 << 20)).round(2)
176
+ putf_debug("Length", filesize, "%d (%.2fM)", optional: n)
177
177
  puts
178
178
 
179
179
  progressbar = ProgressBar.create(
@@ -200,38 +200,62 @@ module Forecaster
200
200
 
201
201
  # Print forecast
202
202
  def print_forecast(forecast, lat, lon)
203
- forecast_time = forecast.time.localtime
203
+ putf("Date", forecast.time.localtime.strftime("%Y-%m-%d"), "%s")
204
+ putf("Time", forecast.time.localtime.strftime("%T"), "%s")
205
+ putf("Zone", forecast.time.localtime.strftime("%z"), "%s")
204
206
 
205
- puts format(" %-11s % 11s", "Date:", forecast_time.strftime("%Y-%m-%d"))
206
- puts format(" %-11s % 11s", "Time:", forecast_time.strftime("%T"))
207
- puts format(" %-11s % 11s", "Zone:", forecast_time.strftime("%z"))
208
- puts format(FORECAST_FORMAT, "Latitude:", lat, "°")
209
- puts format(FORECAST_FORMAT, "Longitude:", lon, "°")
207
+ # Coordinates rounded to the precision of the GFS model
208
+ putf("Latitude", (lat / 0.25).round / 4.0, "%05.2f °")
209
+ putf("Longitude", (lon / 0.25).round / 4.0, "%05.2f °")
210
210
  puts
211
211
 
212
- pres = forecast.read(:pres, :latitude => lat, :longitude => lon).to_f
213
212
  tmp = forecast.read(:tmp, :latitude => lat, :longitude => lon).to_f
214
213
  ugrd = forecast.read(:ugrd, :latitude => lat, :longitude => lon).to_f
215
214
  vgrd = forecast.read(:vgrd, :latitude => lat, :longitude => lon).to_f
216
215
  prate = forecast.read(:prate, :latitude => lat, :longitude => lon).to_f
217
216
  rh = forecast.read(:rh, :latitude => lat, :longitude => lon).to_f
218
217
  tcdc = forecast.read(:tcdc, :latitude => lat, :longitude => lon).to_f
218
+ pres = forecast.read(:pres, :latitude => lat, :longitude => lon).to_f
219
219
 
220
- pressure = pres / 100.0
221
220
  temperature = tmp - 273.15
222
- wind_speed = Math.sqrt(ugrd**2 + vgrd**2)
223
221
  wind_direction = (270 - Math.atan2(ugrd, vgrd) * 180 / Math::PI) % 360
222
+ wind_speed = Math.sqrt(ugrd**2 + vgrd**2)
224
223
  precipitation = prate * 3600
225
224
  humidity = rh
226
225
  cloud_cover = tcdc
226
+ pressure = pres / 100.0
227
+
228
+ wdir = compass_rose(wind_direction)
227
229
 
228
- puts format(FORECAST_FORMAT, "Pressure:", pressure, "hPa")
229
- puts format(FORECAST_FORMAT, "Temperature:", temperature, "°C")
230
- puts format(FORECAST_FORMAT, "Wind Speed:", wind_speed, "m/s")
231
- puts format(FORECAST_FORMAT, "Wind Direction:", wind_direction, "°")
232
- puts format(FORECAST_FORMAT, "Precipitation:", precipitation, "mm")
233
- puts format(FORECAST_FORMAT, "Humidity:", humidity, "%")
234
- puts format(FORECAST_FORMAT, "Cloud Cover:", cloud_cover, "%")
230
+ putf("Temperature", temperature, "%.0f °C")
231
+ putf("Wind", wind_speed, "%.1f m/s (%s)", optional: wdir)
232
+ putf("Precipitation", precipitation, "%.1f mm")
233
+ putf("Humidity", humidity, "%.0f %")
234
+ putf("Cloud Cover", cloud_cover, "%.0f %")
235
+ putf("Pressure", pressure, "%.0f hPa")
236
+ end
237
+
238
+ def putf(name, value, fmt, optional: "", color: :cyan)
239
+ left_column = Rainbow(format(" %-20s", name)).color(color)
240
+ right_column = Rainbow(format(fmt, value, optional))
241
+ puts "#{left_column} #{right_column}"
242
+ end
243
+
244
+ def putf_debug(name, value, fmt, optional: "")
245
+ putf(name, value, fmt, optional: optional, color: :yellow)
246
+ end
247
+
248
+ def compass_rose(degree)
249
+ case degree
250
+ when 0...45 then "N"
251
+ when 45...90 then "NE"
252
+ when 90...135 then "E"
253
+ when 135...180 then "SE"
254
+ when 180...225 then "S"
255
+ when 225...270 then "SW"
256
+ when 270...315 then "W"
257
+ else "NW"
258
+ end
235
259
  end
236
260
 
237
261
  def geolocalize(location)
@@ -12,7 +12,7 @@ module Forecaster
12
12
  # takes approximately 3 to 5 hours before a run is available online, so
13
13
  # to be on the safe side we return the previous one.
14
14
  now = Time.now.utc
15
- run = Time.new(now.year, now.month, now.day, (now.hour / 6) * 6)
15
+ run = Time.utc(now.year, now.month, now.day, (now.hour / 6) * 6)
16
16
 
17
17
  run - 6 * 3600
18
18
  end
@@ -20,8 +20,8 @@ module Forecaster
20
20
  def self.at(time)
21
21
  # There is a forecast every 3 hours after a run for 384 hours.
22
22
  t = time.utc
23
- fct = Time.new(t.year, t.month, t.day, (t.hour / 3) * 3)
24
- run = Time.new(t.year, t.month, t.day, (t.hour / 6) * 6)
23
+ fct = Time.utc(t.year, t.month, t.day, (t.hour / 3) * 3)
24
+ run = Time.utc(t.year, t.month, t.day, (t.hour / 6) * 6)
25
25
  run -= 6 * 3600 if run == fct
26
26
 
27
27
  last_run = Forecast.last_run_at
@@ -74,34 +74,48 @@ module Forecaster
74
74
  # the configuration will be downloaded.
75
75
  def fetch
76
76
  return if fetched?
77
- ranges = fetch_index
78
- fetch_grib2(ranges)
77
+
78
+ ranges = fetch_ranges
79
+
80
+ # Select which byte ranges to download
81
+ records = Forecaster.configuration.records.values
82
+ filtered_ranges = records.map { |k| ranges[k] }
83
+
84
+ fetch_grib2(filtered_ranges)
79
85
  end
80
86
 
81
- def fetch_index
87
+ # Fetch the index file of a GRIB2 file containing the data of a forecast.
88
+ #
89
+ # Returns a hashmap of every records in the file with their byte ranges.
90
+ #
91
+ # With this method we can avoid downloading unnecessary parts of the GRIB2
92
+ # file by matching the records defined in the configuration. It can also
93
+ # be used to set up the later.
94
+ def fetch_ranges
82
95
  begin
83
96
  res = Excon.get("#{url}.idx")
84
97
  rescue Excon::Errors::Error
85
98
  raise "Download of '#{url}.idx' failed"
86
99
  end
87
-
88
- lines = res.body.lines
89
- lines.each_index.reduce([]) do |r, i|
90
- records = Forecaster.configuration.records
91
- if records.values.any? { |record| lines[i].include?(record) }
92
- first = lines[i].split(":")[1].to_i
93
- last = ""
94
-
95
- j = i
96
- while (j += 1) < lines.count
97
- last = lines[j].split(":")[1].to_i - 1
98
- break if last != first - 1
99
- end
100
-
101
- r << "#{first}-#{last}" # Range header syntax
102
- else
103
- r
104
- end
100
+ lines = res.body.lines.map { |line| line.split(":") }
101
+ lines.each_index.each_with_object({}) do |i, ranges|
102
+ # A typical line (before the split on `:`) looks like this:
103
+ # `12:4593854:d=2016051118:TMP:2 mb:9 hour fcst:`
104
+ line = lines[i]
105
+ next_line = lines[i + 1] # NOTE: Will be `nil` on the last line
106
+
107
+ # The fourth and fifth fields constitue the key to identify the records
108
+ # defined in `Forecaster::Configuration`.
109
+ key = ":#{line[3]}:#{line[4]}:"
110
+
111
+ # The second field is the first byte of the record in the GRIB2 file.
112
+ ranges[key] = [line[1].to_i]
113
+
114
+ # To get the last byte we need to read the next line.
115
+ # If we are on the last line we won't be able to get the last byte,
116
+ # but we don't need it according to the section 14.35.1 Byte Ranges
117
+ # of RFC 2616.
118
+ ranges[key] << next_line[1].to_i - 1 if next_line
105
119
  end
106
120
  end
107
121
 
@@ -114,7 +128,9 @@ module Forecaster
114
128
  progress_block.call(total - remaining, total) if progress_block
115
129
  end
116
130
 
117
- headers = { "Range" => "bytes=#{ranges.join(',')}" }
131
+ byte_ranges = ranges.map { |r| r.join("-") }.join(",")
132
+ headers = { "Range" => "bytes=#{byte_ranges}" }
133
+
118
134
  begin
119
135
  Excon.get(url, :headers => headers, :response_block => streamer)
120
136
  rescue Excon::Errors::Error => e
@@ -1,4 +1,4 @@
1
- # Fetch and read data from the Global Forecast System.
1
+ # Read data from the Global Forecast System
2
2
  module Forecaster
3
- VERSION = "0.1.0".freeze
3
+ VERSION = "1.0.0".freeze
4
4
  end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forecaster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Ollivier
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-11 00:00:00.000000000 Z
11
+ date: 2018-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chronic
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.10'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.10.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.10'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.10.0
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: excon
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +50,86 @@ dependencies:
30
50
  - - ">="
31
51
  - !ruby/object:Gem::Version
32
52
  version: 0.49.0
53
+ - !ruby/object:Gem::Dependency
54
+ name: geocoder
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.3'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 1.3.0
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 1.3.0
73
+ - !ruby/object:Gem::Dependency
74
+ name: rainbow
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.0'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.0
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 3.0.0
93
+ - !ruby/object:Gem::Dependency
94
+ name: ruby-progressbar
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '1.8'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.8.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.8'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 1.8.0
113
+ - !ruby/object:Gem::Dependency
114
+ name: timezone
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '1.2'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 1.2.0
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '1.2'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 1.2.0
33
133
  - !ruby/object:Gem::Dependency
34
134
  name: trollop
35
135
  requirement: !ruby/object:Gem::Requirement
@@ -51,92 +151,96 @@ dependencies:
51
151
  - !ruby/object:Gem::Version
52
152
  version: 2.1.0
53
153
  - !ruby/object:Gem::Dependency
54
- name: chronic
154
+ name: codecov
55
155
  requirement: !ruby/object:Gem::Requirement
56
156
  requirements:
57
157
  - - "~>"
58
158
  - !ruby/object:Gem::Version
59
- version: '0.10'
159
+ version: '0.1'
60
160
  - - ">="
61
161
  - !ruby/object:Gem::Version
62
- version: 0.10.0
63
- type: :runtime
162
+ version: 0.1.10
163
+ type: :development
64
164
  prerelease: false
65
165
  version_requirements: !ruby/object:Gem::Requirement
66
166
  requirements:
67
167
  - - "~>"
68
168
  - !ruby/object:Gem::Version
69
- version: '0.10'
169
+ version: '0.1'
70
170
  - - ">="
71
171
  - !ruby/object:Gem::Version
72
- version: 0.10.0
172
+ version: 0.1.10
73
173
  - !ruby/object:Gem::Dependency
74
- name: timezone
174
+ name: rspec
75
175
  requirement: !ruby/object:Gem::Requirement
76
176
  requirements:
77
177
  - - "~>"
78
178
  - !ruby/object:Gem::Version
79
- version: '0.99'
179
+ version: '3.7'
80
180
  - - ">="
81
181
  - !ruby/object:Gem::Version
82
- version: 0.99.0
83
- type: :runtime
182
+ version: 3.7.0
183
+ type: :development
84
184
  prerelease: false
85
185
  version_requirements: !ruby/object:Gem::Requirement
86
186
  requirements:
87
187
  - - "~>"
88
188
  - !ruby/object:Gem::Version
89
- version: '0.99'
189
+ version: '3.7'
90
190
  - - ">="
91
191
  - !ruby/object:Gem::Version
92
- version: 0.99.0
192
+ version: 3.7.0
93
193
  - !ruby/object:Gem::Dependency
94
- name: geocoder
194
+ name: simplecov
95
195
  requirement: !ruby/object:Gem::Requirement
96
196
  requirements:
97
197
  - - "~>"
98
198
  - !ruby/object:Gem::Version
99
- version: '1.3'
199
+ version: '0.16'
100
200
  - - ">="
101
201
  - !ruby/object:Gem::Version
102
- version: 1.3.0
103
- type: :runtime
202
+ version: 0.16.1
203
+ type: :development
104
204
  prerelease: false
105
205
  version_requirements: !ruby/object:Gem::Requirement
106
206
  requirements:
107
207
  - - "~>"
108
208
  - !ruby/object:Gem::Version
109
- version: '1.3'
209
+ version: '0.16'
110
210
  - - ">="
111
211
  - !ruby/object:Gem::Version
112
- version: 1.3.0
212
+ version: 0.16.1
113
213
  - !ruby/object:Gem::Dependency
114
- name: ruby-progressbar
214
+ name: timecop
115
215
  requirement: !ruby/object:Gem::Requirement
116
216
  requirements:
117
217
  - - "~>"
118
218
  - !ruby/object:Gem::Version
119
- version: '1.8'
219
+ version: '0.9'
120
220
  - - ">="
121
221
  - !ruby/object:Gem::Version
122
- version: 1.8.0
123
- type: :runtime
222
+ version: 0.9.0
223
+ type: :development
124
224
  prerelease: false
125
225
  version_requirements: !ruby/object:Gem::Requirement
126
226
  requirements:
127
227
  - - "~>"
128
228
  - !ruby/object:Gem::Version
129
- version: '1.8'
229
+ version: '0.9'
130
230
  - - ">="
131
231
  - !ruby/object:Gem::Version
132
- version: 1.8.0
133
- description: Wrapper around curl and wgrib2 to fetch and read GFS data
232
+ version: 0.9.0
233
+ description: Wrapper around wgrib2 to read data directly from the Global Forecast
234
+ System
134
235
  email: v@vinc.cc
135
236
  executables:
136
237
  - forecast
137
238
  extensions: []
138
239
  extra_rdoc_files: []
139
240
  files:
241
+ - CHANGELOG.md
242
+ - LICENSE
243
+ - README.md
140
244
  - bin/forecast
141
245
  - lib/forecaster.rb
142
246
  - lib/forecaster/cli.rb
@@ -163,8 +267,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
163
267
  version: '0'
164
268
  requirements: []
165
269
  rubyforge_project:
166
- rubygems_version: 2.4.5.1
270
+ rubygems_version: 2.6.11
167
271
  signing_key:
168
272
  specification_version: 4
169
- summary: Wrapper around curl and wgrib2 to fetch and read GFS data
273
+ summary: Wrapper around wgrib2 to read data from the GFS
170
274
  test_files: []