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 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