meteorologist 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|