RubySunrise 0.1.0

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