File Coverage

blib/lib/Insolation.pm
Criterion Covered Total %
statement 12 347 3.4
branch 0 76 0.0
condition 0 12 0.0
subroutine 4 29 13.7
pod 0 23 0.0
total 16 487 3.2


line stmt bran cond sub pod time code
1             #
2             # Written by Travis Kent Beste
3             # Mon Sep 13 10:49:23 CDT 2010
4              
5             package Insolation;
6              
7 1     1   22201 use 5.008000;
  1         3  
  1         28  
8 1     1   5 use strict;
  1         2  
  1         60  
9 1     1   5 use warnings;
  1         5  
  1         24  
10              
11 1     1   389561 use Math::Trig;
  1         27408  
  1         13603  
12              
13             require Exporter;
14              
15             our @ISA = qw(Exporter);
16              
17             # Items to export into callers namespace by default. Note: do not export
18             # names by default without a very good reason. Use EXPORT_OK instead.
19             # Do not simply export all your public functions/methods/constants.
20              
21             # This allows declaration use Insolation ':all';
22             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
23             # will save memory.
24             our %EXPORT_TAGS = ( 'all' => [ qw(
25            
26             ) ] );
27              
28             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
29              
30             our @EXPORT = qw(
31            
32             );
33              
34             our $VERSION = '0.01';
35              
36             #----------------------------------------#
37             #
38             #----------------------------------------#
39             sub new {
40 0     0 0   my $class = shift;
41 0           my %args = @_;
42              
43 0           my %fields = (
44             longitude => 0,
45             latitude => 0,
46             month => 0,
47             _min_month => 0,
48             _max_month => 0,
49             year => 0,
50              
51             # calculated data, hold it here while they decide how to output it
52             data => {},
53              
54             # calculated variables
55             SCRATCH => {
56             ECCEN => 0, # orbital parameter
57             OBLIQ => 0, # orbital parameter
58             OMEGVP => 0, # orbital parameter
59             ITZONE => 0, # time zone
60             },
61              
62             # assign constants
63             CONSTANTS => {
64             PI => 4 * atan2(1, 1), # compute value of PI at run time - accurately
65             TWOPI => (2 * (4 * atan2(1, 1))), # compute value of PI at run time - accurately
66             RSUND => .267, # mean radius of Sun in degrees
67             REFRAC => .583, # effective Sun disk increase
68             EDAYZY => 365.2425, # actual length of a year - now
69             DAYzMO => [31,28,31, 30,31,30, 31,31,30, 31,30,31], # days in each month - we calculate leap year below
70             EARTH_WATT_M2 => 1367, # amount of energy that reaches the earth - watts/(m*m)
71             },
72             );
73              
74 0           my $self = {
75             %fields
76             };
77 0           bless $self, $class;
78              
79             #print Dumper $self;
80             #exit;
81              
82             #--------------------#
83             # validate input
84             #--------------------#
85              
86 0 0         if (defined($args{'Longitude'})) {
87 0           $self->set_longitude($args{Longitude});
88             }
89              
90 0 0         if (defined($args{'Latitude'})) {
91 0           $self->set_latitude($args{Latitude});
92             }
93              
94 0           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
95 0 0         if (defined($args{'Month'})) {
96 0           $self->set_month($args{Month});
97             } else {
98 0           $self->{month} = -1; # will end up computing for all months
99 0           $self->{_min_month} = 1;
100 0           $self->{_max_month} = 12;
101             }
102              
103 0 0         if (defined($args{'Year'})) {
104 0           $self->set_year($args{Year});
105             } else {
106 0           $self->{year} = $year + 1900; # set default year to this year
107             }
108              
109 0           return $self;
110             }
111              
112             #----------------------------------------#
113             #
114             #----------------------------------------#
115             sub DESTROY {
116 0     0     my $self = shift;
117              
118 0 0         $self->SUPER::DESTROY if $self->can("SUPER::DESTROY");
119             }
120              
121             # Preloaded methods go here.
122              
123             #----------------------------------------#
124             #
125             #----------------------------------------#
126             sub set_year {
127 0     0 0   my $self = shift;
128 0           my $year = shift;
129              
130 0           $self->{year} = sprintf("%d", $year);
131             }
132              
133             #----------------------------------------#
134             #
135             #----------------------------------------#
136             sub set_month {
137 0     0 0   my $self = shift;
138 0           my $month = shift;
139              
140 0 0         if ($self->isvalid_month($month)) {
141 0           $self->{'month'} = sprintf("%d", $month);
142 0           $self->{_min_month} = $self->{month};
143 0           $self->{_max_month} = $self->{month};
144             } else {
145 0           print "error with month\n";
146 0           exit(1);
147             }
148             }
149              
150             #----------------------------------------#
151             #
152             #----------------------------------------#
153             sub set_longitude {
154 0     0 0   my $self = shift;
155 0           my $longitude = shift;
156              
157 0 0         if ($self->isvalid_longitude) {
158 0 0         if ($self->isvalid_longitude($longitude)) {
159 0           $self->{longitude} = $longitude;
160             } else {
161 0           print "error with longitude\n";
162 0           exit(1);
163             }
164             }
165             }
166              
167             #----------------------------------------#
168             #
169             #----------------------------------------#
170             sub set_latitude {
171 0     0 0   my $self = shift;
172 0           my $latitude = shift;
173              
174 0 0         if ($self->isvalid_latitude($latitude)) {
175 0           $self->{latitude} = $latitude;
176             } else {
177 0           print "error with latitude\n";
178 0           exit(1);
179             }
180             }
181              
182             #----------------------------------------#
183             #
184             #----------------------------------------#
185             sub isvalid_year {
186 0     0 0   my $self = shift;
187 0           my $year = shift;
188              
189 0 0 0       if ( ($year >= 1) && ($year <= 9999) ) {
190 0           return 1;
191             }
192              
193 0           return 0;
194             }
195              
196             #----------------------------------------#
197             #
198             #----------------------------------------#
199             sub isvalid_month {
200 0     0 0   my $self = shift;
201 0           my $month = shift;
202              
203 0 0         if ($month eq "") {
204 0           return 0;
205             }
206              
207 0 0 0       if ( ($month >= 1) && ($month <= 12) ) {
208 0           return 1;
209             }
210              
211 0           return 0;
212             }
213              
214             #----------------------------------------#
215             #
216             #----------------------------------------#
217             sub isvalid_latitude {
218 0     0 0   my $self = shift;
219 0           my $latitude = shift;
220              
221             #print "$latitude\n";
222              
223 0 0         if (abs($latitude) > 90) {
224 0           return 0
225             }
226              
227 0           return 1;
228             }
229              
230             #----------------------------------------#
231             #
232             #----------------------------------------#
233             sub isvalid_longitude {
234 0     0 0   my $self = shift;
235 0           my $longitude = shift;
236              
237             #print "$lonitude\n";
238              
239 0 0         if ($self->{longitude} > 187.5) {
240 0           $self->{longitude} = $self->{longitude} - 360;
241             }
242 0 0         if ($self->{longitude} < -187.5) {
243 0           $self->{longitude} = $self->{longitude} + 360;
244             }
245 0 0         if (abs($self->{longitude}) > 187.5) {
246 0           return 0;
247             }
248              
249 0           return 1;
250             }
251              
252             #----------------------------------------#
253             # calculate the data for the months given
254             #----------------------------------------#
255             sub calculate_insolation {
256 0     0 0   my $self = shift;
257              
258 0           $self->{SCRATCH}->{ITZONE} = int($self->{longitude} / 15); # Determine time zone
259 0           $self->ORBPAR(); # Determine orbital parameters
260              
261 0           for (my $MONTH = $self->{_min_month}; $MONTH <= $self->{_max_month}; $MONTH++) {
262 0           my $DATMAX = $self->{CONSTANTS}->{DAYzMO}[($MONTH - 1)]; # array indexed at zero instead of 1
263 0 0 0       if ( ($MONTH == 2) and ($self->QLEAPY($self->{year})) ) {
264 0           $DATMAX = 29;
265             }
266 0           for (my $JDATE = 1; $JDATE <= $DATMAX; $JDATE++) {
267 0           my $DATE = $JDATE-1 + .5 - $self->{longitude}/360;
268 0           my $DAY = $self->YMDtoD($self->{year}, $MONTH, $DATE);
269             #print "DAY : $DAY\n";
270              
271 0           my ($SIND, $COSD, $SUNDIS, $SUNLON, $SUNLAT, $EQTIME) = $self->ORBIT($DAY);
272             #print "SIND : $SIND\n";
273             #print "COSD : $COSD\n";
274             #print "SUNDIS : $SUNDIS\n";
275             #print "SUNLON : $SUNLON\n";
276             #print "SUNLAT : $SUNLAT\n";
277             #print "EQTIME : $EQTIME\n";
278              
279 0           my ($COSZT, $COSZS) = $self->COSZIJ($SIND, $COSD);
280 0           my $RSmEzM = ($self->{CONSTANTS}->{REFRAC} + $self->{CONSTANTS}->{RSUND} / $SUNDIS) * $self->{CONSTANTS}->{TWOPI}/360;
281 0           my $DUSK = $self->SUNSET($SIND, $COSD, $RSmEzM);
282 0           my $SRINC = $self->{CONSTANTS}->{EARTH_WATT_M2} * $COSZT / $SUNDIS**2;
283              
284 0           my ($dawn, $dusk);
285 0           my $date = sprintf("%04d-%02d-%02d", $self->{year}, $MONTH, $JDATE);
286 0 0         if ($DUSK >= 999999) {
    0          
287             #--------------------#
288             # Daylight at all times at this location on this day
289             #--------------------#
290              
291 0           $dawn = 0;
292 0           $dusk = 0;
293              
294             } elsif ($DUSK <= -999999) {
295             #--------------------#
296             # Nightime at all times at this location on this day
297             #--------------------#
298              
299 0           $dawn = 0;
300 0           $dusk = 0;
301              
302             } else {
303             #--------------------#
304             # Daylight and nightime at this location on this day
305             #--------------------#
306              
307 0           my $DAWN = (-$DUSK-$EQTIME) * 24 / $self->{CONSTANTS}->{TWOPI} + 12 - $self->{longitude} / 15 + $self->{SCRATCH}->{ITZONE};
308 0           $DUSK = ( $DUSK-$EQTIME) * 24 / $self->{CONSTANTS}->{TWOPI} + 12 - $self->{longitude} / 15 + $self->{SCRATCH}->{ITZONE};
309 0           my $IDAWNH = int($DAWN);
310 0           my $IDUSKH = int($DUSK);
311 0           my $IDAWNM = int( ($DAWN - $IDAWNH) * 60);
312 0           my $IDUSKM = int( ($DUSK - $IDUSKH) * 60);
313 0           $dawn = sprintf("%02d:%02d", $IDAWNH, $IDAWNM);
314 0           $dusk = sprintf("%02d:%02d", $IDUSKH, $IDUSKM);
315              
316             }
317              
318             # set the data in the object's data collector - that way you can output it the way you'd like xml,csv,txt,etc
319 0           $self->{data}->{'day'}->{$date}->{'data'} = {
320             'dawn' => $dawn,
321             'dusk' => $dusk,
322             'srinc' => sprintf("%.45f", $SRINC),
323             'coszs' => sprintf("%.45f", $COSZS),
324             };
325             #printf "%10s - %5s - %5s - %.40f - %.40f\n", $date, $dawn, $dusk, $SRINC, $COSZS;
326             }
327             }
328             }
329              
330             #----------------------------------------#
331             # output in xml
332             #----------------------------------------#
333             sub get_xml {
334 0     0 0   my $self = shift;
335              
336             # only require if they use this method
337 0           my $package = 'XML::Simple';
338 0           eval {
339 0           (my $pkg = $package) =~ s|::|/|g; # require need a path
340 0           require "$pkg.pm";
341 0           import $package;
342             };
343 0 0         die $@ if( $@ );
344              
345 0           my $xs = XML::Simple->new(
346             AttrIndent => 1,
347             RootName => 'Insolation',
348             KeyAttr => [ 'date' => 'name'],
349             NoAttr => 1,
350             );
351              
352 0           my $xml = $xs->XMLout($self->{data});
353             #print "$xml";
354              
355 0           return $xml;
356             }
357              
358             #----------------------------------------#
359             # output in csv
360             #----------------------------------------#
361             sub get_csv {
362 0     0 0   my $self = shift;
363 0           my $csv;
364              
365 0           foreach my $date (sort keys %{$self->{data}->{day}}) {
  0            
366 0           my $dawn = $self->{data}->{day}->{$date}->{data}->{dawn};
367 0           my $dusk = $self->{data}->{day}->{$date}->{data}->{dusk};
368 0           my $srinc = $self->{data}->{day}->{$date}->{data}->{srinc};
369 0           my $coszs = $self->{data}->{day}->{$date}->{data}->{coszs};
370 0           $csv .= sprintf("%s,%s,%s,%5.2f,%.4f\n", $date, $dawn, $dusk, $srinc, $coszs);
371             }
372              
373 0           return $csv;
374             }
375              
376             #----------------------------------------#
377             # get the insolation for a computed value
378             #----------------------------------------#
379             sub get_ym_insolation {
380 0     0 0   my $self = shift;
381 0           my $ym = shift;
382 0           my $year = substr($ym, 0, 4);
383 0           my $month = substr($ym, 5, 2);
384 0           my $total = 0;
385              
386 0 0         if (! defined($self->{data}->{day}->{$ym . '-01'}->{data}->{srinc})) {
387 0           print "front of the month doesn't exist...\n";
388 0           return 0;
389             } else {
390             #print "front of the month looks good...\n";
391             }
392              
393 0           my $DATMAX = $self->{CONSTANTS}->{DAYzMO}[($month - 1)]; # array indexed at zero instead of 1
394 0 0 0       if ( ($month == 2) and ($self->QLEAPY($year)) ) {
395 0           $DATMAX = 29;
396             }
397 0 0         if (! defined($self->{data}->{day}->{$ym . '-' . $DATMAX}->{data}->{srinc})) {
398 0           print "end of the month doesn't exist...\n";
399 0           return 0;
400             } else {
401             #print "end of the month looks good...\n";
402             }
403              
404             # compute the month total
405 0           for(my $d = 1; $d <= $DATMAX; $d++) {
406 0           $total += $self->{data}->{day}->{$ym . sprintf("-%02d", $d)}->{data}->{srinc};
407             }
408              
409 0           return $total;
410             }
411              
412             #----------------------------------------#
413             # get the insolation for a computed value
414             #----------------------------------------#
415             sub get_ymd_insolation {
416 0     0 0   my $self = shift;
417 0           my $ymd = shift;
418              
419 0 0         if (! defined($self->{data}->{day}->{$ymd}->{data}->{srinc})) {
420 0           return -1;
421             }
422              
423 0           return $self->{data}->{day}->{$ymd}->{data}->{srinc};
424             }
425              
426             #----------------------------------------#
427             # ORBPAR calculates the three orbital parameters as a function of
428             # YEAR. The source of these calculations is: Andre L. Berger,
429             # 1978, "Long-Term Variations of Daily Insolation and Quaternary
430             # Climatic Changes", JAS, v.35, p.2362. Also useful is: Andre L.
431             # Berger, May 1978, "A Simple Algorithm to Compute Long Term
432             # Variations of Daily Insolation", published by Institut
433             # D'Astronomie de Geophysique, Universite Catholique de Louvain,
434             # Louvain-la Neuve, No. 18.
435             #
436             # Tables and equations refer to the first reference (JAS). The
437             # corresponding table or equation in the second reference is
438             # enclosed in parentheses. The coefficients used in this
439             # subroutine are slightly more precise than those used in either
440             # of the references. The generated orbital parameters are precise
441             # within plus or minus 1000000 years from present.
442             #
443             # Input: YEAR = years A.D. are positive, B.C. are negative
444             # Output: ECCEN = ECCENtricity of orbital ellipse
445             # OBLIQ = latitude of Tropic of Cancer in radians
446             # OMEGVP = longitude of perihelion =
447             # = spatial angle from vernal equinox to perihelion
448             # in radians with sun as angle vertex
449             #----------------------------------------#
450             sub ORBPAR {
451 0     0 0   my $self = shift;
452              
453             # Table 1 (2). Obliquity relative to mean ecliptic of date: OBLIQD
454 0           my @table1 = (-2462.2214466, 31.609974, 251.9025,
455             -857.3232075, 32.620504, 280.8325,
456             -629.3231835, 24.172203, 128.3057,
457             -414.2804924, 31.983787, 292.7252,
458             -311.7632587, 44.828336, 15.3747,
459             308.9408604, 30.973257, 263.7951,
460             -162.5533601, 43.668246, 308.4258,
461             -116.1077911, 32.246691, 240.0099,
462             101.1189923, 30.599444, 222.9725,
463             -67.6856209, 42.681324, 268.7809,
464             24.9079067, 43.836462, 316.7998,
465             22.5811241, 47.439436, 319.6024,
466             -21.1648355, 63.219948, 143.8050,
467             -15.6549876, 64.230478, 172.7351,
468             15.3936813, 1.010530, 28.9300,
469             14.6660938, 7.437771, 123.5968,
470             -11.7273029, 55.782177, 20.2082,
471             10.2742696, .373813, 40.8226,
472             6.4914588, 13.218362, 123.4722,
473             5.8539148, 62.583231, 155.6977,
474             -5.4872205, 63.593761, 184.6277,
475             -5.4290191, 76.438310, 267.2772,
476             5.1609570, 45.815258, 55.0196,
477             5.0786314, 8.448301, 152.5268,
478             -4.0735782, 56.792707, 49.1382,
479             3.7227167, 49.747842, 204.6609,
480             3.3971932, 12.058272, 56.5233,
481             -2.8347004, 75.278220, 200.3284,
482             -2.6550721, 65.241008, 201.6651,
483             -2.5717867, 64.604291, 213.5577,
484             -2.4712188, 1.647247, 17.0374,
485             2.4625410, 7.811584, 164.4194,
486             2.2464112, 12.207832, 94.5422,
487             -2.0755511, 63.856665, 131.9124,
488             -1.9713669, 56.155990, 61.0309,
489             -1.8813061, 77.448840, 296.2073,
490             -1.8468785, 6.801054, 135.4894,
491             1.8186742, 62.209418, 114.8750,
492             1.7601888, 20.656133, 247.0691,
493             -1.5428851, 48.344406, 256.6114,
494             1.4738838, 55.145460, 32.1008,
495             -1.4593669, 69.000539, 143.6804,
496             1.4192259, 11.071350, 16.8784,
497             -1.1818980, 74.291298, 160.6835,
498             1.1756474, 11.047742, 27.5932,
499             -1.1316126, 0.636717, 348.1074,
500             1.0896928, 12.844549, 82.6496
501             );
502             #print Dumper \@table1;
503              
504             # Table 4 (1). Fundamental elements of the ecliptic: ECCEN sin(pi)
505 0           my @table4 = ( .01860798, 4.207205, 28.620089,
506             .01627522, 7.346091, 193.788772,
507             -.01300660, 17.857263, 308.307024,
508             .00988829, 17.220546, 320.199637,
509             -.00336700, 16.846733, 279.376984,
510             .00333077, 5.199079, 87.195000,
511             -.00235400, 18.231076, 349.129677,
512             .00140015, 26.216758, 128.443387,
513             .00100700, 6.359169, 154.143880,
514             .00085700, 16.210016, 291.269597,
515             .00064990, 3.065181, 114.860583,
516             .00059900, 16.583829, 332.092251,
517             .00037800, 18.493980, 296.414411,
518             -.00033700, 6.190953, 145.769910,
519             .00027600, 18.867793, 337.237063,
520             .00018200, 17.425567, 152.092288,
521             -.00017400, 6.186001, 126.839891,
522             -.00012400, 18.417441, 210.667199,
523             .00001250, 0.667863, 72.108838
524             );
525             #print Dumper \@table4;
526              
527             # Table 5 (3). General precession in longitude: psi
528 0           my @table5 = ( 7391.0225890, 31.609974, 251.9025,
529             2555.1526947, 32.620504, 280.8325,
530             2022.7629188, 24.172203, 128.3057,
531             -1973.6517951, 0.636717, 348.1074,
532             1240.2321818, 31.983787, 292.7252,
533             953.8679112, 3.138886, 165.1686,
534             -931.7537108, 30.973257, 263.7951,
535             872.3795383, 44.828336, 15.3747,
536             606.3544732, 0.991874, 58.5749,
537             -496.0274038, 0.373813, 40.8226,
538             456.9608039, 43.668246, 308.4258,
539             346.9462320, 32.246691, 240.0099,
540             -305.8412902, 30.599444, 222.9725,
541             249.6173246, 2.147012, 106.5937,
542             -199.1027200, 10.511172, 114.5182,
543             191.0560889, 42.681324, 268.7809,
544             -175.2936572, 13.650058, 279.6869,
545             165.9068833, 0.986922, 39.6448,
546             161.1285917, 9.874455, 126.4108,
547             139.7878093, 13.013341, 291.5795,
548             -133.5228399, 0.262904, 307.2848,
549             117.0673811, 0.004952, 18.9300,
550             104.6907281, 1.142024, 273.7596,
551             95.3227476, 63.219948, 143.8050,
552             86.7824524, 0.205021, 191.8927,
553             86.0857729, 2.151964, 125.5237,
554             70.5893698, 64.230478, 172.7351,
555             -69.9719343, 43.836462, 316.7998,
556             -62.5817473, 47.439436, 319.6024,
557             61.5450059, 1.384343, 69.7526,
558             -57.9364011, 7.437771, 123.5968,
559             57.1899832, 18.829299, 217.6432,
560             -57.0236109, 9.500642, 85.5882,
561             -54.2119253, 0.431696, 156.2147,
562             53.2834147, 1.160090, 66.9489,
563             52.1223575, 55.782177, 20.2082,
564             -49.0059908, 12.639528, 250.7568,
565             -48.3118757, 1.155138, 48.0188,
566             -45.4191685, 0.168216, 8.3739,
567             -42.2357920, 1.647247, 17.0374,
568             -34.7971099, 10.884985, 155.3409,
569             34.4623613, 5.610937, 94.1709,
570             -33.8356643, 12.658184, 221.1120,
571             33.6689362, 1.010530, 28.9300,
572             -31.2521586, 1.983748, 117.1498,
573             -30.8798701, 14.023871, 320.5095,
574             28.4640769, 0.560178, 262.3602,
575             -27.1960802, 1.273434, 336.2148,
576             27.0860736, 12.021467, 233.0046,
577             -26.3437456, 62.583231, 155.6977,
578             24.7253740, 63.593761, 184.6277,
579             24.6732126, 76.438310, 267.2772,
580             24.4272733, 4.280910, 78.9281,
581             24.0127327, 13.218362, 123.4722,
582             21.7150294, 17.818769, 188.7132,
583             -21.5375347, 8.359495, 180.1364,
584             18.1148363, 56.792707, 49.1382,
585             -16.9603104, 8.448301, 152.5268,
586             -16.1765215, 1.978796, 98.2198,
587             15.5567653, 8.863925, 97.4808,
588             15.4846529, 0.186365, 221.5376,
589             15.2150632, 8.996212, 168.2438,
590             14.5047426, 6.771027, 161.1199,
591             -14.3873316, 45.815258, 55.0196,
592             13.1351419, 12.002811, 262.6495,
593             12.8776311, 75.278220, 200.3284,
594             11.9867234, 65.241008, 201.6651,
595             11.9385578, 18.870667, 294.6547,
596             11.7030822, 22.009553, 99.8233,
597             11.6018181, 64.604291, 213.5577,
598             -11.2617293, 11.498094, 154.1631,
599             -10.4664199, 0.578834, 232.7153,
600             10.4333970, 9.237738, 138.3034,
601             -10.2377466, 49.747842, 204.6609,
602             10.1934446, 2.147012, 106.5938,
603             -10.1280191, 1.196895, 250.4676,
604             10.0289441, 2.133898, 332.3345,
605             -10.0034259, 0.173168, 27.3039
606             );
607              
608 0           my $YM1950 = $self->{year} - 1950;
609             #print "YM1950 : $YM1950\n";
610              
611 0           my $PIz180 = $self->{CONSTANTS}->{TWOPI} / 360;
612             #print "PIz180 : $PIz180\n";
613              
614             #--------------------#
615             # Obliquity from Table 1 (2):
616             #--------------------#
617             # OBLIQ = 23.320556 (degrees) Equation 5.5 (15)
618             # OBLIQD = OBLIQ + sum[A cos(ft+delta)] Equation 1 (5)
619 0           my $sumc = 0;
620 0           for (my $i = 0; $i < 47; $i++) {
621             #printf "%2d => 1=%18.8f 2=%18.8f 3=%18.8f\n", $i, $table1[(($i*3)+0)], $table1[(($i*3)+1)], $table1[(($i*3)+2)];
622 0           my $arg = $PIz180 * ($YM1950 * $table1[(($i*3)+1)] / 3600 + $table1[(($i*3)+2)]);
623 0           $sumc = $sumc + $table1[(($i*3)+0)] * cos($arg);
624             #print "$arg - $sumc\n";
625             }
626 0           my $OBLIQD = 23.320556 + $sumc/3600;
627             #print "OBLIQD : $OBLIQD\n";
628 0           $self->{SCRATCH}->{OBLIQ} = $OBLIQD * $PIz180;
629             #print "OBLIQ : " . $self->{SCRATCH}->{OBLIQ} . "\n";
630              
631             #--------------------#
632             # Eccentricity from Table 4 (1):
633             #--------------------#
634             # ECCEN sin(pi) = sum[M sin(gt+beta)] Equation 4 (1)
635             # ECCEN cos(pi) = sum[M cos(gt+beta)] Equation 4 (1)
636             # ECCEN = ECCEN sqrt[sin(pi)^2 + cos(pi)^2]
637 0           my $ESINPI = 0;
638 0           my $ECOSPI = 0;
639 0           for (my $i = 0; $i < 19; $i++) {
640             #printf "%2d => 1=%18.8f 2=%18.8f 3=%18.8f\n", $i, $table4[(($i*3)+0)], $table4[(($i*3)+1)], $table4[(($i*3)+2)];
641 0           my $arg = $PIz180 * ($YM1950 * $table4[(($i*3)+1)] / 3600 + $table4[(($i*3)+2)]);
642 0           $ESINPI = $ESINPI + $table4[(($i*3)+0)] * sin($arg);
643 0           $ECOSPI = $ECOSPI + $table4[(($i*3)+0)] * cos($arg);
644             #print "ESINPI : $ESINPI\n";
645             #print "ECOSPI : $ECOSPI\n";
646             }
647 0           $self->{SCRATCH}->{ECCEN} = sqrt(($ESINPI * $ESINPI) + ($ECOSPI * $ECOSPI));
648             #print "ECCEN : " . $self->{SCRATCH}->{ECCEN} . "\n";
649              
650             #--------------------#
651             # Perihelion from Equation 4,6,7 (9) and Table 4,5 (1,3):
652             #--------------------#
653             # PSI# = 50.439273 (seconds of degree) Equation 7.5 (16)
654             # ZETA = 3.392506 (degrees) Equation 7.5 (17)
655             # PSI = PSI# t + ZETA + sum[F sin(ft+delta)] Equation 7 (9)
656             # PIE = atan[ECCEN sin(pi) / ECCEN cos(pi)]
657             # OMEGVP = PIE + PSI + 3.14159 Equation 6 (4.5)
658              
659 0           my $PIE = atan2($ESINPI, $ECOSPI);
660             #print "PIE : $PIE\n";
661 0           my $FSINFD = 0;
662 0           for(my $i = 0; $i < 78; $i++) {
663             #printf "%2d => 1=%18.8f 2=%18.8f 3=%18.8f\n", $i, $table5[(($i*3)+0)], $table5[(($i*3)+1)], $table5[(($i*3)+2)];
664 0           my $arg = $PIz180 * ($YM1950 * $table5[(($i*3)+1)] / 3600 + $table5[(($i*3)+2)]);
665 0           $FSINFD = $FSINFD + $table5[(($i*3)+0)] * sin($arg);
666             }
667             #print "FSINFD : $FSINFD\n";
668 0           my $PSI = $PIz180 * (3.392506 + ($YM1950 * 50.439273 + $FSINFD) / 3600);
669             #print "PSI : $PSI\n";
670 0           my $a = ($PIE + $PSI + .5 * $self->{CONSTANTS}->{TWOPI}) ;
671 0           my $b = $self->{CONSTANTS}->{TWOPI};
672 0           my $c = $a / $b;
673 0           my $d = modulo($a, $b);
674             #printf "%25.20f\n", $a;
675             #printf "%25.20f\n", $b;
676             #printf "%25.20f\n", $c;
677             #printf "%25.20f\n", $d;
678 0           $self->{SCRATCH}->{OMEGVP} = modulo(($PIE + $PSI + .5 * $self->{CONSTANTS}->{TWOPI}), $self->{CONSTANTS}->{TWOPI});
679             #print "OMEGVP : " . $self->{SCRATCH}->{OMEGVP} . "\n";
680             }
681              
682             #----------------------------------------#
683             # ORBIT receives orbital parameters and time of year, and returns
684             # distance from Sun and Sun's position.
685             # Reference for following caculations is: V.M.Blanco and
686             # S.W.McCuskey, 1961, "Basic Physics of the Solar System", pages
687             # 135 - 151. Existence of Moon and heavenly bodies other than
688             # Earth and Sun are ignored. Earth is assumed to be spherical.
689             #
690             # Program author: Gary L. Russell 2002/09/25
691             # Angles, longitude and latitude are measured in radians.
692             #
693             # Input: ECCEN = ECCENtricity of the orbital ellipse
694             # OBLIQ = latitude of Tropic of Cancer
695             # OMEGVP = longitude of perihelion (sometimes Pi is added) =
696             # = spatial angle from vernal equinox to perihelion
697             # with Sun as angle vertex
698             # DAY = days measured since 2000 January 1, hour 0
699             #
700             # Constants: EDAYzY = tropical year = Earth days per year = 365.2425
701             # VE200D = days from 2000 January 1, hour 0 till vernal
702             # equinox of year 2000 = 31 + 29 + 19 + 7.5/24
703             #
704             # Intermediate quantities:
705             # BSEMI = semi minor axis in units of semi major axis
706             # PERIHE = perihelion in days since 2000 January 1, hour 0
707             # in its annual revolution about Sun
708             # TA = true anomaly = spatial angle from perihelion to
709             # current location with Sun as angle vertex
710             # EA = ECCENtric anomaly = spatial angle measured along
711             # ECCENtric circle (that circumscribes Earth's orbit)
712             # from perihelion to point above (or below) Earth's
713             # absisca (where absisca is directed from center of
714             # ECCENtric circle to perihelion)
715             # MA = mean anomaly = temporal angle from perihelion to
716             # current time in units of 2*Pi per tropical year
717             # TAofVE = TA(VE) = true anomaly of vernal equinox = - OMEGVP
718             # EAofVE = EA(VE) = ECCENtric anomaly of vernal equinox
719             # MAofVE = MA(VE) = mean anomaly of vernal equinox
720             # SLNORO = longitude of Sun in Earth's nonrotating reference frame
721             # VEQLON = longitude of Greenwich Meridion in Earth's nonrotating
722             # reference frame at vernal equinox
723             # ROTATE = change in longitude in Earth's nonrotating reference
724             # frame from point's location on vernal equinox to its
725             # current location where point is fixed on rotating Earth
726             # SLMEAN = longitude of fictitious mean Sun in Earth's rotating
727             # reference frame (normal longitude and latitude)
728             #
729             # Output: DIST = distance to Sun in units of semi major axis
730             # SIND = sine of declination angle = sin(SUNLAT)
731             # COSD = cosine of the declination angle = cos(SUNLAT)
732             # SUNLON = longitude of point on Earth directly beneath Sun
733             # SUNLAT = latitude of point on Earth directly beneath Sun
734             # EQTIME = Equation of Time = longitude of fictitious mean Sun minus SUNLON
735             #
736             # From the above reference:
737             # (4-54): [1 - ECCEN*cos(EA)]*[1 + ECCEN*cos(TA)] = (1 - ECCEN^2)
738             # (4-55): tan(TA/2) = sqrt[(1+ECCEN)/(1-ECCEN)]*tan(EA/2)
739             # Yield: tan(EA) = sin(TA)*sqrt(1-ECCEN^2) / [cos(TA) + ECCEN]
740             # or: tan(TA) = sin(EA)*sqrt(1-ECCEN^2) / [cos(EA) - ECCEN]
741             #
742             # Use C540, Only: EDAYzY,VE200D,CONSTANTS}->{TWOPI
743             # Implicit Real*8 (A-H,M-Z)
744             #----------------------------------------#
745             sub ORBIT {
746 0     0 0   my $self = shift;
747 0           my $DAY = shift;
748              
749 0           my $EDAYzY = 365.2425;
750 0           my $VE200D = 79.3125;
751              
752             # Determine EAofVE from geometry: tan(EA) = b*sin(TA) / [e+cos(TA)]
753             # Determine MAofVE from Kepler's equation: MA = EA - e*sin(EA)
754             # Determine MA knowing time from vernal equinox to current day
755              
756             #printf "DAY : %.10f\n", $DAY;
757             #printf "ECCEN : %.10f\n", $self->{SCRATCH}->{ECCEN};
758             #printf "OMEGVP : %.10f\n", $self->{SCRATCH}->{OMEGVP};
759              
760 0           my $BSEMI = sqrt(1 - ($self->{SCRATCH}->{ECCEN} * $self->{SCRATCH}->{ECCEN}));
761             #printf "BSEMI : %.10f\n", $BSEMI;
762              
763 0           my $TAofVE = -($self->{SCRATCH}->{OMEGVP});
764             #printf "TAofVE : %.10f\n", $TAofVE;
765              
766 0           my $EAofVE = atan2( ($BSEMI * sin($TAofVE)), ($self->{SCRATCH}->{ECCEN} + cos($TAofVE)) );
767             #printf "EAofVE : %.10f\n", $EAofVE;
768              
769 0           my $MAofVE = $EAofVE - $self->{SCRATCH}->{ECCEN} * sin($EAofVE);
770             #printf "MAofVE : %.10f\n", $MAofVE;
771              
772 0           my $MA = modulo(($self->{CONSTANTS}->{TWOPI} * ($DAY - $VE200D) / $EDAYzY + $MAofVE), $self->{CONSTANTS}->{TWOPI});
773             #printf "MA : $MA\n";
774              
775             # Numerically invert Kepler's equation: MA = EA - e*sin(EA)
776 0           my $dEA = 1;
777 0           my $i = 0;
778 0           my $EA = $MA + ($self->{SCRATCH}->{ECCEN} * ( sin($MA)) + ($self->{SCRATCH}->{ECCEN} * sin( 2 * $MA ) / 2) );
779 0           while (abs($dEA) > 0.0000000001){
780 0           $dEA = ($MA - $EA + $self->{SCRATCH}->{ECCEN} * sin($EA)) / (1 - $self->{SCRATCH}->{ECCEN} * cos($EA));
781 0           $EA += $dEA;
782             #printf "computing $i times ($EA, $dEA - %25.20f)\n", (abs($dEA));
783 0           $i++;
784             }
785              
786             #
787             # Calculate distance to Sun and true anomaly
788             #
789 0           my $SUNDIS = 1 - $self->{SCRATCH}->{ECCEN} * cos($EA);
790 0           my $TA = atan2( ($BSEMI * sin($EA)), (cos($EA) - $self->{SCRATCH}->{ECCEN}));
791              
792             #
793             # Change reference frame to be nonrotating reference frame, angles
794             # fixed according to stars, with Earth at center and positive x
795             # axis be ray from Earth to Sun were Earth at vernal equinox, and
796             # x-y plane be Earth's equatorial plane. Distance from current Sun
797             # to this x axis is SUNDIS sin(TA-TAofVE). At vernal equinox, Sun
798             # is located at (SUNDIS,0,0). At other times, Sun is located at:
799             #
800             # SUN = (SUNDIS cos(TA-TAofVE),
801             # SUNDIS sin(TA-TAofVE) cos(OBLIQ),
802             # SUNDIS sin(TA-TAofVE) sin(OBLIQ))
803             #
804 0           my $SIND = sin($TA - $TAofVE) * sin($self->{SCRATCH}->{OBLIQ});
805 0           my $COSD = sqrt(1 - ($SIND * $SIND));
806 0           my $SUNX = cos($TA - $TAofVE);
807 0           my $SUNY = sin($TA - $TAofVE) * cos($self->{SCRATCH}->{OBLIQ});
808 0           my $SLNORO = atan2($SUNY, $SUNX);
809              
810             #
811             # Determine Sun location in Earth's rotating reference frame
812             # (normal longitude and latitude)
813             #
814 0           my $VEQLON = $self->{CONSTANTS}->{TWOPI} * $VE200D - $self->{CONSTANTS}->{TWOPI} / 2 + $MAofVE - $TAofVE; # modulo 2*Pi
815 0           my $ROTATE = $self->{CONSTANTS}->{TWOPI} * ($DAY - $VE200D) * ($EDAYzY + 1) / $EDAYzY;
816 0           my $SUNLON = modulo(($SLNORO - $ROTATE - $VEQLON), $self->{CONSTANTS}->{TWOPI});
817 0 0         if ($SUNLON > ($self->{CONSTANTS}->{TWOPI} / 2)) {
818 0           $SUNLON = $SUNLON - $self->{CONSTANTS}->{TWOPI};
819             }
820 0           my $SUNLAT = asin(sin($TA - $TAofVE) * sin($self->{SCRATCH}->{OBLIQ}));
821              
822             #
823             # Determine longitude of fictitious mean Sun
824             # Calculate Equation of Time
825             #
826 0           my $SLMEAN = $self->{CONSTANTS}->{TWOPI} / 2 - $self->{CONSTANTS}->{TWOPI} * ($DAY - int($DAY));
827 0           my $EQTIME = modulo($SLMEAN - $SUNLON, $self->{CONSTANTS}->{TWOPI});
828 0 0         if ($EQTIME > $self->{CONSTANTS}->{TWOPI}/2) {
829 0           $EQTIME = $EQTIME - $self->{CONSTANTS}->{TWOPI};
830             }
831              
832 0           return ($SIND, $COSD, $SUNDIS, $SUNLON, $SUNLAT, $EQTIME);
833             }
834              
835             #----------------------------------------#
836             # SUNSET
837             # Input: RLAT = latitude (degrees)
838             # SIND,COSD = sine and cosine of the declination angle
839             # RSmEzM = (Sun Radius - Earth Radius) / (distance to Sun)
840             #
841             # Output: DUSK = time of DUSK (temporal radians) at mean local time
842             #
843             #----------------------------------------#
844             sub SUNSET {
845 0     0 0   my $self = shift;
846 0           my $RLAT = $self->{latitude};;
847 0           my $SIND = shift;
848 0           my $COSD = shift;
849 0           my $RSmEzM = shift;
850              
851 0           my $DUSK = 0;
852              
853 0           my $SINJ = sin( $self->{CONSTANTS}->{TWOPI} * $self->{latitude} / 360);
854 0           my $COSJ = cos( $self->{CONSTANTS}->{TWOPI} * $self->{latitude} / 360);
855 0           my $SJSD = $SINJ * $SIND;
856 0           my $CJCD = $COSJ * $COSD;
857              
858             # Constant nightime at this latitude
859 0 0         if (($SJSD + $RSmEzM + $CJCD) <= 0) {
860 0           $DUSK = -999999;
861 0           return $DUSK;
862             }
863              
864             # Constant daylight at this latitude
865 0 0         if (($SJSD + $RSmEzM - $CJCD) >= 0) {
866 0           $DUSK = 999999;
867 0           return $DUSK;
868             }
869              
870             # Compute DUSK (at local time)
871 0           my $CDUSK = -($SJSD + $RSmEzM) / $CJCD; # cosine of DUSK
872 0           $DUSK = acos($CDUSK);
873              
874 0           return $DUSK;
875             }
876              
877             #----------------------------------------#
878             # For a given JYEAR (A.D.), JMONTH and DATE (between 0 and 31),
879             # calculate number of DAYs measured from 2000 January 1, hour 0.
880             #----------------------------------------#
881             sub YMDtoD {
882 0     0 0   my $self = shift;
883 0           my $JYEAR = shift;
884 0           my $JMONTH = shift;
885 0           my $DATE = shift;
886              
887             #print "year : $JYEAR\n";
888             #print "month : $JMONTH\n";
889             #print "date : $DATE\n";
890              
891 0           my $JDAY4C = 365 * 400 + 97; # number of days in 4 centuries
892 0           my $JDAY1C = 365 * 100 + 24; # number of days in 1 century
893 0           my $JDAY4Y = 365 * 4 + 1; # number of days in 4 years
894 0           my $JDAY1Y = 365; # number of days in 1 year
895              
896 0           my @JDSUMN = (0,31,59, 90,120,151, 181,212,243, 273,304,334);
897 0           my @JDSUML = (0,31,60, 91,121,152, 182,213,244, 274,305,335);
898              
899 0           my $N4CENT = int( ($JYEAR-2000) / 400);
900 0           my $IYR4C = $JYEAR - 2000 - ($N4CENT * 400);
901 0           my $N1CENT = int($IYR4C / 100);
902 0           my $IYR1C = $IYR4C - $N1CENT * 100;
903 0           my $N4YEAR = int($IYR1C / 4);
904 0           my $IYR4Y = $IYR1C - $N4YEAR * 4;
905 0           my $N1YEAR = $IYR4Y;
906 0           my $DAY = $N4CENT * $JDAY4C;
907              
908             #print "N4CENT : $N4CENT\n";
909             #print "IYR4C : $IYR4C\n";
910             #print "N1CENT : $N1CENT\n";
911             #print "IYR1C : $IYR1C\n";
912             #print "N4YEAR : $N4YEAR\n";
913             #print "IYR4Y : $IYR4Y\n";
914             #print "N1YEAR : $N1YEAR\n";
915             #print "DAY : $DAY\n";
916              
917 0 0         if ($N1CENT > 0) {
918 0           $DAY = $DAY + $JDAY1C + 1 + ($N1CENT - 1) * $JDAY1C;
919             #print "1. $DAY\n";
920 0 0         if ($N4YEAR > 0) {
921             #print "2. $DAY\n";
922 0           $DAY = $DAY + $JDAY4Y-1 + ($N4YEAR - 1) * $JDAY4Y;
923 0 0         if ($N1YEAR > 0) {
924             #print "3. $DAY\n";
925 0           $DAY = $DAY + $JDAY1Y+1 + ($N1YEAR - 1) * $JDAY1Y;
926             #print "4. $DAY\n";
927 0           $DAY = $DAY + $JDSUMN[($JMONTH - 1)] + $DATE;
928             #print "5. $DAY\n";
929 0           return $DAY;
930             } else {
931 0           $DAY = $DAY + $JDSUML[($JMONTH - 1)] + $DATE;
932             #print "6. $DAY\n";
933 0           return $DAY;
934             }
935             } else {
936             #print "day : -> $DAY\n";
937             #print "month : -> $JMONTH\n";
938             #print "date : -> $DATE\n";
939             #print "index : -> " . $JDSUML[$JMONTH] . "\n";
940 0           $DAY = $DAY + $JDSUML[($JMONTH - 1)] + $DATE;
941             #print "7. - $DAY\n";
942 0           return $DAY;
943             }
944             } else {
945              
946 0           $DAY = $DAY + ($N4YEAR * $JDAY4Y);
947             #print "8. $DAY\n";
948              
949 0 0         if ($N1YEAR > 0) {
950 0           $DAY = $DAY + ($JDAY1Y + 1) + (($N1YEAR - 1) * $JDAY1Y);
951             #print "9. $DAY\n";
952              
953             #print "JMONTH " . $JMONTH . "\n";
954             #print "JDSUMN " . $JDSUMN[($JMONTH - 1)] . "\n";
955              
956 0           $DAY = $DAY + $JDSUMN[($JMONTH - 1)] + $DATE;
957             #print "10. $DAY\n";
958              
959 0           return $DAY;
960             } else {
961 0           $DAY = $DAY + $JDSUML[($JMONTH - 1)] + $DATE;
962             #print "11. $DAY\n";
963 0           return $DAY;
964             }
965             }
966             }
967              
968             #----------------------------------------#
969             # For a given DAY measured from 2000 January 1, hour 0, determine
970             # the JYEAR (A.D.), JMONTH and DATE (between 0 and 31).
971             #----------------------------------------#
972             sub DtoYMD {
973 0     0 0   my $self = shift;
974 0           my $DAY = shift;
975              
976 0           my $JDAY4C = 365*400 + 97; # number of days in 4 centuries
977 0           my $JDAY1C = 365*100 + 24; # number of days in 1 century
978 0           my $JDAY4Y = 365* 4 + 1, # number of days in 4 years
979             my $JDAY1Y = 365; # number of days in 1 year
980              
981 0           my @JDSUMN = (0,31,59, 90,120,151, 181,212,243, 273,304,334);
982 0           my @JDSUML = (0,31,60, 91,121,152, 182,213,244, 274,305,335);
983            
984             #my $N4CENT = int($DAY / $JDAY4C);
985             #my $DAY4C = $DAY - $N4CENT * $JDAY4C;
986             #my $N1CENT = ($DAY4C - 1) / $JDAY1C;
987              
988             # Second to fourth of every fourth century: 21??, 22??, 23??, etc.
989             #if ($N1CENT > 0) {
990             # $DAY1C = $DAY4C - $N1CENT * $JDAY1C - 1;
991             # $N4YEAR = ($DAY1C + 1) / $JDAY4Y;
992              
993             # if ($N4YEAR > 0) {
994             # # Subsequent four years of every second to fourth century when
995             # # there is a leap year: 2104-2107, 2108-2111 ... 2204-2207, etc.
996             # $DAY4Y = $DAY1C - $N4YEAR * $JDAY4Y + 1;
997             # $N1YEAR = ($DAY4Y - 1) / $JDAY1Y;
998             # if ($N1YEAR > 0) {
999             # # Current year is not a leap frog year
1000             # $DAY1Y = $DAY4Y - $N1YEAR * $JDAY1Y - 1;
1001             # }
1002             #}
1003              
1004             # First of every fourth century: 16??, 20??, 24??, etc.
1005             #$DAY1C = $DAY4C;
1006             #$N4YEAR = $DAY1C / JDAY4Y;
1007             #$DAY4Y = $DAY1C - $N4YEAR * $JDAY4Y;
1008             #$N1YEAR = ($DAY4Y - 1) / $JDAY1Y;
1009             #if (N1YEAR > 0) {
1010             # # Current year is not a leap frog year
1011             # $DAY1Y = $DAY4Y - $N1YEAR * $JDAY1Y - 1;
1012             #}
1013              
1014             # GoTo 100
1015              
1016             # First four years of every second to fourth century when there is
1017             # no leap year: 2100-2103, 2200-2203, 2300-2303, etc.
1018             # DAY4Y = DAY1C
1019             # N1YEAR = DAY4Y/JDAY1Y
1020             # DAY1Y = DAY4Y - N1YEAR*JDAY1Y
1021             # GoTo 210
1022              
1023             #
1024             # Current year is a leap frog year
1025             #
1026             #100 DAY1Y = DAY4Y
1027             # Do 120 M=1,11
1028             #120 If (DAY1Y < JDSUML(M+1)) GoTo 130
1029             #C M=12
1030             #130 JYEAR = 2000 + N4CENT*400 + N1CENT*100 + N4YEAR*4 + N1YEAR
1031             # JMONTH = M
1032             # DATE = DAY1Y - JDSUML(M)
1033             # Return
1034              
1035             #
1036             # Current year is not a leap frog year
1037             #
1038             #210 Do 220 M=1,11
1039             #220 If (DAY1Y < JDSUMN(M+1)) GoTo 230
1040             #C M=12
1041             #230 JYEAR = 2000 + N4CENT*400 + N1CENT*100 + N4YEAR*4 + N1YEAR
1042             # JMONTH = M
1043             # DATE = DAY1Y - JDSUMN(M)
1044             # Return
1045             # End
1046             }
1047              
1048             #----------------------------------------#
1049             # For a given year, VERNAL calculates an approximate time of vernal
1050             # equinox in days measured from 2000 January 1, hour 0.
1051              
1052             # VERNAL assumes that vernal equinoxes from one year to the next
1053             # are separated by exactly 365.2425 days, a tropical year
1054             # [Explanatory Supplement to The Astronomical Ephemeris]. If the
1055             # tropical year is 365.2422 days, as indicated by other references,
1056             # then the time of the vernal equinox will be off by 2.88 hours in
1057             # 400 years.
1058              
1059             # Time of vernal equinox for year 2000 A.D. is March 20, 7:36 GMT
1060             # [NASA Reference Publication 1349, Oct. 1994]. VERNAL assumes
1061             # that vernal equinox for year 2000 will be on March 20, 7:30, or
1062             # 79.3125 days from 2000 January 1, hour 0. Vernal equinoxes for
1063             # other years returned by VERNAL are also measured in days from
1064             # 2000 January 1, hour 0. 79.3125 = 31 + 29 + 19 + 7.5/24.
1065             sub vernal {
1066 0     0 0   my $self = shift;
1067 0           my $JYEAR = shift;
1068              
1069 0           my $EDAYzY = 365.2425;
1070 0           my $VE200D = 79.3125;
1071 0           my $VERNAL = $VE200D + ($JYEAR - 2000) * $EDAYzY;
1072             }
1073              
1074             #----------------------------------------#
1075             # QLEAPY
1076             # Determine whether the given JYEAR is a Leap Year or not.
1077             #----------------------------------------#
1078             sub QLEAPY {
1079 0     0 0   my $year = shift;
1080              
1081 0 0         if(!($year%4)) {
1082 0 0         if($year%100) {
1083 0           return(0); # if leap year
1084             } else {
1085 0 0         if(!($year%400)) {
1086 0           return(0);
1087             }
1088 0           return(1)
1089             }
1090             }
1091              
1092 0           return 1; # if it is not leap year
1093             }
1094              
1095             #----------------------------------------#
1096             # COSZIJ calculates the daily average cosine of the zenith angle
1097             # weighted by time and weighted by sunlight.
1098             #
1099             # Input: RLAT = latitude (degrees)
1100             # SIND,COSD = sine and cosine of the declination angle
1101             #
1102             # Output: COSZT = sum(cosZ*dT) / sum(dT)
1103             # COSZS = sum(cosZ*cosZ*dT) / sum(cosZ*dT)
1104             #
1105             # Intern: DAWN = time of DAWN (temporal radians) at mean local time
1106             # DUSK = time of DUSK (temporal radians) at mean local time
1107             #----------------------------------------#
1108             sub COSZIJ {
1109 0     0 0   my $self = shift;
1110 0           my $SIND = shift;
1111 0           my $COSD = shift;
1112              
1113 0           my $DUSK = 0;
1114 0           my $CDUSK = 0;
1115 0           my $SDUSK = 0;
1116 0           my $DAWN = 0;
1117 0           my $S2DUSK = 0;
1118 0           my $SDAWN = 0;
1119 0           my $S2DAWN = 0;
1120 0           my $COSZT = 0;
1121 0           my $COSZS = 0;
1122 0           my $ECOSZ = 0;
1123 0           my $QCOSZ = 0;
1124              
1125 0           my $SINJ = sin($self->{CONSTANTS}->{TWOPI} * $self->{latitude} / 360);
1126             #print "sinj : $SINJ\n";
1127 0           my $COSJ = cos($self->{CONSTANTS}->{TWOPI} * $self->{latitude} / 360);
1128             #print "cosj : $COSJ\n";
1129              
1130 0           my $SJSD = $SINJ * $SIND;
1131 0           my $CJCD = $COSJ * $COSD;
1132              
1133             # Constant nightime at this latitude
1134 0 0         if ( ($SJSD + $CJCD) <= 0) {
1135 0           $DAWN = 999999;
1136 0           $DUSK = 999999;
1137 0           $COSZT = 0;
1138 0           $COSZS = 0;
1139             }
1140              
1141             # Constant daylight at this latitude
1142 0 0         if ( ($SJSD - $CJCD) >= 0) {
1143 0           $DAWN = -999999;
1144 0           $DUSK = -999999;
1145 0           $ECOSZ = $SJSD * $self->{CONSTANTS}->{TWOPI};
1146 0           $QCOSZ = $SJSD * $ECOSZ + .5 * $CJCD * $CJCD * $self->{CONSTANTS}->{TWOPI};
1147 0           $COSZT = $SJSD; # = ECOSZ/$self->{CONSTANTS}->{TWOPI}
1148 0           $COSZS = $QCOSZ / $ECOSZ;
1149             }
1150              
1151             # Compute DAWN and DUSK (at local time) and their sines
1152 0           $CDUSK = - ($SJSD / $CJCD);
1153 0           $DUSK = acos($CDUSK);
1154 0           $SDUSK = sqrt( $CJCD * $CJCD - $SJSD * $SJSD) / $CJCD;
1155 0           $S2DUSK = 2 * $SDUSK * $CDUSK;
1156 0           $DAWN = -$DUSK;
1157 0           $SDAWN = -$SDUSK;
1158 0           $S2DAWN = -$S2DUSK;
1159              
1160             # Nightime at initial and final times with daylight in between
1161 0           $ECOSZ = $SJSD * ($DUSK-$DAWN) + $CJCD * ($SDUSK - $SDAWN);
1162 0           $QCOSZ = $SJSD * $ECOSZ + $CJCD * ($SJSD * ($SDUSK - $SDAWN) +.5 * $CJCD * ($DUSK - $DAWN + .5 * ($S2DUSK-$S2DAWN) ) );
1163 0           $COSZT = $ECOSZ / $self->{CONSTANTS}->{TWOPI};
1164 0           $COSZS = $QCOSZ / $ECOSZ;
1165              
1166 0           return ($COSZT, $COSZS);
1167             }
1168              
1169             # my own div because we're getting nowhere with '%'
1170             # this is such a hack
1171             # a = 49.2
1172             # b = 6.23
1173             # a/b = 7.89
1174             # d = 7
1175             # e = 89
1176             # return 49.2 - (7 * 6.23)
1177             sub modulo {
1178 0     0 0   my $a = shift;
1179 0           my $b = shift;
1180 0           my ($d, $e) = split(/\./, ($a/$b), 2);
1181              
1182 0           return ($a - ($d * $b));
1183             }
1184              
1185             # my own round up because the one that I found on perlmonks.org
1186             # didn't work well
1187             sub _roundup {
1188 0     0     my $a = shift;
1189 0           my ($b, $c) = split(/\./, $a, 2);
1190              
1191 0 0         if (($a-$b) >= .5) {
1192 0           return ($b+1);
1193             }
1194              
1195 0           return $b;
1196             }
1197              
1198             1;
1199              
1200             __END__