File Coverage

blib/lib/Geo/Location/Point.pm
Criterion Covered Total %
statement 87 98 88.7
branch 38 50 76.0
condition 34 48 70.8
subroutine 16 18 88.8
pod 9 9 100.0
total 184 223 82.5


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