forecaster 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![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.
|
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: []
|