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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f221780cb05ec076ac1c2120566a4b6b82adadf0
4
- data.tar.gz: 476bc7db639356030a8cfaae177efa66736c5e9f
3
+ metadata.gz: 3f10ca63f0c1a1097591ec35acb4112cd6a96083
4
+ data.tar.gz: dcf4f7bb458f33c83e6274cab27d997b53f5c01e
5
5
  SHA512:
6
- metadata.gz: b08026e0114d74cd411ae23ac28916bf52b471675a22f4f2b7ab7af19eaf875e298c9f7aaf7a0736cbdc30088c6a49f9248dd25b0da78ca0996187a2e9ed8733
7
- data.tar.gz: 49b399bb03e0fa67feffe20c6e60891d16facd2377c00c5a41fa87abd3e130686b795e53214e6e77e09839a717d22d198c3d9c305881a6ceefdffc34728f8af6
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.5
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