File Coverage

blib/lib/Geo/Location/Point.pm
Criterion Covered Total %
statement 98 120 81.6
branch 44 62 70.9
condition 35 45 77.7
subroutine 17 20 85.0
pod 11 11 100.0
total 205 258 79.4


line stmt bran cond sub pod time code
1             package Geo::Location::Point;
2              
3 7     7   964292 use 5.10.0; # For the //= operator
  7         80  
4 7     7   37 use strict;
  7         16  
  7         162  
5 7     7   35 use warnings;
  7         14  
  7         199  
6              
7 7     7   35 use Carp;
  7         15  
  7         386  
8 7     7   3248 use GIS::Distance;
  7         19247  
  7         490  
9              
10             use overload (
11             '==' => \&equal,
12             '!=' => \¬_equal,
13             '""' => \&as_string,
14 0     0   0 bool => sub { 1 },
15 7         93 fallback => 1 # So that boolean tests don't cause as_string to be called
16 7     7   50 );
  7         19  
17              
18             =head1 NAME
19              
20             Geo::Location::Point - Location information
21              
22             =head1 VERSION
23              
24             Version 0.09
25              
26             =cut
27              
28             our $VERSION = '0.09';
29              
30             =head1 SYNOPSIS
31              
32             Geo::Location::Point stores a place.
33              
34             use Geo::Location::Point;
35              
36             my $location = Geo::Location::Point->new(latitude => 0.01, longitude => -71);
37              
38             =head1 SUBROUTINES/METHODS
39              
40             =head2 new
41              
42             $location = Geo::Location::Point->new({ latitude => 0.01, longitude => -71 });
43              
44             Takes one optional argument 'key' which is an API key for L for looking up timezone data.
45              
46             =cut
47              
48             sub new {
49 13     13 1 3836 my $class = $_[0];
50              
51 13         24 shift;
52              
53 13         23 my %args;
54 13 100       117 if(ref($_[0]) eq 'HASH') {
    50          
    50          
55 1         2 %args = %{$_[0]};
  1         4  
56             } elsif(ref($_[0])) {
57 0         0 Carp::carp('Usage: ', __PACKAGE__, '->new(cache => $cache [, object => $object ], %args)');
58 0         0 return;
59             } elsif(@_ % 2 == 0) {
60 12         61 %args = @_;
61             }
62              
63 13 100       48 if(!defined($class)) {
    50          
64 1         20 carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
65 1         262 return;
66             } elsif(ref($class)) {
67             # clone the given object
68 0         0 return bless { %{$class}, %args }, ref($class);
  0         0  
69             }
70              
71 12   100     74 $args{'lat'} //= $args{'latitude'} // $args{'Latitude'};
      100        
72 12 100       34 if(!defined($args{'lat'})) {
73 1         4 Carp::carp(__PACKAGE__, ': latitude not given');
74 1         564 return;
75             }
76 11   100     56 $args{'long'} //= $args{'longitude'} // $args{'Longitude'};
      100        
77 11 100       32 if(!defined($args{'long'})) {
78 1         5 Carp::carp(__PACKAGE__, ': longitude not given');
79 1         345 return;
80             }
81              
82 10         37 return bless \%args, $class;
83             }
84              
85             =head2 lat
86              
87             print 'Latitude: ', $location->lat(), "\n";
88             print 'Longitude: ', $location->long(), "\n";
89              
90             =cut
91              
92             sub lat {
93 16     16 1 30 my $self = shift;
94              
95 16         72 return $self->{'lat'};
96             }
97              
98             =head2 latitude
99              
100             Synonym for lat().
101              
102             =cut
103              
104             sub latitude {
105 1     1 1 9 my $self = shift;
106              
107 1         5 return $self->{'lat'};
108             }
109              
110             =head2 long
111              
112             print 'Latitude: ', $location->lat(), "\n";
113             print 'Longitude: ', $location->long(), "\n";
114              
115             =cut
116              
117             sub long {
118 10     10 1 25 my $self = shift;
119              
120 10         71 return $self->{'long'};
121             }
122              
123             =head2 longitude
124              
125             Synonym for long().
126              
127             =cut
128              
129             sub longitude {
130 1     1 1 3 my $self = shift;
131              
132 1         5 return $self->{'long'};
133             }
134              
135             =head2 distance
136              
137             Determine the distance between two locations,
138             returns a L object.
139              
140             =cut
141              
142             sub distance {
143 4     4 1 2319 my ($self, $location) = @_;
144              
145 4 100       15 if(!defined($location)) {
146 1         18 Carp::carp('Usage: ', __PACKAGE__, '->distance($location)');
147 1         350 return;
148             }
149              
150 3   66     52 $self->{'gis'} //= GIS::Distance->new();
151              
152 3         40812 return $self->{'gis'}->distance($self->{'lat'}, $self->{'long'}, $location->lat(), $location->long());
153             }
154              
155             =head2 equal
156              
157             Are two points the same?
158              
159             my $loc1 = Geo::Location::Point->new(lat => 2, long => 2);
160             my $loc2 = Geo::Location::Point->new(lat => 2, long => 2);
161             print ($loc1 == $loc2), "\n"; # Prints 1
162              
163             =cut
164              
165             sub equal {
166 6     6 1 412 my $self = shift;
167 6         9 my $other = shift;
168              
169             # return ($self->distance($other) <= 1e-9);
170 6   66     13 return((abs($self->lat() - $other->lat()) <= 1e-9) && (abs(($self->long() - $other->long())) <= 1e-9));
171             }
172              
173             =head2 not_equal
174              
175             Are two points different?
176              
177             my $loc1 = Geo::Location::Point->new(lat => 2, long => 2);
178             my $loc2 = Geo::Location::Point->new(lat => 2, long => 2);
179             print ($loc1 != $loc2), "\n"; # Prints 0
180              
181             =cut
182              
183             sub not_equal {
184 3     3 1 8 my $self = shift;
185              
186 3         8 return(!$self->equal(shift));
187             }
188              
189             =head2 tz
190              
191             Returns the timezone of the location.
192              
193             =cut
194              
195             sub tz {
196 0     0 1 0 my $self = shift;
197              
198 0 0       0 if(defined($self->{'key'})) {
199 0 0       0 return $self->{'tz'} if(defined($self->{'tz'}));
200              
201 0 0       0 if(!defined($self->{'timezonedb'})) {
202 0         0 require TimeZone::TimeZoneDB;
203 0         0 TimeZone::TimeZoneDB->import();
204              
205 0         0 $self->{'timezonedb'} = TimeZone::TimeZoneDB->new(key => $self->{'key'});
206             }
207 0         0 $self->{'tz'} = $self->{'timezonedb'}->get_time_zone($self)->{'zoneName'};
208              
209 0         0 return $self->{'tz'};
210             }
211             }
212              
213             =head2 timezone
214              
215             Synonym for tz().
216              
217             =cut
218              
219             sub timezone {
220 0     0 1 0 my $self = shift;
221              
222 0         0 return $self->tz();
223             }
224              
225             =head2 as_string
226              
227             Prints the object in human-readable format.
228              
229             =cut
230              
231             sub as_string {
232 5     5 1 921 my $self = shift;
233              
234 5 100       42 if($self->{'location'}) {
235 1         7 return $self->{'location'};
236             }
237              
238 4         8 my $rc = $self->{'name'};
239 4 50       10 if($rc) {
240 0         0 $rc = ucfirst(lc($rc));
241             }
242              
243             # foreach my $field('house_number', 'number', 'road', 'street', 'AccentCity', 'city', 'county', 'region', 'state_district', 'state', 'country') {
244 4         10 foreach my $field('house_number', 'number', 'road', 'street', 'city', 'county', 'region', 'state_district', 'state', 'country') {
245 40 100 100     140 if(my $value = ($self->{$field} || $self->{ucfirst($field)})) {
246 15 100       44 if($rc) {
    50          
247 11 100 66     31 if(($field eq 'street') || ($field eq 'road')) {
248 2 50 33     8 if($self->{'number'} || $self->{'house_number'}) {
249 2         4 $rc .= ' ';
250             } else {
251 0         0 $rc .= ', '
252             }
253             } else {
254 9         15 $rc .= ', ';
255             }
256             } elsif($rc) {
257 0         0 $rc .= ', ';
258             }
259 15         20 my $leave_case = 0;
260 15 50 66     46 if(my $country = $self->{'country'} // $self->{'Country'}) {
261 15 100 33     43 if(uc($country) eq 'US') {
    50          
    50          
262 12 100 100     50 if(($field eq 'state') || ($field eq 'region') || ($field eq 'country')) {
      100        
263 5         6 $leave_case = 1;
264 5 100       15 if(lc($field) eq 'country') {
265 3         5 $value = 'US';
266             }
267             }
268             } elsif(($country eq 'Canada') || ($country eq 'Australia')) {
269 0 0       0 if($field eq 'state') {
270 0         0 $leave_case = 1;
271             }
272             } elsif(uc($country) eq 'GB') {
273 3 100       8 if($field eq 'country') {
274 1         2 $leave_case = 1;
275 1         1 $value = 'GB';
276             }
277             }
278             }
279 15 100       26 if($leave_case) {
280 6         13 $rc .= $value;
281             } else {
282 9         18 $rc .= $self->_sortoutcase($value);
283 9 50 66     73 if((($field eq 'street') || ($field eq 'road')) &&
      66        
284             ($rc =~ /(.+)\s([NS][ew])$/)) {
285             # e.g South Street NW
286 0         0 $rc = "$1 " . uc($2);
287             }
288             }
289             }
290             }
291              
292 4         27 return $self->{'location'} = $rc;
293             }
294              
295             sub _sortoutcase {
296             # my $self = shift;
297             # my $field = lc(shift);
298 9     9   15 my $field = $_[1];
299 9         10 my $rc;
300              
301 9         24 foreach (split(/ /, $field)) {
302 14 100       26 if($rc) {
303 5         7 $rc .= ' ';
304             }
305 14         27 $rc .= ucfirst($_);
306             }
307              
308 9         24 return $rc;
309             }
310              
311             =head2 attr
312              
313             Get/set location attributes, e.g. city
314              
315             $location->city('London');
316             $location->country('UK');
317             print $location->as_string(), "\n";
318             print "$location\n"; # Calls as_string
319              
320             =cut
321              
322             sub AUTOLOAD {
323 13     13   2259 our $AUTOLOAD;
324 13         26 my $key = $AUTOLOAD;
325              
326 13         75 $key =~ s/.*:://;
327              
328 13 100       595 return if($key eq 'DESTROY');
329              
330 3         5 my $self = shift;
331              
332 3 100       8 if(my $value = shift) {
333 1         4 $self->{$key} = $value;
334 1         3 delete $self->{'location'}; # Invalidate the cache
335             }
336              
337 3         14 return $self->{$key};
338             }
339              
340             =head1 AUTHOR
341              
342             Nigel Horne
343              
344             This library is free software; you can redistribute it and/or modify
345             it under the same terms as Perl itself.
346              
347             =head1 BUGS
348              
349             =head1 SEE ALSO
350              
351             L,
352             L,
353             L.
354              
355             =head1 LICENSE AND COPYRIGHT
356              
357             Copyright 2019-2023 Nigel Horne.
358              
359             The program code is released under the following licence: GPL2 for personal use on a single computer.
360             All other users (including Commercial, Charity, Educational, Government)
361             must apply in writing for a licence for use from Nigel Horne at ``.
362              
363             =cut
364              
365             1;