RubySunrise 0.1.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,190 @@
1
+ require 'bigdecimal'
2
+ require 'date'
3
+
4
+ class SolarEventCalculator
5
+
6
+ @date
7
+ @latitude
8
+ @longitude
9
+
10
+ def initialize(date, latitude, longitude)
11
+ @date = date
12
+ @latitude = latitude
13
+ @longitude = longitude
14
+ end
15
+
16
+ def compute_longitude_hour
17
+ lngHour = @longitude / BigDecimal.new("15")
18
+ lngHour.round(4)
19
+ end
20
+
21
+ def compute_rise_longitude_hour
22
+ longHour = @date.yday + ((BigDecimal.new("6") - compute_longitude_hour) / BigDecimal.new("24"))
23
+ longHour.round(4)
24
+ end
25
+
26
+ def compute_set_longitude_hour
27
+ longHour = @date.yday + ((BigDecimal.new("18") - compute_longitude_hour) / BigDecimal.new("24"))
28
+ longHour.round(4)
29
+ end
30
+
31
+ def compute_sun_mean_anomaly(longHour)
32
+ constant = BigDecimal.new("0.9856")
33
+ ((longHour * constant) - BigDecimal.new("3.289")).round(4)
34
+ end
35
+
36
+ def compute_sun_true_longitude(meanAnomaly)
37
+ mAsRads = degrees_as_rads(meanAnomaly)
38
+ sinM = BigDecimal.new(Math.sin(mAsRads.to_f).to_s)
39
+ sinTwoM = BigDecimal.new(Math.sin((2 * mAsRads).to_f).to_s)
40
+ firstParens = BigDecimal.new("1.916") * sinM
41
+ secondParens = BigDecimal.new("0.020") * sinTwoM
42
+ trueLong = meanAnomaly + firstParens + secondParens + BigDecimal.new("282.634")
43
+ trueLong = put_in_range(trueLong, 0, 360, 360)
44
+ trueLong.round(4)
45
+ end
46
+
47
+ def compute_right_ascension(sunTrueLong)
48
+ tanL = BigDecimal.new(Math.tan(degrees_as_rads(sunTrueLong).to_f).to_s)
49
+ ra = rads_as_degrees(BigDecimal.new(Math.atan(BigDecimal.new("0.91764") * tanL).to_s))
50
+
51
+ ra = put_in_range(ra, 0, 360, 360)
52
+ ra.round(4)
53
+ end
54
+
55
+ def put_ra_in_correct_quadrant(sunTrueLong)
56
+ lQuadrant = BigDecimal.new("90") * (sunTrueLong / BigDecimal.new("90")).floor
57
+ raQuadrant = BigDecimal.new("90") * (compute_right_ascension(sunTrueLong) / BigDecimal.new("90")).floor
58
+
59
+ ra = compute_right_ascension(sunTrueLong) + (lQuadrant - raQuadrant)
60
+ ra = ra / BigDecimal.new("15")
61
+ ra.round(4)
62
+ end
63
+
64
+ def compute_sin_sun_declination(sunTrueLong)
65
+ sinL = BigDecimal.new(Math.sin(degrees_as_rads(sunTrueLong).to_f).to_s)
66
+ sinDec = sinL * BigDecimal.new("0.39782")
67
+ sinDec.round(4)
68
+ end
69
+
70
+ def compute_cosine_sun_declination(sinSunDeclination)
71
+ cosDec = BigDecimal.new(Math.cos(Math.asin(sinSunDeclination)).to_s)
72
+ cosDec.round(4)
73
+ end
74
+
75
+ def compute_cosine_sun_local_hour(sunTrueLong, zenith)
76
+ cosZenith = BigDecimal.new(Math.cos(degrees_as_rads(BigDecimal.new(zenith.to_s))).to_s)
77
+ sinLatitude = BigDecimal.new(Math.sin(degrees_as_rads(@latitude)).to_s)
78
+ cosLatitude = BigDecimal.new(Math.cos(degrees_as_rads(@latitude)).to_s)
79
+
80
+ sinSunDeclination = compute_sin_sun_declination(sunTrueLong)
81
+ top = cosZenith - (sinSunDeclination * sinLatitude)
82
+ bottom = compute_cosine_sun_declination(sinSunDeclination) * cosLatitude
83
+
84
+ cosLocalHour = top / bottom
85
+ cosLocalHour.round(4)
86
+ end
87
+
88
+ def compute_sunrise_local_hour_angle(cosSunLocalHour)
89
+ acosH = BigDecimal.new(Math.acos(cosSunLocalHour).to_s)
90
+ localHourAngle = BigDecimal.new("360") - rads_as_degrees(acosH)
91
+ localHourAngle = localHourAngle / BigDecimal.new("15")
92
+ localHourAngle.round(4)
93
+ end
94
+
95
+ def compute_sunset_local_hour_angle(cosSunLocalHour)
96
+ acosH = BigDecimal.new(Math.acos(cosSunLocalHour).to_s)
97
+ acosH = rads_as_degrees(acosH)
98
+ localHourAngle = acosH / BigDecimal.new("15")
99
+ localHourAngle.round(4)
100
+ end
101
+
102
+ def compute_local_mean_time(sunTrueLong, longHour, t, sunLocalHour)
103
+ h = sunLocalHour
104
+ ra = put_ra_in_correct_quadrant(sunTrueLong)
105
+
106
+ parens = BigDecimal.new("0.06571") * t
107
+ time = h + ra - parens - BigDecimal.new("6.622")
108
+
109
+ utcTime = time - longHour
110
+ utcTime = put_in_range(utcTime, 0, 24, 24)
111
+ utcTime.round(4)
112
+ end
113
+
114
+ def compute_utc_sunrise(zenith)
115
+ longHour = compute_longitude_hour
116
+ riseLongHour = compute_rise_longitude_hour
117
+
118
+ meanAnomaly = compute_sun_mean_anomaly(riseLongHour)
119
+ sunTrueLong = compute_sun_true_longitude(meanAnomaly)
120
+ cosineSunLocalHour = compute_cosine_sun_local_hour(sunTrueLong, zenith)
121
+
122
+ if(cosineSunLocalHour > BigDecimal.new("1") || cosineSunLocalHour < BigDecimal.new("-1"))
123
+ return nil
124
+ end
125
+
126
+ sunLocalHour = compute_sunrise_local_hour_angle(cosineSunLocalHour)
127
+ localMeanTime = compute_local_mean_time(sunTrueLong, longHour, riseLongHour, sunLocalHour)
128
+
129
+ timeParts = localMeanTime.to_s('F').split('.')
130
+ mins = BigDecimal.new("." + timeParts[1]) * BigDecimal.new("60")
131
+ mins = mins.truncate()
132
+ mins = pad_minutes(mins.to_i)
133
+ hours = timeParts[0]
134
+
135
+ Time.gm(@date.year, @date.mon, @date.mday, hours, pad_minutes(mins.to_i))
136
+ end
137
+
138
+ def compute_utc_sunset(zenith)
139
+ longHour = compute_longitude_hour
140
+ setLongHour = compute_set_longitude_hour
141
+
142
+ meanAnomaly = compute_sun_mean_anomaly(setLongHour)
143
+ sunTrueLong = compute_sun_true_longitude(meanAnomaly)
144
+ cosineSunLocalHour = compute_cosine_sun_local_hour(sunTrueLong, zenith)
145
+
146
+ if(cosineSunLocalHour > BigDecimal.new("1") || cosineSunLocalHour < BigDecimal.new("-1"))
147
+ return nil
148
+ end
149
+
150
+ sunLocalHour = compute_sunset_local_hour_angle(cosineSunLocalHour)
151
+ localMeanTime = compute_local_mean_time(sunTrueLong, longHour, setLongHour, sunLocalHour)
152
+
153
+ timeParts = localMeanTime.to_s('F').split('.')
154
+ mins = BigDecimal.new("." + timeParts[1]) * BigDecimal.new("60")
155
+ mins = mins.truncate()
156
+ hours = timeParts[0]
157
+
158
+ Time.gm(@date.year, @date.mon, @date.mday, hours, pad_minutes(mins.to_i))
159
+ end
160
+
161
+ def pad_minutes(minutes)
162
+ if(minutes < 10)
163
+ "0" + minutes.to_s
164
+ else
165
+ minutes
166
+ end
167
+ end
168
+
169
+ def put_in_range(number, lower, upper, adjuster)
170
+ if number > upper then
171
+ number -= adjuster
172
+ elsif number < lower then
173
+ number += adjuster
174
+ else
175
+ number
176
+ end
177
+ end
178
+
179
+ def degrees_as_rads(degrees)
180
+ pi = BigDecimal(Math::PI.to_s)
181
+ radian = pi / BigDecimal.new("180")
182
+ degrees * radian
183
+ end
184
+
185
+ def rads_as_degrees(radians)
186
+ pi = BigDecimal(Math::PI.to_s)
187
+ degree = BigDecimal.new("180") / pi
188
+ radians * degree
189
+ end
190
+ end
@@ -0,0 +1,12 @@
1
+ spec = Gem::Specification.new do | s |
2
+ s.name = "RubySunrise"
3
+ s.version = "0.1.0"
4
+ s.author = "Mike Reedell / LuckyCatLabs"
5
+ s.email = "mike@luckycatlabs.com"
6
+ s.homepage = "http://www.luckycatlabs.com"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "Calculate the sunrise/sunset given lat/long coordinates and the date. Computes civil, official, nautical, and astronomical sunrise/sunset."
9
+ s.files = ["rubysunrise.gemspec"] + Dir.glob("lib/**/*")
10
+ s.test_files = Dir.glob("{test}/**/*test.rb")
11
+ s.has_rdoc = false
12
+ end
@@ -0,0 +1,89 @@
1
+ require 'sunrise'
2
+
3
+ describe SolarEventCalculator, "test the math for home" do
4
+
5
+ before do
6
+ @date = Date.parse('2008-11-01') #01 November 2008
7
+ @calc = SolarEventCalculator.new(@date, BigDecimal.new("39.9537"), BigDecimal.new("-75.7850"))
8
+ end
9
+
10
+ it "returns correct longitude hour" do
11
+ @calc.compute_longitude_hour.should eql(BigDecimal.new("-5.0523"))
12
+ end
13
+
14
+ it "returns correct longitude hour" do
15
+ @calc.compute_rise_longitude_hour.should eql(BigDecimal.new("306.4605"))
16
+ end
17
+
18
+ it "returns correct sunrise mean anomaly" do
19
+ @calc.compute_sun_mean_anomaly(BigDecimal.new("306.4605")).should eql(BigDecimal.new("298.7585"))
20
+ end
21
+
22
+ it "returns correct sunrise's sun true longitude" do
23
+ @calc.compute_sun_true_longitude(BigDecimal.new("298.7585")).should eql(BigDecimal.new("219.6960"))
24
+ end
25
+
26
+ it "returns correct sunrise's right ascension" do
27
+ @calc.compute_right_ascension(BigDecimal.new("219.6960")).should eql(BigDecimal.new("37.2977"))
28
+ end
29
+
30
+ it "returns correct sunrise's right ascension quadrant" do
31
+ @calc.put_ra_in_correct_quadrant(BigDecimal.new("219.6960")).should eql(BigDecimal.new("14.4865"))
32
+ end
33
+
34
+ it "returns correct sunrise sin sun declination" do
35
+ @calc.compute_sin_sun_declination(BigDecimal.new("219.6960")).should eql(BigDecimal.new("-0.2541"))
36
+ end
37
+
38
+ it "returns correct sunrise cosine sun declination" do
39
+ @calc.compute_cosine_sun_declination(BigDecimal.new("-0.2541")).should eql(BigDecimal.new("0.9672"))
40
+ end
41
+
42
+ it "returns correct sunrise cosine sun local hour" do
43
+ @calc.compute_cosine_sun_local_hour(BigDecimal.new("219.6960"), 96).should eql(BigDecimal.new("0.0791"))
44
+ end
45
+
46
+ it "returns correct sunrise local hour angle" do
47
+ @calc.compute_sunrise_local_hour_angle(BigDecimal.new("0.0791")).should eql(BigDecimal.new("18.3025"))
48
+ end
49
+
50
+ it "returns correct sunrise local mean time" do
51
+ trueLong = BigDecimal.new("219.6960")
52
+ longHour = BigDecimal.new("-5.0523")
53
+ localHour = BigDecimal.new("18.3025")
54
+ t = BigDecimal.new("306.4605")
55
+ @calc.compute_local_mean_time(trueLong, longHour, t, localHour).should eql(BigDecimal.new("11.0818"))
56
+ end
57
+
58
+ it "returns correct civil sunrise time" do
59
+ @calc.compute_utc_sunrise(96).should eql(Time.gm(@date.year, @date.mon, @date.mday, 11, 4))
60
+ end
61
+
62
+ it "returns correct official sunrise time" do
63
+ @calc.compute_utc_sunrise(90.8333).should eql(Time.gm(@date.year, @date.mon, @date.mday, 11, 33))
64
+ end
65
+
66
+ it "returns correct nautical sunrise time" do
67
+ @calc.compute_utc_sunrise(102).should eql(Time.gm(@date.year, @date.mon, @date.mday, 10, 32))
68
+ end
69
+
70
+ it "returns correct astronomical sunrise time" do
71
+ @calc.compute_utc_sunrise(108).should eql(Time.gm(@date.year, @date.mon, @date.mday, 10, 1))
72
+ end
73
+ end
74
+
75
+ describe SolarEventCalculator, "test the math for areas where there could be no rise/set" do
76
+
77
+ it "returns correct time" do
78
+ date = Date.parse('2008-04-25') #25 April 2008
79
+ calc = SolarEventCalculator.new(date, BigDecimal.new("64.8378"), BigDecimal.new("-147.7164"))
80
+ calc.compute_utc_sunrise(108).should eql(nil)
81
+ end
82
+
83
+ it "returns correct time" do
84
+ date = Date.parse('2008-04-25') #25 April 2008
85
+ calc = SolarEventCalculator.new(date, BigDecimal.new("64.8378"), BigDecimal.new("-147.7164"))
86
+ calc.compute_utc_sunrise(102).should eql(nil)
87
+ end
88
+ end
89
+
@@ -0,0 +1,89 @@
1
+ require 'sunrise'
2
+
3
+ describe SolarEventCalculator, "Test the sunset algorithm" do
4
+
5
+ before do
6
+ @date = Date.parse('2008-11-01') #01 November 2008
7
+ @calc = SolarEventCalculator.new(@date, BigDecimal.new("39.9537"), BigDecimal.new("-75.7850"))
8
+ end
9
+
10
+ it "returns correct longitude hour" do
11
+ @calc.compute_longitude_hour.should eql(BigDecimal.new("-5.0523"))
12
+ end
13
+
14
+ it "returns correct longitude hour" do
15
+ @calc.compute_set_longitude_hour.should eql(BigDecimal.new("306.9605"))
16
+ end
17
+
18
+ it "returns correct sunset mean anomaly" do
19
+ @calc.compute_sun_mean_anomaly(BigDecimal.new("306.9605")).should eql(BigDecimal.new("299.2513"))
20
+ end
21
+
22
+ it "returns correct sunset's sun true longitude" do
23
+ @calc.compute_sun_true_longitude(BigDecimal.new("299.2513")).should eql(BigDecimal.new("220.1966"))
24
+ end
25
+
26
+ it "returns correct sunset's right ascension" do
27
+ @calc.compute_right_ascension(BigDecimal.new("220.1966")).should eql(BigDecimal.new("37.7890"))
28
+ end
29
+
30
+ it "returns correct sunset's right ascension quadrant" do
31
+ @calc.put_ra_in_correct_quadrant(BigDecimal.new("220.1966")).should eql(BigDecimal.new("14.5193"))
32
+ end
33
+
34
+ it "returns correct sunset sin sun declination" do
35
+ @calc.compute_sin_sun_declination(BigDecimal.new("220.1966")).should eql(BigDecimal.new("-0.2568"))
36
+ end
37
+
38
+ it "returns correct sunset cosine sun declination" do
39
+ @calc.compute_cosine_sun_declination(BigDecimal.new("-0.2541")).should eql(BigDecimal.new("0.9672"))
40
+ end
41
+
42
+ it "returns correct sunset cosine sun local hour" do
43
+ @calc.compute_cosine_sun_local_hour(BigDecimal.new("220.1966"), 96).should eql(BigDecimal.new("0.0815"))
44
+ end
45
+
46
+ it "returns correct sunset local hour angle" do
47
+ @calc.compute_sunset_local_hour_angle(BigDecimal.new("0.0815")).should eql(BigDecimal.new("5.6883"))
48
+ end
49
+
50
+ it "returns correct sunset local mean time" do
51
+ trueLong = BigDecimal.new("220.1966")
52
+ longHour = BigDecimal.new("-5.0523")
53
+ localHour = BigDecimal.new("5.6883")
54
+ t = BigDecimal.new("306.9605")
55
+ @calc.compute_local_mean_time(trueLong, longHour, t, localHour).should eql(BigDecimal.new("22.4675"))
56
+ end
57
+
58
+ it "returns correct civil sunset time" do
59
+ @calc.compute_utc_sunset(96).should eql(Time.gm(@date.year, @date.mon, @date.mday, 22, 28))
60
+ end
61
+
62
+ it "returns correct official sunset time" do
63
+ @calc.compute_utc_sunset(90.8333).should eql(Time.gm(@date.year, @date.mon, @date.mday, 21, 59))
64
+ end
65
+
66
+ it "returns correct nautical sunset time" do
67
+ @calc.compute_utc_sunset(102).should eql(Time.gm(@date.year, @date.mon, @date.mday, 23, 0))
68
+ end
69
+
70
+ it "returns correct astronomical sunset time" do
71
+ @calc.compute_utc_sunset(108).should eql(Time.gm(@date.year, @date.mon, @date.mday, 23, 31))
72
+ end
73
+ end
74
+
75
+ describe SolarEventCalculator, "test the math for areas where the sun doesn't set" do
76
+
77
+ it "returns correct time" do
78
+ date = Date.parse('2008-04-25') #25 April 2008
79
+ calc = SolarEventCalculator.new(date, BigDecimal.new("64.8378"), BigDecimal.new("-147.7164"))
80
+ calc.compute_utc_sunset(108).should eql(nil)
81
+ end
82
+
83
+ it "returns correct time" do
84
+ date = Date.parse('2008-04-25') #25 April 2008
85
+ calc = SolarEventCalculator.new(date, BigDecimal.new("64.8378"), BigDecimal.new("-147.7164"))
86
+ calc.compute_utc_sunset(102).should eql(nil)
87
+ end
88
+ end
89
+
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: RubySunrise
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Mike Reedell / LuckyCatLabs
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-08 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: mike@luckycatlabs.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files: []
28
+
29
+ files:
30
+ - rubysunrise.gemspec
31
+ - lib/sunrise.rb
32
+ has_rdoc: true
33
+ homepage: http://www.luckycatlabs.com
34
+ licenses: []
35
+
36
+ post_install_message:
37
+ rdoc_options: []
38
+
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ requirements: []
56
+
57
+ rubyforge_project:
58
+ rubygems_version: 1.3.6
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: Calculate the sunrise/sunset given lat/long coordinates and the date. Computes civil, official, nautical, and astronomical sunrise/sunset.
62
+ test_files:
63
+ - test/sunrisetest.rb
64
+ - test/sunsettest.rb