stormglass 0.1.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 +7 -0
- data/.gitignore +8 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +40 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/stormglass.rb +103 -0
- data/lib/stormglass/alternate_values.rb +32 -0
- data/lib/stormglass/alternate_values/celsius.rb +13 -0
- data/lib/stormglass/alternate_values/degrees.rb +16 -0
- data/lib/stormglass/alternate_values/kgm2.rb +12 -0
- data/lib/stormglass/alternate_values/kilometers.rb +12 -0
- data/lib/stormglass/alternate_values/meters.rb +12 -0
- data/lib/stormglass/alternate_values/meters_sec.rb +17 -0
- data/lib/stormglass/configuration.rb +9 -0
- data/lib/stormglass/hour.rb +61 -0
- data/lib/stormglass/response.rb +52 -0
- data/lib/stormglass/result_dict.rb +29 -0
- data/lib/stormglass/string.rb +9 -0
- data/lib/stormglass/subvalue.rb +21 -0
- data/lib/stormglass/value.rb +78 -0
- data/lib/stormglass/version.rb +3 -0
- data/stormglass.gemspec +29 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 82b85c3b145480cb967b4138502dff4b3459c959
|
4
|
+
data.tar.gz: 263aa703403641202b4b7bbeec493012439af742
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '09229ef4fda64e6b36c9ee45d7483b1d41eee15b002c725a526bd7e5be99d61f3d3d508aa2e0d95660833c36c02805f0d9114490d9b90fda8de6c2b56af12651'
|
7
|
+
data.tar.gz: 7a0bbedde8f5df9ecabfcf2f8b2bac33adc00765edddeb50c2344e89acbc628868b262f656c755a8388747a670398079f71b1063a484f19116956f22319d31a3
|
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.4.2
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
Stormglass (0.1.0)
|
5
|
+
geocoder
|
6
|
+
rest-client
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
domain_name (0.5.20180417)
|
12
|
+
unf (>= 0.0.5, < 1.0.0)
|
13
|
+
geocoder (1.5.0)
|
14
|
+
http-cookie (1.0.3)
|
15
|
+
domain_name (~> 0.5)
|
16
|
+
mime-types (3.2.2)
|
17
|
+
mime-types-data (~> 3.2015)
|
18
|
+
mime-types-data (3.2018.0812)
|
19
|
+
minitest (5.10.3)
|
20
|
+
netrc (0.11.0)
|
21
|
+
rake (10.5.0)
|
22
|
+
rest-client (2.0.2)
|
23
|
+
http-cookie (>= 1.0.2, < 2.0)
|
24
|
+
mime-types (>= 1.16, < 4.0)
|
25
|
+
netrc (~> 0.8)
|
26
|
+
unf (0.1.4)
|
27
|
+
unf_ext
|
28
|
+
unf_ext (0.0.7.5)
|
29
|
+
|
30
|
+
PLATFORMS
|
31
|
+
ruby
|
32
|
+
|
33
|
+
DEPENDENCIES
|
34
|
+
Stormglass!
|
35
|
+
bundler (~> 1.16)
|
36
|
+
minitest (~> 5.3)
|
37
|
+
rake (~> 10.0)
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
1.16.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Russell Jennings
|
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,128 @@
|
|
1
|
+
# Stormglass
|
2
|
+
|
3
|
+
Ruby gem for access to the [Stormglass](https://stormglass.io/) Weather forecast API.
|
4
|
+
|
5
|
+
# Overview
|
6
|
+
|
7
|
+
This gem augments the Stormglass API responses to make consuming the API more accessible for ruby developers.
|
8
|
+
|
9
|
+
- Provides weather lookup for address and lat/lng
|
10
|
+
- Provides units and descriptions for all forecast types (from API docs)
|
11
|
+
- Specify start/end as DateTime
|
12
|
+
- specify `hours` to calcuate end offset from start (default 12)
|
13
|
+
- Creates alternative representations of values (such as Fahrenheit, or knots)
|
14
|
+
- Specify default units and sources via `Stormglass.configure` block, or override at method invocation
|
15
|
+
|
16
|
+
For more information, visit [Stormglass API docs](https://docs.stormglass.io/)
|
17
|
+
|
18
|
+
*Note: this gem currently only implements the `point` API endpoint. Accepting pull requests!*
|
19
|
+
|
20
|
+
# Installation
|
21
|
+
|
22
|
+
```
|
23
|
+
gem install stormglass
|
24
|
+
```
|
25
|
+
|
26
|
+
or in your gemfile
|
27
|
+
|
28
|
+
```
|
29
|
+
gem 'stormglass'
|
30
|
+
```
|
31
|
+
|
32
|
+
# API Key
|
33
|
+
|
34
|
+
To use Stormglass you'll need to provide an API key, which can be configured a number of ways:
|
35
|
+
|
36
|
+
1. Enviroment variable `STORMGLASS_API_KEY`
|
37
|
+
2. within a `Stormglass.configure` block (more on that below)
|
38
|
+
3. with Rails secret `stormglass_api_key`
|
39
|
+
4. at request invocation by passing `key`
|
40
|
+
|
41
|
+
# Configuration
|
42
|
+
|
43
|
+
You can configure the gem with the following block
|
44
|
+
```
|
45
|
+
Stormglass.configure do |config|
|
46
|
+
config.units = {air_temperature: 'F', gust: 'knot'}
|
47
|
+
config.api_key = 'abcd-abcd-abcd'
|
48
|
+
config.source = 'sg'
|
49
|
+
end
|
50
|
+
```
|
51
|
+
|
52
|
+
# Walkthrough
|
53
|
+
|
54
|
+
First, let's query the stormglass API for an address. This will look up via `Geocoder` and then call `Stormglass.for_lat_lng` for us with the first result.
|
55
|
+
```
|
56
|
+
response = Stormglass.for_address('123 broad street philadelphia pa', hours: 24)
|
57
|
+
=> #<Stormglass::Response remaining_requests=31,
|
58
|
+
hours=[#<Stormglass::Hour time='2018-12-12 21:00:00 +0000'> ...]>
|
59
|
+
```
|
60
|
+
The methods we can call are underscored variants of what the camelCaps API returns. Taken from `stormglass/hour.rb`
|
61
|
+
```
|
62
|
+
VALUES = [:air_temperature,:cloud_cover,:current_direction,:current_speed,:gust,:humidity,
|
63
|
+
:precipitation,:pressure,:sea_level,:swell_direction,:swell_height,:swell_period,
|
64
|
+
:visibility,:water_temperature,:wave_direction,:wave_height,:wave_period,
|
65
|
+
:wind_direction,:wind_speed,:wind_wave_direction,:wind_wave_height,:wind_wave_period]
|
66
|
+
```
|
67
|
+
Let's see the first hour's air_temperature.
|
68
|
+
```
|
69
|
+
response.hours.first.air_temperature
|
70
|
+
=> #<Stormglass::Value value=44.91, unit='C', description='Air temperature as degrees Celsius',
|
71
|
+
unit_type='C', unit_types=["C", "F"], data_source='sg',
|
72
|
+
data_sources=["sg", "dwd", "noaa", "wrf"]>
|
73
|
+
```
|
74
|
+
we can also call `response.first.air_temperature` or even `response.air_temperature` and it will defer to the first hour result.
|
75
|
+
|
76
|
+
But what if we don't want the first hour? In addition to calling `response.hours.select{...}`, you can use `#find` with your local timezone and it will convert to UTC for lookup
|
77
|
+
```
|
78
|
+
response.find('7AM EST').air_temperature
|
79
|
+
=> #<Stormglass::Value value=44.91, unit='C', description='Air temperature as degrees Celsius',
|
80
|
+
unit_type='C', unit_types=["C", "F"], data_source='sg', data_sources=["sg", "dwd", "noaa", "wrf"]>
|
81
|
+
```
|
82
|
+
we can work with the `response.find('7AM EST').air_temperature.value` directly (a Float), or call `to_s` for a pretty string
|
83
|
+
|
84
|
+
```
|
85
|
+
response.air_temperature.to_s
|
86
|
+
=> "4.91 C"
|
87
|
+
```
|
88
|
+
|
89
|
+
What if we want air temperature in Fahrenheit instead? No need to whip out the calculator, we've
|
90
|
+
got you covered!
|
91
|
+
|
92
|
+
```
|
93
|
+
response.air_temperature(unit_type: 'F')
|
94
|
+
=> #<Stormglass::Value value=44.91, unit='F', description='Air temperature as degrees Fahrenheit',
|
95
|
+
unit_type='F', unit_types=["C", "F"], data_source='sg', data_sources=["sg", "dwd", "noaa", "wrf"]>
|
96
|
+
```
|
97
|
+
|
98
|
+
Or we can reference a different data source than the default
|
99
|
+
```
|
100
|
+
response.air_temperature(unit_type: 'F', data_source: 'noaa')
|
101
|
+
=> #<Stormglass::Value value=45.33, unit='F', description='Air temperature as degrees Fahrenheit',
|
102
|
+
unit_type='F', unit_types=["C", "F"], data_source='noaa', data_sources=["sg", "dwd", "noaa", "wrf"]>
|
103
|
+
```
|
104
|
+
|
105
|
+
# Structure
|
106
|
+
|
107
|
+
There are a few primary classes involved:
|
108
|
+
1. `Stormglass::Response` represents the result from stormglass, wrapping `hours` and `meta` responses.
|
109
|
+
Calling something like `#air_temperature` on this delegates to `#hours.first`
|
110
|
+
2. `Stormglass::Hour` represents an hour portion of the API response
|
111
|
+
3. `Stormglass::Value` represents a particular forecast value. along with the numeric `value` it also exposes
|
112
|
+
the `description` and `unit` used. It is just a wrapper to the collection of `Stormglass::Subvalue`'s and defers to the preferred/specified one.
|
113
|
+
4. `Stormglass::Subvalue` represents a particular variant of forecast value, such as `air_temperature` in Celsius or in Fahrenheit.
|
114
|
+
|
115
|
+
For any of the above instances, you can call `#src` to access the raw (usually JSON) data that was passed in to populate it.
|
116
|
+
|
117
|
+
Because each forecast type has different sources and units, `Stormglass::Value` instances exposes the progmatic
|
118
|
+
available options `data_sources` and `unit_types` you can reference for alternatives to provide.
|
119
|
+
|
120
|
+
```
|
121
|
+
response.air_temperature(unit_type: 'C')
|
122
|
+
=> #<Stormglass::Value value=7.17, unit='C', description='Air temperature as degrees Celsius',
|
123
|
+
unit_type='C', unit_types=["C", "F"], data_source='sg', data_sources=["sg", "dwd", "noaa", "wrf"]>
|
124
|
+
|
125
|
+
response.air_temperature(unit_type: 'C', data_source: 'wrf')
|
126
|
+
=> #<Stormglass::Value value=8.34, unit='C', description='Air temperature as degrees Celsius',
|
127
|
+
unit_type='C', unit_types=["C", "F"], data_source='noaa', data_sources=["sg", "dwd", "noaa", "wrf"]>
|
128
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "stormglass"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/stormglass.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require "stormglass/version"
|
2
|
+
require 'restclient'
|
3
|
+
require 'date'
|
4
|
+
require 'json'
|
5
|
+
require 'forwardable'
|
6
|
+
require 'geocoder'
|
7
|
+
require 'stormglass/configuration'
|
8
|
+
require 'stormglass/hour'
|
9
|
+
require 'stormglass/response'
|
10
|
+
require 'stormglass/string'
|
11
|
+
require 'stormglass/result_dict'
|
12
|
+
require 'stormglass/value'
|
13
|
+
require 'stormglass/subvalue'
|
14
|
+
require 'stormglass/alternate_values'
|
15
|
+
|
16
|
+
Dir[File.join(__dir__, 'stormglass', 'alternate_values', '*.rb')].each { |file| require file }
|
17
|
+
|
18
|
+
|
19
|
+
module Stormglass
|
20
|
+
class Error < StandardError; end
|
21
|
+
class ConnectionError < StandardError; end
|
22
|
+
class ExceededLimitError < StandardError; end
|
23
|
+
|
24
|
+
|
25
|
+
class << self
|
26
|
+
attr_accessor :settings
|
27
|
+
|
28
|
+
def configure
|
29
|
+
self.settings ||= Configuration.new
|
30
|
+
yield(settings)
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_settings
|
34
|
+
self.settings = Configuration.new
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# lookup an address (such as city + zip) and get the coordinates for the first match
|
39
|
+
def self.for_address(address_string, params={})
|
40
|
+
if results = Geocoder.search(address_string)
|
41
|
+
lat,lng = results.first.coordinates
|
42
|
+
self.for_lat_lng(lat: lat, lng: lng, params: params)
|
43
|
+
else
|
44
|
+
raise Error, 'Could not find address'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# query StormGlass given lat/lng.
|
49
|
+
# params:
|
50
|
+
# :lat - dateTime (default Now)
|
51
|
+
# :lng - Datetime (default 12 hours from :start)
|
52
|
+
# :params - additional params available to self.reqest
|
53
|
+
def self.for_lat_lng(lat:, lng:, params: {})
|
54
|
+
self.request(params: {lat: lat, lng: lng}.merge(params))
|
55
|
+
end
|
56
|
+
|
57
|
+
# Primary interface to StormGlass.
|
58
|
+
# params:
|
59
|
+
# :start - dateTime (default Now)
|
60
|
+
# :end - Datetime (default 12 hours from :start)
|
61
|
+
# :hours - number of hours to determine end (default 12)
|
62
|
+
# :key - API key (default to api_key method )
|
63
|
+
def self.request(endpoint: 'point', params: {})
|
64
|
+
hours = (params.delete(:hours) || 11) - 1
|
65
|
+
params[:key] ||= api_key
|
66
|
+
params[:start] ||= DateTime.now
|
67
|
+
params[:end] ||= hours_offset(params[:start], hours)
|
68
|
+
params[:start] = query_time_string(params[:start])
|
69
|
+
params[:end] = query_time_string(params[:end])
|
70
|
+
key = params.delete(:key)
|
71
|
+
begin
|
72
|
+
body = RestClient.get("https://api.stormglass.io/#{endpoint}", {params: params, 'Authorization' => key}).body
|
73
|
+
rescue SocketError => msg
|
74
|
+
puts msg
|
75
|
+
raise ConnectionError, 'error connecting to stormglass'
|
76
|
+
rescue RestClient::PaymentRequired => msg
|
77
|
+
puts msg
|
78
|
+
raise ExceededLimitError, 'exceeded limit. payment required for additional daily requests'
|
79
|
+
end
|
80
|
+
Stormglass::Response.new(body)
|
81
|
+
end
|
82
|
+
|
83
|
+
# API key for stormglass. key is sourced:
|
84
|
+
# - if set via enviroment variable
|
85
|
+
# - if set in configuration block
|
86
|
+
# - if defined in Rails secrets
|
87
|
+
# - when passed directly to parameters
|
88
|
+
def self.api_key
|
89
|
+
key = Stormglass.settings.api_key
|
90
|
+
key ||= Rails.application.secrets[:stormglass_api_key] if Gem.loaded_specs.has_key?('rails')
|
91
|
+
key
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.hours_offset(start_time, hours=12)
|
95
|
+
(start_time + (Rational(1,24) * hours))
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.query_time_string(datetime)
|
99
|
+
datetime.new_offset(0).iso8601
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
Stormglass.set_settings
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Stormglass::AlternateValues
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
@original = subvalues.first
|
5
|
+
case subvalues.first[:unit_type]
|
6
|
+
when "Ms" then self::MetersSec.perform(subvalues)
|
7
|
+
when "M" then self::Meters.perform(subvalues)
|
8
|
+
when "C" then self::Celsius.perform(subvalues)
|
9
|
+
when "Km" then self::Kilometers.perform(subvalues)
|
10
|
+
when 'Kgm2' then self::Kgm2.perform(subvalues)
|
11
|
+
when 'Deg' then self::Degrees.perform(subvalues)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.meters_sec(subvalues)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.meters(subvalues)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.c(subvalues)
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.km(subvalues)
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Stormglass::AlternateValues::Celsius
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
inject_fahrenheit(subvalues)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.inject_fahrenheit(subvalues)
|
10
|
+
subvalues << {unit_type: 'F', value: (subvalues.first[:value] * 1.8 + 32).round(2), unit: 'F', description: subvalues.first[:description], unit_description: 'degrees Fahrenheit'}
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Stormglass::AlternateValues::Degrees
|
2
|
+
|
3
|
+
POINTS = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
|
4
|
+
|
5
|
+
def self.perform(subvalues)
|
6
|
+
inject_compass_points(subvalues)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def self.inject_compass_points(subvalues)
|
12
|
+
compass_point = Geocoder::Calculations.compass_point(subvalues.first[:value], POINTS)
|
13
|
+
subvalues << {unit_type: 'compass', value: compass_point, unit: '', description: subvalues.first[:description], unit_description: 'From 16 shorthand compass points'}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Stormglass::AlternateValues::Kgm2
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
inject_inches(subvalues)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.inject_inches(subvalues)
|
10
|
+
subvalues << {unit_type: 'inches', unit: 'in', value: (subvalues.first[:value] * 0.039370).round(2), description: subvalues.first[:description], unit_description: 'inches'}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Stormglass::AlternateValues::Kilometers
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
inject_miles(subvalues)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.inject_miles(subvalues)
|
10
|
+
subvalues << {unit_type: 'miles', unit: 'mi', value: (subvalues.first[:value] / 1.609344).round(2), description: subvalues.first[:description], unit_description: 'miles'}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Stormglass::AlternateValues::Meters
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
inject_feet(subvalues)
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.inject_feet(subvalues)
|
10
|
+
subvalues << {unit_type: 'feet', unit: 'ft', value: (subvalues.first[:value] / 0.3048).round(2), description: subvalues.first[:description], unit_description: 'feet'}
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Stormglass::AlternateValues::MetersSec
|
2
|
+
|
3
|
+
def self.perform(subvalues)
|
4
|
+
inject_knots(subvalues)
|
5
|
+
inject_mph(subvalues)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.inject_knots(subvalues)
|
11
|
+
subvalues << {unit_type: 'knot', unit: 'Kn', value: (subvalues.first[:value] * 1.9438445).round(2), description: subvalues.first[:description], unit_description: 'nautical knots'}
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.inject_mph(subvalues)
|
15
|
+
subvalues << {unit_type: 'MPH', unit: 'MPH', value: (subvalues.first[:value] * 2.236936).round(2), description: subvalues.first[:description], unit_description: 'miles per hour'}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# represents an hour result from Stormglass
|
2
|
+
# VALUES represent callable methods
|
3
|
+
class Stormglass::Hour
|
4
|
+
|
5
|
+
VALUES = [:air_temperature,:cloud_cover,:current_direction,:current_speed,:gust,:humidity,
|
6
|
+
:precipitation,:pressure,:sea_level,:swell_direction,:swell_height,:swell_period,
|
7
|
+
:visibility,:water_temperature,:wave_direction,:wave_height,:wave_period,
|
8
|
+
:wind_direction,:wind_speed,:wind_wave_direction,:wind_wave_height,:wind_wave_period]
|
9
|
+
|
10
|
+
def initialize(src)
|
11
|
+
@src = src
|
12
|
+
end
|
13
|
+
|
14
|
+
def src
|
15
|
+
@src
|
16
|
+
end
|
17
|
+
|
18
|
+
def time
|
19
|
+
Time.parse(src["time"])
|
20
|
+
end
|
21
|
+
|
22
|
+
def values
|
23
|
+
src.keys.collect(&:underscore)
|
24
|
+
end
|
25
|
+
|
26
|
+
def method_missing(method, *args)
|
27
|
+
if VALUES.include?(method)
|
28
|
+
get_value(method, args)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def respond_to_missing?(method_name, include_private = false)
|
35
|
+
VALUES.include?(method_name) || super
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
"#<#{self.class.to_s} time='#{time}'> "
|
40
|
+
end
|
41
|
+
|
42
|
+
# handler for each VALUES method (such as air_temperature)
|
43
|
+
# takes two optional arguments:
|
44
|
+
# data_source: - data source to use. (default 'sg')
|
45
|
+
# unit_type: - preferred unit type (default API result)
|
46
|
+
def get_value(attribute,args)
|
47
|
+
vals = fetch_value(args.first ? {attribute: attribute}.merge(args.first) : {attribute: attribute})
|
48
|
+
@src.keys.collect(&:underscore).zip(vals).to_h[attribute.to_s]
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch_value(data_source: nil, attribute:, unit_type: nil)
|
52
|
+
data_source ||= Stormglass.settings.source
|
53
|
+
@src.values.collect do |val|
|
54
|
+
if val.is_a?(String)
|
55
|
+
val
|
56
|
+
else
|
57
|
+
Stormglass::Value.new(attribute, val, data_source, unit_type)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Responses from Stormglass API are wrapped in this class
|
2
|
+
class Stormglass::Response
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
# convenience delegate calls (like air_temperature)
|
6
|
+
# to the first hour returned
|
7
|
+
def_delegators :first, *Stormglass::Hour::VALUES
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(src)
|
11
|
+
@src = JSON.parse(src)
|
12
|
+
end
|
13
|
+
|
14
|
+
# an array of Stormglass::Hour instances
|
15
|
+
def hours
|
16
|
+
@hours ||= []
|
17
|
+
return @hours if !@hours.empty?
|
18
|
+
src['hours'].each{|h| @hours << Stormglass::Hour.new(h) }
|
19
|
+
@hours
|
20
|
+
end
|
21
|
+
|
22
|
+
def src
|
23
|
+
@src
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
string = '#<' + "#{self.class.to_s} remaining_requests=#{remaining_requests}, "
|
28
|
+
string +="hours=#{hours.to_s}> "
|
29
|
+
string
|
30
|
+
end
|
31
|
+
|
32
|
+
def first
|
33
|
+
hours.first
|
34
|
+
end
|
35
|
+
|
36
|
+
def last
|
37
|
+
hours.last
|
38
|
+
end
|
39
|
+
|
40
|
+
# takes a string like "7PM EST" and returns the relevant hour if found
|
41
|
+
def find(string)
|
42
|
+
hours.find{|h| h.time == Time.parse(string)}
|
43
|
+
end
|
44
|
+
|
45
|
+
def meta
|
46
|
+
src['meta']
|
47
|
+
end
|
48
|
+
|
49
|
+
def remaining_requests
|
50
|
+
meta['dailyQuota'] - meta['requestCount']
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Stormglass
|
2
|
+
|
3
|
+
RESULT_DICT= {
|
4
|
+
:air_temperature =>{:unit_type => "C", :unit=>"C", :description=>"Air temperature as", :unit_description =>"degrees Celsius"},
|
5
|
+
:air_pressure =>{:unit_type => "hPa", :unit=>"hPa", :description=>"Air pressure as", :unit_description =>"hectopascal"},
|
6
|
+
:cloud_cover =>{:unit_type => "%", :unit=>"%", :description=>"Total cloud coverage as", :unit_description =>"percent"},
|
7
|
+
:current_direction =>{:unit_type => "Deg", :unit=>"°", :description=>"Direction of current", :unit_description =>"0° indicates coming from north"},
|
8
|
+
:current_speed =>{:unit_type => "Ms", :unit=>"M/s", :description=>"Speed of current as", :unit_description =>"meters per second"},
|
9
|
+
:gust =>{:unit_type => "Ms", :unit=>"M/s", :description=>"Wind gust as", :unit_description =>"meters per second"},
|
10
|
+
:humidity =>{:unit_type => "%", :unit=>"%", :description=>"Relative humidity as", :unit_description => "percent"},
|
11
|
+
:ice_cover =>{:unit_type => "/1", :unit=>"/1", :description=>"Proportion", :unit_description=> "over 1"},
|
12
|
+
:precipitation =>{:unit_type => "Kgm2", :unit=>"kg/m²", :description=>"Mean precipitation as", :unit_description => "kilogram per square meter"},
|
13
|
+
:sea_level =>{:unit_type => "M", :unit=>"M", :description=>"Height of sea level as", :unit_description =>"meters"},
|
14
|
+
:snow_depth =>{:unit_type => "M", :unit=>"M", :description=>"Depth of snow as", :unit_description =>"meters"},
|
15
|
+
:swell_direction =>{:unit_type => "Deg", :unit=>"°", :description=>"Direction of swell waves.", :unit_description => "0° indicates coming from north"},
|
16
|
+
:swell_height =>{:unit_type => "M", :unit=>"M", :description=>"Height of swell waves as", :unit_description =>"meters"},
|
17
|
+
:swell_period =>{:unit_type => "Sec", :unit=>"s", :description=>"Period of swell waves as", :unit_description =>"seconds"},
|
18
|
+
:visibility =>{:unit_type => "Km", :unit=>"km", :description=>"Horizontal visibility as", :unit_description =>"Kilometer"},
|
19
|
+
:water_temperature =>{:unit_type => "C", :unit=>"C", :description=>"Water temperature as", :unit_description =>"degrees Celsius"},
|
20
|
+
:wave_direction =>{:unit_type => "Deg", :unit=>"°", :description=>"Direction of combined wind and swell waves.",:unit_description => "0° indicates coming from north"},
|
21
|
+
:wave_height =>{:unit_type => "M", :unit=>"M", :description=>"Height of combined wind and swell waves as", :unit_description =>"meters"},
|
22
|
+
:wave_period =>{:unit_type => "Sec", :unit=>"s", :description=>"Period of combined wind and swell waves as", :unit_description =>"seconds"},
|
23
|
+
:wind_wave_direction=>{:unit_type => "Deg", :unit=>"°", :description=>"Direction of wind waves.", :unit_description => "0° indicates coming from north"},
|
24
|
+
:wind_wave_height =>{:unit_type => "M", :unit=>"M", :description=>"Height of wind waves as", :unit_description =>"meters"},
|
25
|
+
:wind_wave_period =>{:unit_type => "Sec", :unit=>"s", :description=>"Period of wind waves as", :unit_description =>"seconds"},
|
26
|
+
:wind_direction =>{:unit_type => "Deg", :unit=>"°", :description=>"Direction of wind.", :unit_description => "0° indicates coming from north"},
|
27
|
+
:wind_speed =>{:unit_type => "Ms", :unit=>"M/s", :description=>"Speed of wind as", :unit_description =>"meters per second"}
|
28
|
+
}
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Stormglass::Subvalue < OpenStruct
|
2
|
+
|
3
|
+
def initialize(src)
|
4
|
+
@src = src
|
5
|
+
super
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_s
|
9
|
+
"#{@src[:value]} #{@src[:unit]}"
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def description
|
14
|
+
"#{@src[:description]} #{@src[:unit_description]}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def src
|
18
|
+
@src
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# represents a value for a stormglass prediction source with prefered unit_type
|
2
|
+
# contains a collection of Stormglass::Subvalue's, which represent a particular unit of measurement
|
3
|
+
# for example, a Stormglass::Value for air_temperature would have Stormglass::Subvalue's for Celsius and Fahrenheit
|
4
|
+
class Stormglass::Value
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegators :preffered_subvalue, :unit, :unit_type, :description, :unit_description
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(attribute, src, data_source, unit_type)
|
11
|
+
@attribute = attribute
|
12
|
+
@src = src
|
13
|
+
@data_source = data_source
|
14
|
+
@unit_type = unit_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def data_source
|
18
|
+
@data_source
|
19
|
+
end
|
20
|
+
|
21
|
+
def for_source
|
22
|
+
@src.collect do |v|
|
23
|
+
v['value'] if (!v['source'].nil? && v['source'] == data_source)
|
24
|
+
end.compact.first
|
25
|
+
end
|
26
|
+
|
27
|
+
# returns the sources available for this value
|
28
|
+
def data_sources
|
29
|
+
src.collect{|v| v['source']}
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
preffered_subvalue.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def attribute
|
37
|
+
@attribute
|
38
|
+
end
|
39
|
+
|
40
|
+
def src
|
41
|
+
@src
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
string = "#<#{self.class.to_s} "
|
46
|
+
string +="value=#{preffered_subvalue.value}, unit='#{preffered_subvalue.unit}', description='#{preffered_subvalue.description}', "
|
47
|
+
string +="unit_type='#{preffered_subvalue.unit_type}', unit_types=#{unit_types.to_s}, data_source='#{@data_source}', data_sources=#{data_sources}>"
|
48
|
+
string
|
49
|
+
end
|
50
|
+
|
51
|
+
def unit_types
|
52
|
+
subvalues.collect(&:unit_type)
|
53
|
+
end
|
54
|
+
|
55
|
+
def preffered_subvalue
|
56
|
+
if @unit_type
|
57
|
+
subvalues.find{|subvalue| subvalue.unit_type == @unit_type}
|
58
|
+
elsif setting_key = Stormglass.settings.units[@attribute]
|
59
|
+
subvalues.find{|subvalue| subvalue.unit_type == setting_key}
|
60
|
+
else
|
61
|
+
subvalues.first
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def dict
|
66
|
+
Stormglass::RESULT_DICT[attribute]
|
67
|
+
end
|
68
|
+
|
69
|
+
def subvalues
|
70
|
+
subvals = []
|
71
|
+
raw_val = for_source()
|
72
|
+
subvals << {value: raw_val}.merge(dict)
|
73
|
+
Stormglass::AlternateValues.perform(subvals)
|
74
|
+
subvals.collect{|subvalue| Stormglass::Subvalue.new(subvalue) }
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
end
|
data/stormglass.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "stormglass/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "stormglass"
|
8
|
+
spec.version = Stormglass::VERSION
|
9
|
+
spec.authors = ["Russell Jennings"]
|
10
|
+
spec.email = ["violentpurr@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Ruby client for the Stormglass weather API}
|
13
|
+
spec.homepage = "https://github.com/meesterdude/stormglass"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
|
24
|
+
spec.add_dependency("rest-client")
|
25
|
+
spec.add_dependency("geocoder")
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "minitest", "~> 5.3"
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stormglass
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Russell Jennings
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: geocoder
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.16'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.16'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.3'
|
83
|
+
description:
|
84
|
+
email:
|
85
|
+
- violentpurr@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".ruby-version"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.lock
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/setup
|
100
|
+
- lib/stormglass.rb
|
101
|
+
- lib/stormglass/alternate_values.rb
|
102
|
+
- lib/stormglass/alternate_values/celsius.rb
|
103
|
+
- lib/stormglass/alternate_values/degrees.rb
|
104
|
+
- lib/stormglass/alternate_values/kgm2.rb
|
105
|
+
- lib/stormglass/alternate_values/kilometers.rb
|
106
|
+
- lib/stormglass/alternate_values/meters.rb
|
107
|
+
- lib/stormglass/alternate_values/meters_sec.rb
|
108
|
+
- lib/stormglass/configuration.rb
|
109
|
+
- lib/stormglass/hour.rb
|
110
|
+
- lib/stormglass/response.rb
|
111
|
+
- lib/stormglass/result_dict.rb
|
112
|
+
- lib/stormglass/string.rb
|
113
|
+
- lib/stormglass/subvalue.rb
|
114
|
+
- lib/stormglass/value.rb
|
115
|
+
- lib/stormglass/version.rb
|
116
|
+
- stormglass.gemspec
|
117
|
+
homepage: https://github.com/meesterdude/stormglass
|
118
|
+
licenses:
|
119
|
+
- MIT
|
120
|
+
metadata: {}
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
require_paths:
|
124
|
+
- lib
|
125
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 2.6.13
|
138
|
+
signing_key:
|
139
|
+
specification_version: 4
|
140
|
+
summary: Ruby client for the Stormglass weather API
|
141
|
+
test_files: []
|