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 +4 -4
- data/CHANGELOG.md +25 -0
- data/LICENSE +21 -0
- data/README.md +170 -0
- data/lib/forecaster/cli.rb +58 -34
- data/lib/forecaster/forecast.rb +41 -25
- data/lib/forecaster/version.rb +2 -2
- metadata +133 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95c34dc2a848deaba80e411a01e246237ed1f10a
|
4
|
+
data.tar.gz: 5d7c4597a21d4c9b703495c654406cd7e8ff12f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
+
[](https://rubygems.org/gems/forecaster)
|
5
|
+
[](http://travis-ci.org/vinc/forecaster)
|
6
|
+
[](https://codeclimate.com/github/vinc/forecaster)
|
7
|
+
[](https://codecov.io/gh/vinc/forecaster)
|
8
|
+
[](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
|
+
[](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.
|
data/lib/forecaster/cli.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
108
|
+
putf_debug("Location", lat, "%05.2f, %05.2f", optional: lon)
|
110
109
|
else
|
111
|
-
puts "
|
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
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
165
|
+
putf_debug("Downloading", forecast.url, "'%s'")
|
167
166
|
|
168
|
-
|
169
|
-
|
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,
|
172
|
-
first
|
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
|
-
|
176
|
-
|
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
|
-
|
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
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
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)
|
data/lib/forecaster/forecast.rb
CHANGED
@@ -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.
|
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.
|
24
|
-
run = Time.
|
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
|
-
|
78
|
-
|
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
|
-
|
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
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
-
|
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
|
data/lib/forecaster/version.rb
CHANGED
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:
|
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:
|
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:
|
154
|
+
name: codecov
|
55
155
|
requirement: !ruby/object:Gem::Requirement
|
56
156
|
requirements:
|
57
157
|
- - "~>"
|
58
158
|
- !ruby/object:Gem::Version
|
59
|
-
version: '0.
|
159
|
+
version: '0.1'
|
60
160
|
- - ">="
|
61
161
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.10
|
63
|
-
type: :
|
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.
|
169
|
+
version: '0.1'
|
70
170
|
- - ">="
|
71
171
|
- !ruby/object:Gem::Version
|
72
|
-
version: 0.10
|
172
|
+
version: 0.1.10
|
73
173
|
- !ruby/object:Gem::Dependency
|
74
|
-
name:
|
174
|
+
name: rspec
|
75
175
|
requirement: !ruby/object:Gem::Requirement
|
76
176
|
requirements:
|
77
177
|
- - "~>"
|
78
178
|
- !ruby/object:Gem::Version
|
79
|
-
version: '
|
179
|
+
version: '3.7'
|
80
180
|
- - ">="
|
81
181
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
83
|
-
type: :
|
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: '
|
189
|
+
version: '3.7'
|
90
190
|
- - ">="
|
91
191
|
- !ruby/object:Gem::Version
|
92
|
-
version:
|
192
|
+
version: 3.7.0
|
93
193
|
- !ruby/object:Gem::Dependency
|
94
|
-
name:
|
194
|
+
name: simplecov
|
95
195
|
requirement: !ruby/object:Gem::Requirement
|
96
196
|
requirements:
|
97
197
|
- - "~>"
|
98
198
|
- !ruby/object:Gem::Version
|
99
|
-
version: '
|
199
|
+
version: '0.16'
|
100
200
|
- - ">="
|
101
201
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
103
|
-
type: :
|
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: '
|
209
|
+
version: '0.16'
|
110
210
|
- - ">="
|
111
211
|
- !ruby/object:Gem::Version
|
112
|
-
version:
|
212
|
+
version: 0.16.1
|
113
213
|
- !ruby/object:Gem::Dependency
|
114
|
-
name:
|
214
|
+
name: timecop
|
115
215
|
requirement: !ruby/object:Gem::Requirement
|
116
216
|
requirements:
|
117
217
|
- - "~>"
|
118
218
|
- !ruby/object:Gem::Version
|
119
|
-
version: '
|
219
|
+
version: '0.9'
|
120
220
|
- - ">="
|
121
221
|
- !ruby/object:Gem::Version
|
122
|
-
version:
|
123
|
-
type: :
|
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: '
|
229
|
+
version: '0.9'
|
130
230
|
- - ">="
|
131
231
|
- !ruby/object:Gem::Version
|
132
|
-
version:
|
133
|
-
description: Wrapper around
|
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.
|
270
|
+
rubygems_version: 2.6.11
|
167
271
|
signing_key:
|
168
272
|
specification_version: 4
|
169
|
-
summary: Wrapper around
|
273
|
+
summary: Wrapper around wgrib2 to read data from the GFS
|
170
274
|
test_files: []
|