File Coverage

blib/lib/Ham/Resources/Utils.pm
Criterion Covered Total %
statement 238 267 89.1
branch 52 100 52.0
condition 17 39 43.5
subroutine 19 19 100.0
pod 14 14 100.0
total 340 439 77.4


line stmt bran cond sub pod time code
1             package Ham::Resources::Utils;
2              
3 9     9   605415 use 5.006;
  9         118  
4 9     9   4690 use Math::Trig qw(great_circle_distance deg2rad great_circle_direction rad2deg pi asin acos tan);
  9         140171  
  9         954  
5 9     9   4884 use Ham::Locator;
  9         176128  
  9         54  
6 9     9   358 use strict;
  9         21  
  9         225  
7 9     9   52 use warnings;
  9         17  
  9         30134  
8              
9             =head1 NAME
10              
11             Ham::Resources::Utils - Calculation of distance and course beetwen two points
12             on Earth (through coordinates or grid locator), and Sunrise, Sunset and Midday time for these locations (in UTC). Also sexagesimal degrees and decimal degrees convertion and grid location. For use mainly for Radio Amateurs.
13              
14             =head1 VERSION
15              
16             Version 0.05
17              
18             =cut
19              
20             our $VERSION = '0.05';
21              
22             my %coordinates = (
23             long_1 => "",
24             lat_1 => "",
25             long_2 => "",
26             lat_2 => "",
27             );
28              
29             my $locator_precision = 8;
30              
31             my $self = {};
32              
33             =head1 SYNOPSIS
34              
35             This module calculates the distance and course between two points on the Earth.
36             Also Sunrise, Sunset and Midday time for both locations.
37              
38             The data of the locations may be in terrestrial coordinates or through 'Maidenhead Locator System' (grid Locator) notacion.
39              
40             The module offer the possibility to access to some methods that uses it, for
41             example conversions between sexagesimal degrees into decimal degrees or
42             conversion between grid locator to sexagesimal degrees.
43              
44             Also access to convert decimal degrees to compass names, and more.
45              
46             use Ham::Resources::Utils;
47              
48             my $foo = Ham::Resources::Utils->new();
49             ...
50              
51              
52             =head1 SUBROUTINES/METHODS
53              
54             =head2 new
55              
56             This is the constructor.
57              
58             my $foo = Ham::Resources::Utils->new();
59              
60             =cut
61              
62 8     8 1 723 sub new { bless {}, shift }
63              
64              
65             =head2 data_by_coordinates
66              
67             Gets a string with the date and a hash with the coordinates in sexagesimal
68             values from point A and point B and returns a hash with all previous
69             data and Distance, course and compass values and Sunrise, Sunset and Midday
70             times for both locations if needed.
71              
72             my $date = "14-03-2012";
73             my %coordinates = ( lat_1 => "41N23",
74             long_1 => "2E11",
75             lat_2 => "30S0",
76             long_2 => "10W45");
77              
78             my %data = $foo->data_by_coordinates{$date, %coordinates};
79             print Dumper(%data);
80              
81             The $date argument is necessary for the Sun time calculations (Sunrise, Sunset and Midday).
82              
83             Distances are in kilometers (km) and Miles (mi). Times are in UTC (Universal Time).
84              
85             An output example:
86              
87             DATA_BY_COORDINATES()
88             compass: S # compass direction to point B or destination
89             course_dec: 190.94 # direction to destination in decimal degree
90             course_sexag: 190.56 # direction to destination in sexagesimal degree
91             date: 14-3-2012 # date of event (for Sun calculation porpouses)
92             distance_km: 9377.83 # distance to destination in kilometers
93             distance_mi: 17367.74 # distance to destination in miles
94             lat_1: 41N23" # Latitude of point A or origin in sexagesinal notation
95             lat_1_dec: 41.3833333333333 # Latitude of origin in decimal notation
96             lat_2: 41S54" # Latitude of point B or destination in sexagesimal notation
97             lat_2_dec: -41.9 # Latiude of destination in decimal notation
98             locator_1: JN11cj # Grid Locator of origin point
99             locator_2: IE38sc # Grid Locator of destination point
100             long_1: 2E12" # Longitude of point A or origin in sexagesimal notation
101             long_1_dec: 2.2 # Longitude of origin in decimal notation
102             long_2: 12W30" # Longitude of point B or destination in sexagesimal notation
103             long_2_dec: -12.5 # Longitude of destination in decimal notation
104             midday_arrive: 12h 1m # Midday time on point B (destination) in UTC
105             midday_departure: 12h 1m # Midday time on point A (origin) in UTC
106             sunrise_arrive: 6h 5m # Sun rise time on point B (destination) in UTC
107             sunrise_departure: 6h 5m # Sun rise time on point A (origin) in UTC
108             sunset_arrive: 17h 58m # Sun set time on point B (destination) in UTC
109             sunset_departure: 17h 58m # Sun set time on point A (origin) in UTC
110              
111              
112             =cut
113              
114             sub data_by_coordinates {
115 1     1 1 626 my $self = shift;
116 1         3 my $date = shift;
117 1         6 my %coordinates = @_;
118 1         5 my %data = data_constructor($self, $date, %coordinates);
119             }
120              
121             =head2 data_by_locator
122              
123             Gets a string with the date and a string with the locator of point 'A' and an
124             string with a locator for point 'B'. Returns a hash with the data shown it in the
125             previous method.
126              
127             my $date = "14-03-2012"; # date in 'dd-mm-yyyy' format
128             my $locator_dep = "JN11cj";
129             my $locator_arr = "IJ90ca";
130              
131             my %data = $foo->data_by_locator($date,$locator_dep,$locator_arr);
132             print Dumper(%data);
133              
134             =cut
135              
136             sub data_by_locator {
137 1     1 1 483 my $self = shift;
138 1         2 my $date = shift;
139 1         3 my ($locator_dep, $locator_arr) = @_;
140 1         3 my ($lat_dep, $long_dep, $lat_arr, $long_arr) = undef;
141 1 50       6 ($lat_dep, $long_dep) = loc2degree($self,$locator_dep) if ($locator_dep);
142 1 50       6 ($lat_arr, $long_arr) = loc2degree($self,$locator_arr) if ($locator_arr);
143              
144 1 50       9 $lat_dep =~ s/\./N/ if ($lat_dep !~ /^\s\d+/);
145 1 50       4 $lat_dep =~ s/\./S/ if ($lat_dep =~ /^\s\d+/);
146 1 50       5 $lat_arr =~ s/\./N/ if ($lat_arr !~ /^\s\d+/);
147 1 50       5 $lat_arr =~ s/\./S/ if ($lat_arr =~ /^\s\d+/);
148              
149 1 50       5 $long_dep =~ s/\./E/ if ($long_dep !~ /^\s\d+/);
150 1 50       4 $long_dep =~ s/\./W/ if ($long_dep =~ /^\s\d+/);
151 1 50       4 $long_arr =~ s/\./E/ if ($long_arr !~ /^\s\d+/);
152 1 50       5 $long_arr =~ s/\./W/ if ($long_arr =~ /^\s\d+/);
153              
154 1         17 my %coordinates = (
155             long_1 => $long_dep,
156             lat_1 => $lat_dep,
157             long_2 => $long_arr,
158             lat_2 => $lat_arr,
159             );
160              
161 1         7 my %data = data_constructor($self, $date, %coordinates);
162             }
163              
164             sub data_constructor {
165 2     2 1 5 my $self = shift;
166 2         4 my $date = shift;
167 2         6 my %coord = @_;
168 2         6 my $error = "";
169              
170 2         10 %coord = (%coord, sexag2dec($self, %coord));
171 2         12 %coord = check_error(%coord);
172              
173 2         14 my @DEPARTURE = NESW( $coord{long_1_dec}, $coord{lat_1_dec} );
174 2         62 my @ARRIVE = NESW( $coord{long_2_dec}, $coord{lat_2_dec} );
175 2         32 my $km = great_circle_distance(@DEPARTURE, @ARRIVE, 6371); # medium value for Earth radii
176 2         141 $km = sprintf("%.2f",$km);
177 2         8 my $mi = $km / 1.609344; # miles conversion
178 2         11 $mi = sprintf("%.2f",$mi);
179 2         9 my $rad = great_circle_direction(@DEPARTURE, @ARRIVE);
180 2         77 my $sexag = dec2sexag($self, rad2deg($rad));
181 2         9 my $rad_round = sprintf("%.2f",(rad2deg($rad)));
182 2         24 my $compass = compass($self, rad2deg($rad));
183              
184 2         9 my $locator_dep = degree2loc($self, $coord{lat_1}, $coord{long_1});
185 2         472 my $locator_arr = degree2loc($self, $coord{lat_2}, $coord{long_2});
186              
187              
188 2         303 my %sun_departure = cicle_sun($self, $coord{lat_1_dec}, $coord{long_1_dec}, $date, "_departure");
189 2         11 my %sun_arrive = cicle_sun($self, $coord{lat_2_dec}, $coord{long_2_dec}, $date, "_arrive");
190              
191 2         37 %coord = ( %coord,
192             distance_km => $km,
193             distance_mi => $mi,
194             course_sexag => $sexag,
195             course_dec => $rad_round,
196             compass => $compass,
197             date => $date,
198             locator_1 => $locator_dep,
199             locator_2 => $locator_arr,
200             %sun_departure, %sun_arrive
201             );
202 2 50       9 if ($error) { return my %error = (_error => $error); }
  0         0  
203              
204 2         47 return %coord;
205             }
206              
207             =head2 loc2degree
208              
209             Gets a string with grid locator value and returns an array with the latitude and
210             longitude in sexagesimal degrees form. Grid precision only 6 digit.
211              
212             =cut
213              
214             sub loc2degree {
215 2     2 1 5 my ($self,$loc) = @_;
216 2         15 my $grid = new Ham::Locator;
217 2         17 $grid->set_precision($locator_precision);
218 2 50       37 $loc = substr($loc, 0, 6) if (length($loc) > 6);
219              
220 2         9 $grid->set_loc($loc);
221 2         24 my ($latitude, $longitude) = $grid->loc2latlng;
222 2         380 my $lat_sexag = dec2sexag($self, $latitude);
223 2         5 my $long_sexag = dec2sexag($self, $longitude);
224              
225 2 100       7 $lat_sexag =~ tr/-/ / if($latitude < 0);
226 2 100       7 $long_sexag =~ tr/S/W/ if($longitude < 0);
227 2 100       7 $long_sexag =~ tr/N/E/ if($longitude > 0);
228 2 100       6 $long_sexag =~ tr/-/ / if($longitude < 0);
229              
230 2         14 return ($lat_sexag, $long_sexag);
231             }
232              
233             =head2 degree2loc
234              
235             Gets a string with the latitude and a string with the longitude, in sexagesimal
236             notation, of a point on Earth and returns a string with the grid locator notation.
237             Grid precision now is 8 digit.
238              
239             my $lat = "41N23";
240             my $long = "2E11";
241             my $locator = $foo->degree2loc($lat, $long);
242              
243             =cut
244              
245             sub degree2loc {
246 5     5 1 515 my ($self, $lat, $long) = @_;
247 5         21 my %deg_coord = (
248             lat_1 => $lat,
249             long_1 => $long
250             );
251 5         19 my %dec_coord = sexag2dec($self, %deg_coord);
252 5         22 my %check = check_error(%dec_coord);
253 5 50       24 if ($check{_error}) { return "Error to convert degrees to locator."; }
  0         0  
254              
255 5         44 my $m = new Ham::Locator;
256 5         58 $m->set_precision($locator_precision);
257 5         108 $m->set_latlng($dec_coord{lat_1_dec}, $dec_coord{long_1_dec});
258 5         76 my $loc = $m->latlng2loc;
259             }
260              
261             =head2 compass
262              
263             Gets an integer with a decimal degree and returns a string with its
264             equivalent value in a compass (North, East, ...). It uses a separation of 11.25
265             degrees for each position, 32 cardinal positions of total.
266             Values range must be between 0 to 360 degrees.
267              
268             my $compass = $foo->compass("-90.0"); # returns "W" (west)
269             my $compass = $foo->compass("270.0"); # returns "W" (west)
270              
271             =cut
272              
273             sub compass {
274 3     3 1 457 my $self = shift;
275 3         7 my $course = shift;
276 3         16 my $pattern = qr/(\+?)(-?)\d{1,3}(\.?)(\d*)$/;
277 3         6 my $error_msg = "Error value must be a integer between 0 to 360";
278              
279 3 50       37 if ($course !~ $pattern) {
280 0         0 return $error_msg;
281             } else {
282 3 50 33     36 if ($course < -360 or $course > 360) { return $error_msg; }
  0         0  
283 3         33 my @rosa = ('NbE','NNE','NEbN','NE','NEbE','ENE','EbN','E','EbS','ESE','SEbE','SE','SEbS','SSE','SbE','S','SbW','SSW','SWbS','SW','SWbW','WSW','WbS','W','WbN','WNW','NWbW','NW','NWbN','NNW','NbW','N');
284              
285 3         11 my $rosa_index = int((+$course / 11.25))-1;
286 3         18 return $rosa[$rosa_index];
287             }
288             }
289              
290             =head2 dec2sexag
291              
292             Gets an integer with a decimal degree value and returns a string with its
293             equivalence to sexagesimal degree form. Only returns degrees and minutes.
294              
295             =cut
296              
297             sub dec2sexag{
298 7     7 1 499 my ($self, $course) = @_;
299 7         50 my @degree_part = split(/\./, $course);
300 7         30 my $decimal = (('0.'.$degree_part[1]) * 60);
301 7         41 my $min = int((sprintf("%.2f",$decimal)));
302 7         40 my $sexag = $degree_part[0].".".$min;
303             }
304              
305             =head2 sexag2dec
306              
307             Gets a hash with sexagesimal value (maximun four) and returns a hash with its
308             decimal values.
309              
310             Range of values are -90 to 90 for latitudes, and -180 to 180 for longitudes.
311              
312             Values must be a pair, longitude and latitude. Two values for one point or
313             four values (two pairs) for two points.
314              
315             There is not mandatory send a complete hash (4 values), but you will receive a
316             hash with the four.
317              
318             You can use it like this:
319              
320             my %coord = (
321             Long_1 => "41N23.30",
322             Lat_1 => "2E11.10"
323             );
324             my %sexag = $foo->sexag2dec(%coord);
325             foreach my $key (sort keys %sexag) {
326             say $key." = ".$sexag{$key} if ($sexag{$key});
327             }
328              
329             The index send it, you will receive with '_dec' suffix, ie, you send
330             'latitude' and receive 'latitude_dec'
331              
332             =cut
333              
334             sub sexag2dec {
335 9     9 1 1088 my $self = shift;
336 9         29 my %coord = @_;
337              
338 9         20 my $error_msg_1 = "Error sexagesimal conversion. Out of range. (-90 to 90)";
339 9         18 my $error_msg_2 = "Error sexagesimal conversion. Out of range. (-180 to 180)";
340              
341 9         40 my $grad_match = qr|(\d{1,3})([NSEOW+-\.])(\d{1,2})\.{0,1}(\d{0,2}){0,1}|i;
342 9         42 my %coord_dec = (
343             lat_1_dec => '',
344             lat_2_dec => '',
345             long_1_dec => '',
346             long_2_dec => '',
347             );
348 9         18 my $secs = undef;
349 9         62 foreach my $key (sort keys %coord) {
350 22 50       132 if ($4) {$secs = $4/3600;} else {$secs = 0}
  0         0  
  22         38  
351 22 50 33     239 if ($key && ($coord{$key} =~ $grad_match)) {
352 22 100 100     139 if ($2 eq 'S' || $2 eq 'W' || $2 eq '-') {
      66        
353 10         50 $coord_dec{$key.'_dec'} = (-($1+(($3/60)+$secs)));
354 10 50 66     86 if ($2 eq 'S' && $coord_dec{$key.'_dec'} < -90) { $coord_dec{$key.'_dec'} = $error_msg_1; }
  0         0  
355 10 50 66     69 if ($2 eq 'W' && $coord_dec{$key.'_dec'} < -180) { $coord_dec{$key.'_dec'} = $error_msg_2; }
  0         0  
356             } else {
357 12         87 $coord_dec{$key.'_dec'} = ($1+(($3/60)+$secs));
358 12 50 66     76 if ($2 eq 'N' && $coord_dec{$key.'_dec'} > 90) { $coord_dec{$key.'_dec'} = $error_msg_1; }
  0         0  
359 12 50 66     73 if ($2 eq 'E' && $coord_dec{$key.'_dec'} > 180) { $coord_dec{$key.'_dec'} = $error_msg_2; }
  0         0  
360             }
361             }
362             }
363 9         83 return %coord_dec;
364             }
365              
366             =head2 cicle_sun
367              
368             Gets three strings with latitude, longitude, in decimal degrees, and date, in
369             'dd-mm-yyyy' format and returns a hash with Sunrise time, Sunset time and
370             Midday time in hours and minutes format in Universal Time (UTC).
371              
372             my %sun = $foo->cicle_sun($lat,$long,$date);
373              
374             =cut
375              
376             sub cicle_sun {
377 5     5 1 17 my $self = shift;
378 5         15 my ($Lat, $Long, $date, $origin_point) = @_;
379              
380 5 100       17 $origin_point = "" if (!$origin_point);
381 5 50       21 $date = "00-00-0000" if ($date =~ /Error/);
382 5 50       33 $Lat = 0 if ($Lat =~ /Error/);
383 5 50       23 $Long = 0 if ($Long =~ /Error/);
384              
385 5         23 my @date_ = date_split($self, $date);
386 5         11 my $day = $date_[0];
387 5         11 my $month = $date_[1];
388 5         8 my $year = $date_[2];
389 5         18 my $UT = '0';
390              
391 5 50       15 if ($day =~ /Error/) {
392 0         0 return my %solar_cycle = (_error => $day);
393             }
394              
395              
396             # Julian data
397 5         25 my $GGG = 1;
398 5         16 my $S = 1;
399              
400 5 50       18 if ($year <= 1585) { $GGG = 0; }
  0         0  
401 5         25 my $JD = -1 * int(7 * (int(($month + 9) / 12 ) + $year) / 4);
402 5 50       15 if (($month - 9) < 0) { $S = -1; }
  5         9  
403 5         11 my $A = abs($month - 9);
404 5         11 my $J1 = int($year + $S * int($A / 7));
405 5         14 $J1 = -1 * int((int($J1 / 100) + 1) * 3 / 4);
406 5         13 $JD = $JD + int(275 * $month / 9) + $day + ($GGG * $J1);
407 5         13 $JD = $JD + 1721027 + 2 * $GGG + 367 * $year - 0.5;
408 5         10 my $J2 = $JD;
409              
410             # Earth values
411 5         8 my $RAD = 180 / pi;
412 5         8 my $ET = 0.016718;
413 5         17 my $VP = 8.22e-5;
414 5         7 my $P = 4.93204;
415 5         14 my $M0 = 2.12344;
416 5         10 my $MN = 1.72019e-2;
417 5         9 my $T0 = 2444000.5;
418 5         8 $S = 2415020.5;
419 5         14 $P = $P + ($J2 - $T0) * $VP / 100;
420 5         37 my $AM = $M0 + $MN * ($J2 - $T0);
421 5         26 $AM = $AM - 2 * pi * int($AM / (2 * pi));
422              
423             # Kepler equation for the Earth
424 5         94 my $V = $AM + 2 * $ET * sin($AM) + 1.25 * $ET * $ET * sin(2 * $AM);
425 5 50       23 if ($V < 0) {
426 0         0 $V = 2 * pi + $V;
427             }
428 5         11 my $L = $P + $V;
429 5         12 $L = $L - 2 * pi * int($L / (2 * pi));
430              
431             #AR and DEC calculus
432 5         10 my $Z = ($J2 - 2415020.5) / 365.2422;
433 5         13 my $OB = 23.452294 - (0.46845 * $Z + 0.00000059 * $Z * $Z) / 3600;
434 5         9 $OB = $OB / $RAD;
435 5         26 my $DC = asin(sin($OB) * sin($L));
436 5         89 my $AR = acos(cos($L) / cos($DC));
437 5 50       61 if ($L > pi) {
438 0         0 $AR = 2 * pi - $AR;
439             }
440 5         9 $OB = $OB * $RAD;
441 5         8 $L = $L * $RAD;
442 5         11 $AR = $AR * 12 / pi;
443              
444             # HH.MM to AR conversion
445 5         9 my $H = int($AR);
446 5         13 my $M = int(($AR - int($AR)) * 60);
447 5         13 $S=(($AR - int($AR)) * 60 - $M) * 60;
448 5         7 $DC = $DC * $RAD;
449              
450             # Degrees conversion from DEC
451 5         10 my $D = abs($DC);
452 5 50       17 if ($DC > 0) {
453 5         13 my $G1 = int($D);
454             } else {
455 0         0 my $G1 = (-1) * int($D);
456             }
457 5         21 my $M1 = int(($D - int($D)) * 60);
458 5         21 my $S1 = (($D - int($D)) * 60 - $M1) * 60;
459 5 50       23 if ($DC < 0) {
460 0         0 $M1 = -$M1;
461 0         0 $S1 = -$S1;
462             }
463              
464             # Time equation
465 5         44 my $MR = 0.04301;
466 5         12 my $F = 13750.987;
467 5         20 my $C = 2 * $ET * $F * sin($AM) + 1.25 * $ET * $ET * $F * sin(2 * $AM);
468 5         20 my $R = -$MR * $F * sin(2 * ($P + $AM)) + $MR * $MR * $F * sin(4 * ($P + $AM)) / 2;
469 5         16 $ET = $C + $R;
470              
471             # Semi-diurn arc calculus
472 5         21 my $H0 = acos(-tan($Lat / $RAD) * tan($DC / $RAD));
473 5         173 $H0 = $H0 * $RAD;
474              
475             # DEC variations
476 5         24 my $VD = 0.9856 * sin($OB / $RAD) * cos($L / $RAD) / cos($DC / $RAD);
477              
478             # Sun rise calculus
479 5         14 my $VDOR = $VD * (-$H0 + 180) / 360;
480 5         11 my $DCOR = $DC + $VDOR;
481 5         12 my $HORTO = -acos(-tan($Lat / $RAD) * tan($DCOR / $RAD));
482 5         143 my $VHORTO = 5 / (6 * cos($Lat / $RAD) * cos($DCOR / $RAD) * sin($HORTO));
483 5         12 $HORTO = ($HORTO * $RAD + $VHORTO) / 15;
484 5         16 my $TUORTO = $HORTO + $ET / 3600 - $Long / 15 + 12;
485              
486             # Sun rise value conversion to HH.MM
487 5         9 my $HOR = int($TUORTO);
488 5         13 my $MOR = int(($TUORTO - $HOR) * 60 + 0.5);
489              
490             # AZ calculation
491 5         15 my $TUC = 12 + $ET / 3600 - $Long / 15;
492              
493             # AZ value conversion to HH.MM
494 5         22 my $HC = int($TUC);
495 5         15 my $MC = int(($TUC - $HC) * 60 + 0.5);
496              
497             # Sunset calculus
498 5         23 my $VDOC = $VD * ($H0 + 180) / 360;
499 5         12 my $DCOC = $DC + $VDOC;
500 5         17 my $HOC = acos(-tan($Lat / $RAD) * tan($DCOC / $RAD));
501 5         147 my $VHOC = 5 / (6 * cos($Lat / $RAD) * cos($DCOC / $RAD) * sin($HOC));
502 5         10 $HOC = ($HOC * $RAD + $VHOC) / 15;
503 5         14 my $TUOC = $HOC + $ET / 3600 - $Long / 15 + 12;
504              
505             # Sunset conversion to HH.MM
506 5         8 $HOC = int($TUOC);
507 5         12 my $MOC = int(($TUOC - $HOC) * 60 + 0.5);
508              
509             # Altitude of AZ
510 5         13 my $HCUL = 90 - $Lat + ($DCOR + $DCOC) / 2;
511              
512             # Degree conversion from altitude
513 5         9 my $GCUL = int($HCUL);
514 5         14 my $MCUL = int (($HCUL - $GCUL) * 60 + 0.5);
515              
516             # AZ from Sunrise and Sunset
517 5         27 my $ACOC = acos(-sin($DCOC / $RAD) / cos($Lat / $RAD)) * $RAD;
518 5         39 my $ACOR = 360 - acos(-sin($DCOR / $RAD) / cos($Lat / $RAD)) * $RAD;
519              
520             # AZ conversion to degrees
521 5         37 my $GACOC = int($ACOC);
522 5         14 my $MACOC = int(($ACOC - $GACOC) * 60 + 0.5);
523 5         9 my $GACOR = int($ACOR);
524 5         12 my $MACOR = int(($ACOR - $GACOR) * 60 + 0.5);
525              
526 5         18 my $sunrise = $HOR."h ".$MOR."m";
527 5         15 my $sunset = $HOC."h ".$MOC."m";
528 5         14 my $midday = $HC."h ".$MC."m";
529              
530 5         17 my $k_sunrise = "sunrise".$origin_point;
531 5         19 my $k_sunset = "sunset".$origin_point;
532 5         9 my $k_midday = "midday".$origin_point;
533              
534 5         24 my %solar_cycle = (
535             $k_sunrise => $sunrise,
536             $k_sunset => $sunset,
537             $k_midday => $midday,
538             );
539              
540 5 50       16 if ($day =~ /Error/) {
541 0         0 return my %solar_cycle = (_error => $day);
542             }
543 5 50 33     39 if ($Lat == 0 || $Long == 0) {
544 0         0 %solar_cycle = (_error => "Error sexagesimal conversion. Out of range.");
545             }
546              
547 5         56 return %solar_cycle;
548             }
549              
550             =head2 date_split
551              
552             Gets a string with date in format 'dd-mm-yyyy' and check it if value is a valid date.
553              
554             Returns an array with the day, month and year ... or error message.
555              
556             =cut
557              
558             sub date_split {
559 6     6 1 545 my ($self, $date) = @_;
560 6         13 my @part_of_date;
561 6         18 my $check = is_date($date);
562 6 50       24 if ($check !~ /Error/) {
563 6         31 return @part_of_date = split(/-/,$date);
564             } else {
565 0         0 return $part_of_date[0] = $check;
566             }
567             }
568              
569             # ---------------
570             # INTERNAL SUBS
571             # ---------------
572              
573             =head1 Internals subs
574              
575             =head2 data_constructor
576              
577             Internal function used by data_by_coordinates() and data_by_locator() to call the others functions and create a response.
578              
579             =head2 NESW
580              
581             Internal function to convert degrees to radians.
582              
583             =head2 check_error
584              
585             Internal function to check errors in data_by_coordinates() or data_by_locator().
586              
587             =head2 is_date
588              
589             Internal function to check if a date is valid.
590              
591             =cut
592              
593             sub NESW {
594 4     4 1 18 deg2rad($_[0]), deg2rad(90 - $_[1])
595             }
596              
597             sub is_date {
598 6     6 1 12 my $date = shift;
599 6         38 my $pattern = qr/\d{1,2}(-)\d{1,2}(-)\d{4}/;
600              
601 6 50       57 if ($date !~ $pattern) {
602 0         0 return "Error date format. Must be dd-mm-yyyy";
603             } else {
604 6         26 my @part_of_date = split(/-/,$date);
605              
606 6 50       43 my $intDay = ($part_of_date[0] <= 9) ? sprintf("%02d", $part_of_date[0]) : sprintf("%2d", $part_of_date[0]);
607 6 50       30 my $intMonth = ($part_of_date[1] <= 9) ? sprintf("%02d", $part_of_date[1]) : sprintf("%2d", $part_of_date[1]);
608 6         14 my $intYear = $part_of_date[2];
609              
610 6         63 my %array_month = (
611             '01' => 31,
612             '02' => 0,
613             '03' => 31,
614             '04' => 30,
615             '05' => 31,
616             '06' => 30,
617             '07' => 31,
618             '08' => 31,
619             '09' => 30,
620             '10' => 31,
621             '11' => 30,
622             '12' => 31,
623             );
624              
625 6 50       27 if ($intMonth > 12) { return "Error in date format. This must be a valid dd-mm-yyyy." }
  0         0  
626              
627 6 50 33     42 if ($array_month{$intMonth} != 0 && $part_of_date[0] <= $array_month{$intMonth}) {
628 6         45 return 1;
629             }
630              
631 0 0       0 if ($intMonth == 0) {
632 0 0 0     0 if ($intDay > 0 && $intDay < 29) {
    0          
633 0         0 return 1;
634             }
635             elsif ($intDay == 29) {
636 0 0 0     0 if (($intYear % 4 == 0) && ($intYear % 100 != 0) || ($intYear % 400) == 0) {
      0        
637 0         0 return 1;
638             }
639             }
640             }
641              
642 0         0 return "Error in date format. This must be a valid dd-mm-yyyy.";
643             }
644             }
645              
646             sub check_error {
647 7     7 1 26 my %coord = @_;
648 7         32 foreach my $key (sort keys %coord) {
649 36 50       144 if ($coord{$key} =~ /Error/) {
650 0         0 $coord{_error} = $coord{$key};
651 0         0 $coord{$key} = 0;
652             }
653             }
654 7         39 return %coord;
655             }
656              
657              
658             =head1 Cheking Errors
659              
660             In functions that return only a string or an array, errors will detect to match /Error/ word.
661             In complex functions, like data_by_coordinates, that responses with a hash, you check the '_error' index, i.e:
662              
663             %data = $foo->data_by_locator($date,$locator_1,$locator_2);
664             if (!$data{_error}) {
665             foreach my $key (sort keys %data) {
666             say $key.": ".$data{$key};
667             }
668             } else {
669             say $data{_error};
670             }
671              
672             ... or something like this :p
673              
674             =cut
675              
676              
677             =head1 AUTHOR
678              
679             CJUAN, C<< >>
680              
681             =head1 BUGS
682              
683             Please report any bugs or feature requests to C, or through
684             the web interface at L. I will be notified, and then you'll
685             automatically be notified of progress on your bug as I make changes.
686              
687              
688              
689              
690             =head1 SUPPORT
691              
692             You can find documentation for this module with the perldoc command.
693              
694             perldoc Ham::Resources::Utils
695              
696              
697             You can also look for information at:
698              
699             =over 4
700              
701             =item * RT: CPAN's request tracker (report bugs here)
702              
703             L
704              
705             =item * AnnoCPAN: Annotated CPAN documentation
706              
707             L
708              
709             =item * CPAN Ratings
710              
711             L
712              
713             =item * Search CPAN
714              
715             L
716              
717             =back
718              
719             =head1 TODO
720              
721             =over 4
722              
723             =item * Add long path course and distances from point A to B
724              
725             =item * Add a function to calculate X and Y coordinates based on real coordinates for use it on a geographical projection (or Plate Carree)
726              
727             =back
728              
729              
730             =head1 LICENSE AND COPYRIGHT
731              
732             Copyright 2012-2018 CJUAN.
733              
734             This program is free software; you can redistribute it and/or modify it
735             under the terms of either: the GNU General Public License as published
736             by the Free Software Foundation; or the Artistic License.
737              
738             See http://dev.perl.org/licenses/ for more information.
739              
740              
741             =cut
742              
743             1; # End of Ham::Resources::Utils