suncalc 1.0.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ec49609bd57c26eb9641e01e94e2fd9bdf43fe1c
4
+ data.tar.gz: 49800b56abb05f9e98a82054b981821e52846ac2
5
+ SHA512:
6
+ metadata.gz: eb35b29bb651e268853f01c0b484e5550bcb9dcf9fd4c45d9ee60b2e69b3702c95f35a3440e1ab2b7d506696a300e8aec233894e115b8b985f3a2ba0b4678f54
7
+ data.tar.gz: ed6add76cedfd70c0f218a9a3c3d8f052364621382c490154ae9cd0991fd52fcce758c1800e3e6648d9d7d7c01913d59c996b6db4efad6652cd92fab17d71d9a
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in suncalc.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Greg Mundy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,34 @@
1
+ # SunCalc
2
+
3
+ SunCalc is a tiny Ruby library for calculating sun position,
4
+ sunlight phases (times for sunrise, sunset, dusk, etc.), moon position and
5
+ lunar phase for the given location and time. This library is a port of
6
+ [Vladmir Agafonkin](http://agafonkin.com/en)'s ([@mourner](https://github.com/mourner)) excellent SunCalc.js library.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'suncalc'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install suncalc
23
+
24
+ ## Usage
25
+
26
+ TODO: Write usage instructions here
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it ( https://github.com/[my-github-username]/suncalc/fork )
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ Dir.glob('tasks/**/*.rake').each(&method(:import))
@@ -0,0 +1,290 @@
1
+ require "suncalc/version"
2
+
3
+ module SunCalc
4
+ # Shortcuts for easier to read equations
5
+ RAD = Math::PI / 180
6
+ DAY_MS = 1000 * 60 * 60 * 24
7
+ J1970 = 2440588
8
+ J2000 = 2451545
9
+ E = RAD * 23.4397
10
+ J0 = 0.0009
11
+ SDIST = 149598000
12
+ HC = 0.133 * RAD
13
+
14
+ TIMES = [
15
+ [-0.833, :sunrise, :sunset],
16
+ [-0.3, :sunrise_end, :sunset_start],
17
+ [-6, :dawn, :dusk],
18
+ [-12, :nautical_dawn, :nautical_dusk],
19
+ [-18, :night_end, :night],
20
+ [6, :golden_hour_end, :golden_hour]
21
+ ]
22
+
23
+ # Date/time constants and conversions
24
+
25
+ def self.to_julian(date)
26
+ (date.to_f * 1000) / DAY_MS - 0.5 + J1970
27
+ end
28
+
29
+ def self.from_julian(j)
30
+ Time.at(((j + 0.5 - J1970) * DAY_MS)/1000).utc
31
+ end
32
+
33
+ def self.to_days(date)
34
+ to_julian(date) - J2000
35
+ end
36
+
37
+
38
+ # General calculations for position
39
+
40
+ def self.right_ascension(l, b)
41
+ Math::atan2(Math::sin(l) * Math::cos(E) - Math::tan(b) * Math::sin(E), Math::cos(l))
42
+ end
43
+
44
+ def self.declination(l, b)
45
+ Math::asin(Math::sin(b) * Math::cos(E) + Math::cos(b) * Math::sin(E) * Math::sin(l))
46
+ end
47
+
48
+ def self.azimuth(h, phi, dec)
49
+ Math::atan2(Math::sin(h), Math::cos(h) * Math::sin(phi) - Math::tan(dec) * Math::cos(phi))
50
+ end
51
+
52
+ def self.altitude(h, phi, dec)
53
+ Math::asin(Math::sin(phi) * Math::sin(dec) + Math::cos(phi) * Math::cos(dec) * Math::cos(h))
54
+ end
55
+
56
+ def self.sidereal_time(d, lw)
57
+ RAD * (280.16 + 360.9856235 * d) - lw
58
+ end
59
+
60
+ # General sun calculations
61
+ def self.solar_mean_anomaly(d)
62
+ RAD * (357.5291 + 0.98560028 * d)
63
+ end
64
+
65
+ def self.ecliptic_longitude(m)
66
+ c = RAD * (1.9148 * Math::sin(m) + 0.02 * Math::sin(2 * m) + 0.0003 * Math::sin(3 * m))
67
+ p = RAD * 102.9372
68
+
69
+ m + c + p + Math::PI
70
+ end
71
+
72
+ def self.sun_coords(d)
73
+ @result = []
74
+ sM = solar_mean_anomaly(d)
75
+ eL = ecliptic_longitude(sM)
76
+
77
+
78
+ { :dec => declination(eL, 0),
79
+ :ra => right_ascension(eL, 0)
80
+ }
81
+ end
82
+
83
+ # Calculate sun position for a given date and latitude/longitude
84
+ def self.get_position(date, lat, lng)
85
+ lw = RAD * -lng
86
+ phi = RAD * lat
87
+ d = to_days(date)
88
+ c = sun_coords(d)
89
+ h = sidereal_time(d, lw) - c[:ra]
90
+
91
+ { :azimuth => azimuth(h, phi, c[:dec]),
92
+ :altitude => altitude(h, phi, c[:dec])
93
+ }
94
+ end
95
+
96
+ # Sun times configuration (angle, morning name, evening name)
97
+
98
+ def self.add_time(angle, rise_name, set_name)
99
+ TIMES << [angle, rise_name, set_name]
100
+ end
101
+
102
+ # Calculations for sun times
103
+ def self.julian_cycle(d, lw)
104
+ (d - J0 - lw / (2 * Math::PI)).round
105
+ end
106
+
107
+ def self.approx_transit(ht, lw, n)
108
+ J0 + (ht + lw) / (2 * Math::PI) + n
109
+ end
110
+
111
+ def self.solar_transit_j(ds, m, l)
112
+ J2000 + ds + 0.0053 * Math::sin(m) - 0.0069 * Math::sin(2 * l)
113
+ end
114
+
115
+ def self.hour_angle(h, phi, d)
116
+ Math::acos((Math::sin(h) - Math::sin(phi) * Math::sin(d)) / (Math::cos(phi) * Math::cos(d)))
117
+ end
118
+
119
+ # Returns set time for the given sun altitude
120
+ def self.get_set_j(h, lw, phi, dec, n, m, l)
121
+ w = hour_angle(h, phi, dec)
122
+ a = approx_transit(w, lw, n)
123
+ solar_transit_j(a, m, l)
124
+ end
125
+
126
+ # Calculate sun times for a given date and latitude/longitude
127
+ def self.get_times(date, lat, lng)
128
+ lw = RAD * -lng
129
+ phi = RAD * lat
130
+
131
+ d = to_days(date)
132
+ n = julian_cycle(d, lw)
133
+ ds = approx_transit(0, lw, n)
134
+
135
+ m = solar_mean_anomaly(ds)
136
+ l = ecliptic_longitude(m)
137
+ dec = declination(l, 0)
138
+
139
+ jnoon = solar_transit_j(ds, m, l)
140
+
141
+ result = {
142
+ :solar_noon => from_julian(jnoon),
143
+ :nadir => from_julian(jnoon - 0.5)
144
+ }
145
+
146
+ TIMES.each do |time|
147
+ jset = get_set_j(time[0] * RAD, lw, phi, dec, n, m, l)
148
+ jrise = jnoon - (jset - jnoon)
149
+
150
+ result[time[1]] = from_julian(jrise)
151
+ result[time[2]] = from_julian(jset)
152
+ end
153
+
154
+ result
155
+ end
156
+
157
+ # Moon calculations
158
+ def self.moon_coords(d)
159
+ el = RAD * (218.316 + 13.176396 * d)
160
+ m = RAD * (134.963 + 13.064993 * d)
161
+ f = RAD * (93.272 + 13.229350 * d)
162
+
163
+ l = el + RAD * 6.289 * Math::sin(m)
164
+ b = RAD * 5.128 * Math::sin(f)
165
+ dt = 385001 - 20905 * Math::cos(m)
166
+
167
+
168
+ result = {
169
+ :ra => right_ascension(l, b),
170
+ :dec => declination(l, b),
171
+ :dist => dt
172
+ }
173
+
174
+ result
175
+ end
176
+
177
+ def self.get_moon_position(date, lat, lng)
178
+ lw = RAD * -lng
179
+ phi = RAD * lat
180
+ d = to_days(date)
181
+
182
+ c = moon_coords(d)
183
+ th = sidereal_time(d, lw) - c[:ra]
184
+ h = altitude(th, phi, c[:dec])
185
+
186
+ h = h + RAD * 0.017 / Math::tan(h + RAD * 10.26 / (h + RAD * 5.10))
187
+
188
+ result = {
189
+ :azimuth => azimuth(th, phi, c[:dec]),
190
+ :altitude => h,
191
+ :distance => c[:dist]
192
+ }
193
+
194
+ result
195
+ end
196
+
197
+ # Calculations for illumination parameters of the moon
198
+ def self.get_moon_illumination(date)
199
+ d = to_days(date)
200
+ s = sun_coords(d)
201
+ m = moon_coords(d)
202
+
203
+ phi = Math::acos(Math::sin(s[:dec]) * Math::sin(m[:dec]) + Math::cos(s[:dec]) * Math::cos(m[:dec]) * Math::cos(s[:ra] - m[:ra]))
204
+ inc = Math::atan2(SDIST * Math::sin(phi), m[:dist] - SDIST * Math::cos(phi))
205
+ angle = Math::atan2(Math::cos(s[:dec]) * Math::sin(s[:ra] - m[:ra]), Math::sin(s[:dec]) * Math::cos(m[:dec]) - Math::cos(s[:dec]) * Math::sin(m[:dec]) * Math::cos(s[:ra] - m[:ra]))
206
+
207
+ result = {
208
+ :fraction => (1 + Math::cos(inc)) / 2,
209
+ :phase => 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math::PI,
210
+ :angle => angle
211
+ }
212
+
213
+ result
214
+ end
215
+
216
+ def self.hours_later(date, h)
217
+ Time.at(date.to_f + (h * (DAY_MS/1000)) / 24).utc
218
+ end
219
+
220
+ def self.get_moon_times(date, lat, lng)
221
+ t = Time.new(date.year.to_i, date.month.to_i, date.day.to_i).utc
222
+ h0 = get_moon_position(t, lat, lng)[:altitude] - HC
223
+
224
+ rise = false
225
+ set = false
226
+ ye = 0
227
+
228
+ (1..24).step(2) do |i|
229
+ h1 = get_moon_position(hours_later(t, i), lat, lng)[:altitude] - HC
230
+ h2 = get_moon_position(hours_later(t, i + 1), lat, lng)[:altitude] - HC
231
+
232
+ a = (h0 + h2) / 2 - h1
233
+ b = (h2 - h0) / 2
234
+ xe = -b / (2 * a)
235
+ ye = (a * xe + b) * xe + h1
236
+ d = b * b - 4 * a * h1
237
+
238
+ roots = 0
239
+
240
+ if d >= 0
241
+ dx = Math::sqrt(d) / (a.abs * 2)
242
+
243
+ x1 = xe - dx
244
+ x2 = xe + dx
245
+
246
+ if x1.abs <= 1
247
+ roots += 1
248
+ end
249
+
250
+ if x2.abs <= 1
251
+ roots += 1
252
+ end
253
+
254
+ if x1 < -1
255
+ x1 = x2
256
+ end
257
+ end
258
+
259
+ if roots === 1
260
+ if h0 < 0
261
+ rise = i + x1
262
+ else
263
+ set = i + x1
264
+ end
265
+ elsif roots === 2
266
+ rise = i + (ye < 0 ? x2 : x1)
267
+ set = i + (ye < 0 ? x1 : x2)
268
+ end
269
+
270
+ break if rise and set
271
+
272
+ h0 = h2
273
+ end
274
+
275
+ result = {}
276
+ if rise
277
+ result[:rise] = hours_later(t, rise)
278
+ end
279
+
280
+ if set
281
+ result[:set] = hours_later(t, set)
282
+ end
283
+
284
+ if not rise and not set
285
+ result[ye > 0 ? :alwaysUp : :alwaysDown] = true
286
+ end
287
+
288
+ result
289
+ end
290
+ end
@@ -0,0 +1,3 @@
1
+ module SunCalc
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,30 @@
1
+ require 'suncalc'
2
+
3
+ LAT = 50.5
4
+ LNG = 30.5
5
+ DATE = Time.utc(2013,03,05)
6
+
7
+ TEST_TIMES = {
8
+ :solar_noon => Time.utc(2013,03,05,10,10,57),
9
+ :nadir => Time.utc(2013,03,04,22,10,57),
10
+ :sunrise => Time.utc(2013,03,05,04,34,56),
11
+ :sunset => Time.utc(2013,03,05,15,46,57),
12
+ :sunrise_end => Time.utc(2013,03,05,04,38,19),
13
+ :sunset_start => Time.utc(2013,03,05,15,43,34),
14
+ :dawn => Time.utc(2013,03,05,04,02,17),
15
+ :dusk => Time.utc(2013,03,05,16,19,36),
16
+ :nautical_dawn => Time.utc(2013,03,05,03,24,31),
17
+ :nautical_dusk => Time.utc(2013,03,05,16,57,22),
18
+ :night_end => Time.utc(2013,03,05,02,46,17),
19
+ :night => Time.utc(2013,03,05,17,35,36),
20
+ :golden_hour_end => Time.utc(2013,03,05,05,19,01),
21
+ :golden_hour => Time.utc(2013,03,05,15,02,52)
22
+ }
23
+
24
+ MOON_RISE = Time.utc(2013,03,04,23,57,55)
25
+ MOON_SET = Time.utc(2013, 03, 05,8,41,31)
26
+
27
+ def near(val1, val2, margin)
28
+ @compare = margin.nil? ? 0.000000000000001 : margin
29
+ (val1 - val2).abs < @compare
30
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe SunCalc do
4
+ it "calculates azimuth and altitude for the given time and location" do
5
+ @sun_pos = SunCalc.get_position(DATE, LAT, LNG)
6
+ expect(near(@sun_pos[:azimuth], -2.5003175907168385, nil)).to be true
7
+ expect(near(@sun_pos[:altitude], -0.7000406838781611, nil)).to be true
8
+ end
9
+
10
+ it "can return sun phases for the given date and location" do
11
+ @times = SunCalc.get_times(DATE, LAT, LNG)
12
+
13
+ TEST_TIMES.each do |k,v|
14
+ expect(@times[k].to_s).to eq(v.to_s)
15
+ end
16
+ end
17
+
18
+ it "can return moon position data given time and location" do
19
+ @moon_pos = SunCalc.get_moon_position(DATE, LAT, LNG)
20
+ expect(near(@moon_pos[:azimuth], -0.9783999522438226, nil)).to be true
21
+ expect(near(@moon_pos[:altitude], 0.006969727754891917, nil)).to be true
22
+ expect(near(@moon_pos[:distance], 364121.37256256194, nil)).to be true
23
+ end
24
+
25
+ it "can return fraction and angle of moon's illuminated limb and phase" do
26
+ @moon_illum = SunCalc.get_moon_illumination(DATE)
27
+ expect(near(@moon_illum[:fraction], 0.4848068202456373, nil)).to be true
28
+ expect(near(@moon_illum[:phase], 0.7548368838538762, nil)).to be true
29
+ expect(near(@moon_illum[:angle], 1.6732942678578346, nil)).to be true
30
+ end
31
+
32
+ it "can return moon rise and set times" do
33
+ @moon_times = SunCalc.get_moon_times(DATE, LAT, LNG)
34
+ expect(@moon_times[:rise].to_i).to be_within(1000).of(MOON_RISE.to_i)
35
+ expect(@moon_times[:set].to_i).to be_within(1000).of(MOON_SET.to_i)
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'suncalc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "suncalc"
8
+ spec.version = SunCalc::VERSION
9
+ spec.authors = ["Greg Mundy"]
10
+ spec.email = ["gregmundy@gmail.com"]
11
+ spec.summary = %q{Ruby port of Vladimir Agafonkin's excellent suncalc.js library.}
12
+ spec.description = %q{A Ruby library for calculating sun/moon positions and phases.}
13
+ spec.homepage = "https://bitbucket.org/greg_mundy/suncalc.git"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.7"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "rspec"
24
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: suncalc
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Greg Mundy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A Ruby library for calculating sun/moon positions and phases.
56
+ email:
57
+ - gregmundy@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/suncalc.rb
68
+ - lib/suncalc/version.rb
69
+ - spec/spec_helper.rb
70
+ - spec/suncalc_spec.rb
71
+ - suncalc.gemspec
72
+ - tasks/rspec.rake
73
+ homepage: https://bitbucket.org/greg_mundy/suncalc.git
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Ruby port of Vladimir Agafonkin's excellent suncalc.js library.
97
+ test_files:
98
+ - spec/spec_helper.rb
99
+ - spec/suncalc_spec.rb