meteorologist 0.0.5 → 0.0.6
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/lib/meteorologist/forecaster.rb +90 -0
- data/lib/meteorologist/location_cache.rb +55 -0
- data/lib/meteorologist/locator.rb +48 -0
- data/lib/meteorologist/moon_info.rb +109 -0
- data/lib/meteorologist/moon_sign_calculator.rb +111 -0
- data/lib/meteorologist/navigator.rb +39 -0
- data/meteorologist.gemspec +13 -0
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f10ca63f0c1a1097591ec35acb4112cd6a96083
|
4
|
+
data.tar.gz: dcf4f7bb458f33c83e6274cab27d997b53f5c01e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c4973781dc9a5ebd2a0ae810aba2b6370abf1972f8def22a69fd031ce785d3c68b7ec3019530fc0249faf05fd12bfb6a16c09074bdf0f52be16a6107c97a9dd
|
7
|
+
data.tar.gz: 41ff06fa603e494c4026c7ee4c1ca948ee6f40faaed887ce94f2dca7e093f0345a1b4c257f4af82944e857291c4954ecaab4d3253bf8e330267899f3c2f3828f
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Forecaster
|
2
|
+
require 'json'
|
3
|
+
DARKSKY_URL = "https://api.darksky.net/forecast"
|
4
|
+
|
5
|
+
def initialize(coordinates, units, forecast_time, options = {})
|
6
|
+
data = options.fetch(:data) { get_data(coordinates, units, forecast_time) }
|
7
|
+
@forecast = validate_and_parse(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def current_summary
|
11
|
+
current('summary')
|
12
|
+
end
|
13
|
+
|
14
|
+
def current_temperature
|
15
|
+
current('temperature')
|
16
|
+
end
|
17
|
+
|
18
|
+
def current_apparent_temperature
|
19
|
+
current('apparentTemperature')
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_humidity
|
23
|
+
"#{(current('humidity') * 100).to_i}%"
|
24
|
+
end
|
25
|
+
|
26
|
+
def todays_summary
|
27
|
+
todays('summary')
|
28
|
+
end
|
29
|
+
|
30
|
+
def sunrise
|
31
|
+
get_time(todays('sunriseTime'))
|
32
|
+
end
|
33
|
+
|
34
|
+
def sunset
|
35
|
+
get_time(todays('sunsetTime'))
|
36
|
+
end
|
37
|
+
|
38
|
+
def moon_phase
|
39
|
+
todays('moonPhase')
|
40
|
+
end
|
41
|
+
|
42
|
+
def minimum_temperature
|
43
|
+
todays('temperatureMin')
|
44
|
+
end
|
45
|
+
|
46
|
+
def apparent_minimum_temperature
|
47
|
+
todays('apparentTemperatureMin')
|
48
|
+
end
|
49
|
+
|
50
|
+
def maximum_temperature
|
51
|
+
todays('temperatureMax')
|
52
|
+
end
|
53
|
+
|
54
|
+
def apparent_maximum_temperature
|
55
|
+
todays('apparentTemperatureMax')
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
attr_reader :forecast
|
60
|
+
|
61
|
+
def get_data(coordinates, units, forecast_time)
|
62
|
+
time = forecast_time.to_i
|
63
|
+
url = "#{DARKSKY_URL}/#{ENV['DARKSKYSECRET']}/#{coordinates},#{time}?units=#{units}"
|
64
|
+
|
65
|
+
`curl -s #{url}`
|
66
|
+
end
|
67
|
+
|
68
|
+
def validate_and_parse(forecast)
|
69
|
+
response = JSON.parse(forecast)
|
70
|
+
|
71
|
+
raise ArgumentError, "Location not found" if response['error']
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
def current(key)
|
76
|
+
forecast['currently'][key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def todays(key)
|
80
|
+
forecast['daily']['data'][0][key]
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_time(integer)
|
84
|
+
(Time.at(integer) + offset).strftime('%H:%M')
|
85
|
+
end
|
86
|
+
|
87
|
+
def offset
|
88
|
+
@offset ||= forecast['offset'].to_i * 3600
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class LocationCache
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
def self.write(location, attributes)
|
5
|
+
new(location).write(attributes)
|
6
|
+
end
|
7
|
+
|
8
|
+
def initialize(location, options = {})
|
9
|
+
@location = location.downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def exists?
|
13
|
+
!cache[location].nil?
|
14
|
+
end
|
15
|
+
|
16
|
+
def coordinates
|
17
|
+
cache[location][:coordinates] if exists?
|
18
|
+
end
|
19
|
+
|
20
|
+
def name
|
21
|
+
cache[location][:name] if exists?
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(attributes)
|
25
|
+
validate_required_attributes(attributes)
|
26
|
+
|
27
|
+
cache[location] = attributes
|
28
|
+
File.open(cache_full_path, 'w+') { |f| f.write(cache.to_yaml) }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
attr_reader :location
|
33
|
+
|
34
|
+
def cache
|
35
|
+
return @cache if defined? @cache
|
36
|
+
@cache = YAML.load(cache_contents) || Hash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def cache_contents
|
40
|
+
File.read(cache_full_path)
|
41
|
+
end
|
42
|
+
|
43
|
+
def cache_full_path
|
44
|
+
File.expand_path(ENV['CACHE_PATH'], __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_required_attributes(attributes)
|
48
|
+
invalid_attributes unless attributes[:coordinates]
|
49
|
+
invalid_attributes unless attributes[:name]
|
50
|
+
end
|
51
|
+
|
52
|
+
def invalid_attributes
|
53
|
+
raise ArgumentError, 'You must include location name and coordinates'
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Locator
|
2
|
+
require 'json'
|
3
|
+
GEOCODE_URL = 'https://maps.googleapis.com/maps/api/geocode'
|
4
|
+
|
5
|
+
def initialize(location, options = {})
|
6
|
+
data = options.fetch(:data) { get_data_from_api(location) }
|
7
|
+
@location = location
|
8
|
+
@navigation_data = validate_and_parse(data)
|
9
|
+
@cache_class = options.fetch(:cache_class) { LocationCache }
|
10
|
+
end
|
11
|
+
|
12
|
+
def coordinates
|
13
|
+
"#{geometry['location']['lat']},#{geometry['location']['lng']}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def name
|
17
|
+
navigation_data['formatted_address']
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_to_cache
|
21
|
+
@cache_class.write(location, attributes_for_cache)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
attr_reader :navigation_data, :location
|
26
|
+
|
27
|
+
def attributes_for_cache
|
28
|
+
{
|
29
|
+
coordinates: coordinates,
|
30
|
+
name: name
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def geometry
|
35
|
+
navigation_data['geometry']
|
36
|
+
end
|
37
|
+
|
38
|
+
def get_data_from_api(location)
|
39
|
+
`curl -s "#{GEOCODE_URL}/json?address=#{location}&key=#{ENV['GOOGLEMAPSSECRET']}"`
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate_and_parse(data)
|
43
|
+
response = JSON.parse(data)
|
44
|
+
|
45
|
+
raise ArgumentError, "Location not found" if response['results'].empty?
|
46
|
+
response['results'][0]
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
class MoonInfo
|
2
|
+
# This service is built for the 'moonPhase' variable in the DarkSky API
|
3
|
+
# where a 'new' moon is 0 (0.99, 0, and 0.01)
|
4
|
+
# and a 'full' moon is 0.5 (0.49 - 0.51)
|
5
|
+
|
6
|
+
EMOJI = {
|
7
|
+
'new' => '🌑',
|
8
|
+
'crescent' => '🌒',
|
9
|
+
'first quarter' => '🌓',
|
10
|
+
'gibbous' => '🌔',
|
11
|
+
'full' => '🌕',
|
12
|
+
'disseminating' => '🌖',
|
13
|
+
'last quarter' => '🌗',
|
14
|
+
'balsamic' => '🌘'
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def initialize(cycle_completion)
|
18
|
+
@cycle_completion = cycle_completion
|
19
|
+
end
|
20
|
+
|
21
|
+
def illumination
|
22
|
+
return 0 if new?
|
23
|
+
return 1 if full?
|
24
|
+
if waxing?
|
25
|
+
((cycle_completion / 0.48) - 0.01).round(2)
|
26
|
+
else
|
27
|
+
(1 - ((cycle_completion - 0.51) / 0.48)).round(2)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def waxing?
|
32
|
+
cycle_completion.between?(0.02, 0.48)
|
33
|
+
end
|
34
|
+
|
35
|
+
def waning?
|
36
|
+
cycle_completion.between?(0.51, 0.98)
|
37
|
+
end
|
38
|
+
|
39
|
+
def phase_name
|
40
|
+
return 'new' if new?
|
41
|
+
return 'full' if full?
|
42
|
+
|
43
|
+
if waxing?
|
44
|
+
return 'crescent' if crescent?
|
45
|
+
return 'first quarter' if quarter?
|
46
|
+
return 'gibbous' if gibbous?
|
47
|
+
else
|
48
|
+
return 'disseminating' if gibbous?
|
49
|
+
return 'last quarter' if quarter?
|
50
|
+
return 'balsamic' if crescent?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def in_sign
|
55
|
+
#MoonSignCalculator.calculate(date)
|
56
|
+
end
|
57
|
+
|
58
|
+
def active_elements
|
59
|
+
return []
|
60
|
+
@active_elements ||= build_active_elements
|
61
|
+
end
|
62
|
+
|
63
|
+
def emoji
|
64
|
+
EMOJI[phase_name]
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
private
|
69
|
+
attr_reader :cycle_completion
|
70
|
+
|
71
|
+
def new?
|
72
|
+
cycle_completion > 0.98 || cycle_completion < 0.02
|
73
|
+
end
|
74
|
+
|
75
|
+
def crescent?
|
76
|
+
cycle_completion.between?(0.02,0.23) || cycle_completion.between?(0.77,0.98)
|
77
|
+
end
|
78
|
+
|
79
|
+
def quarter?
|
80
|
+
cycle_completion.between?(0.24,0.26) || cycle_completion.between?(0.74,0.76)
|
81
|
+
end
|
82
|
+
|
83
|
+
def gibbous?
|
84
|
+
cycle_completion.between?(0.27,0.48) || cycle_completion.between?(0.52,0.73)
|
85
|
+
end
|
86
|
+
|
87
|
+
def full?
|
88
|
+
cycle_completion.between?(0.49,0.51)
|
89
|
+
end
|
90
|
+
|
91
|
+
#def build_active_elements
|
92
|
+
# return ['water'] if new?
|
93
|
+
# return ['fire'] if full?
|
94
|
+
|
95
|
+
# elements = Array.new
|
96
|
+
|
97
|
+
# if waxing?
|
98
|
+
# elements.push('earth')
|
99
|
+
# elements.push('fire') if gibbous?
|
100
|
+
# elements.unshift('water') if crescent?
|
101
|
+
# else
|
102
|
+
# elements.push('air')
|
103
|
+
# elements.push('water') if crescent?
|
104
|
+
# elements.unshift('fire') if gibbous?
|
105
|
+
# end
|
106
|
+
|
107
|
+
# elements
|
108
|
+
#end
|
109
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# Translated into ruby from www.astrocal.co.uk/apps/moonsign/moon.js
|
2
|
+
|
3
|
+
class MoonSign
|
4
|
+
attr_reader :sign
|
5
|
+
|
6
|
+
OBLIQUITY_BASE = 23.452294
|
7
|
+
|
8
|
+
def initialize(time)
|
9
|
+
@sign = calculate_sign(time)
|
10
|
+
end
|
11
|
+
|
12
|
+
def symbol
|
13
|
+
symbol_map[sign]
|
14
|
+
end
|
15
|
+
|
16
|
+
def degree
|
17
|
+
@degree # set in #calculate_sign
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def calculate_sign(time)
|
23
|
+
julian_day = time.to_date.jd
|
24
|
+
|
25
|
+
zone = time.gmt_offset
|
26
|
+
f = time.hour + (time.min / 60.to_f) + (zone / 3600.to_f)
|
27
|
+
t = ((julian_day - 2415020)+ f/24-0.5) / 36525.to_f
|
28
|
+
ll = 973563+ 1732564379*t- 4*t*t
|
29
|
+
g = 1012395+ 6189*t
|
30
|
+
n = 933060- 6962911*t+ 7.5*t*t
|
31
|
+
g1 = 1203586+ 14648523*t- 37*t*t
|
32
|
+
d = 1262655+ 1602961611*t- 5*t*t
|
33
|
+
l = (ll- g1) / 3600.to_f
|
34
|
+
l1 = ((ll- d)- g) / 3600.to_f
|
35
|
+
f = (ll- n) / 3600
|
36
|
+
d = d / 3600
|
37
|
+
y = 2*d
|
38
|
+
ml = 22639.6*FNs(l)- 4586.4*FNs(l- y)
|
39
|
+
ml = ml + 2369.9*FNs(y)+ 769*FNs(2*l)- 669*FNs(l1)
|
40
|
+
ml = ml - 411.6*FNs(2*f)- 212*FNs(2*l- y)
|
41
|
+
ml = ml - 206*FNs(l+ l1- y)+ 192*FNs(l+ y)
|
42
|
+
ml = ml - 165*FNs(l1- y)+ 148*FNs(l- l1)- 125*FNs(d)
|
43
|
+
ml = ml - 110*FNs(l+ l1)- 55*FNs(2*f- y)
|
44
|
+
ml = ml - 45*FNs(l+ 2*f)+ 40*FNs(l- 2*f)
|
45
|
+
tn = n + 5392*FNs(2*f- y)- 541*FNs(l1)- 442*FNs(y)
|
46
|
+
tn = tn + 423*FNs(2*f)- 291*FNs(2*l- 2*f)
|
47
|
+
g = FNu(FNp(ll+ ml))
|
48
|
+
sign = (g/30).floor
|
49
|
+
@degree = (g-(sign*30))
|
50
|
+
sign = sign+1
|
51
|
+
|
52
|
+
case sign
|
53
|
+
when 1 then 'Aries'
|
54
|
+
when 2 then 'Taurus'
|
55
|
+
when 3 then 'Gemini'
|
56
|
+
when 4 then 'Cancer'
|
57
|
+
when 5 then 'Leo'
|
58
|
+
when 6 then 'Virgo'
|
59
|
+
when 7 then 'Libra'
|
60
|
+
when 8 then 'Scorpio'
|
61
|
+
when 9 then 'Sagittarius'
|
62
|
+
when 10 then 'Capricorn'
|
63
|
+
when 11 then 'Aquarius'
|
64
|
+
when 12 then 'Pisces'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def symbol_map
|
69
|
+
{
|
70
|
+
'Aries' => '♈',
|
71
|
+
'Taurus' => '♉',
|
72
|
+
'Gemini' => '♊',
|
73
|
+
'Cancer' => '♋',
|
74
|
+
'Leo' => '♌',
|
75
|
+
'Virgo' => '♍',
|
76
|
+
'Libra' => '♎',
|
77
|
+
'Scorpio' => '♏',
|
78
|
+
'Sagittarius' => '♐',
|
79
|
+
'Capricorn' => '♑',
|
80
|
+
'Aquarius' => '♒',
|
81
|
+
'Pisces' => '♓'
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
# unused?
|
86
|
+
def obliquity(time)
|
87
|
+
radians(OBLIQUITY_BASE - 0.0130125*time.to_i)
|
88
|
+
end
|
89
|
+
|
90
|
+
def FNp(x)
|
91
|
+
if(x<0)
|
92
|
+
sgn=-1
|
93
|
+
else
|
94
|
+
sgn=1
|
95
|
+
end
|
96
|
+
|
97
|
+
sgn*((x.abs/ 3600) / 360 - ((x.abs / 3600) / 360).floor) * 360
|
98
|
+
end
|
99
|
+
|
100
|
+
def FNu(x)
|
101
|
+
x-((x/360).floor*360)
|
102
|
+
end
|
103
|
+
|
104
|
+
def radians(x)
|
105
|
+
Math::PI / 180*x
|
106
|
+
end
|
107
|
+
|
108
|
+
def FNs(x)
|
109
|
+
Math.sin(radians(x))
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Navigator
|
2
|
+
def initialize(location, options = {})
|
3
|
+
@location = location
|
4
|
+
@location_cache_class = options.fetch(:location_cache) { LocationCache }
|
5
|
+
@locator_class = options.fetch(:locator) { Locator }
|
6
|
+
fetch_cache_values
|
7
|
+
end
|
8
|
+
|
9
|
+
def coordinates
|
10
|
+
@coordinates ||= locator.coordinates
|
11
|
+
end
|
12
|
+
|
13
|
+
def location_name
|
14
|
+
@location_name ||= locator.name
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_reader :location
|
19
|
+
|
20
|
+
def fetch_cache_values
|
21
|
+
#todo more elegant solution for when cache is not set?
|
22
|
+
return unless ENV['CACHE_PATH']
|
23
|
+
|
24
|
+
if cached_location.exists?
|
25
|
+
@coordinates = cached_location.coordinates
|
26
|
+
@location_name = cached_location.name
|
27
|
+
else
|
28
|
+
locator.write_to_cache
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def locator
|
33
|
+
@locator ||= @locator_class.new(location, cache_class: @location_cache_class)
|
34
|
+
end
|
35
|
+
|
36
|
+
def cached_location
|
37
|
+
@cached_location ||= @location_cache_class.new(location)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'meteorologist'
|
3
|
+
s.version = '0.0.6'
|
4
|
+
s.date = '2017-02-18'
|
5
|
+
s.summary = "Get the weather for a place and time"
|
6
|
+
s.description = "Get the weather for a place and time"
|
7
|
+
s.authors = ["Dax"]
|
8
|
+
s.email = 'd.dax@email.com'
|
9
|
+
s.files = Dir['lib/**/*', 'meteorologist.gemspec']
|
10
|
+
s.homepage =
|
11
|
+
'http://rubygems.org/gems/meteorologist'
|
12
|
+
s.license = 'CC-BY-NC-SA-4.0'
|
13
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: meteorologist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dax
|
@@ -17,6 +17,13 @@ extensions: []
|
|
17
17
|
extra_rdoc_files: []
|
18
18
|
files:
|
19
19
|
- lib/meteorologist.rb
|
20
|
+
- lib/meteorologist/forecaster.rb
|
21
|
+
- lib/meteorologist/location_cache.rb
|
22
|
+
- lib/meteorologist/locator.rb
|
23
|
+
- lib/meteorologist/moon_info.rb
|
24
|
+
- lib/meteorologist/moon_sign_calculator.rb
|
25
|
+
- lib/meteorologist/navigator.rb
|
26
|
+
- meteorologist.gemspec
|
20
27
|
homepage: http://rubygems.org/gems/meteorologist
|
21
28
|
licenses:
|
22
29
|
- CC-BY-NC-SA-4.0
|