File Coverage

blib/lib/Geo/Coder/CA.pm
Criterion Covered Total %
statement 46 86 53.4
branch 13 38 34.2
condition 7 14 50.0
subroutine 10 13 76.9
pod 5 5 100.0
total 81 156 51.9


line stmt bran cond sub pod time code
1             package Geo::Coder::CA;
2              
3             # See also https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.html for the US for the future
4              
5 6     6   1455812 use strict;
  6         15  
  6         236  
6 6     6   59 use warnings;
  6         13  
  6         331  
7              
8 6     6   36 use Carp;
  6         12  
  6         478  
9 6     6   3033 use Encode;
  6         95060  
  6         697  
10 6     6   3120 use JSON::MaybeXS;
  6         72795  
  6         509  
11 6     6   4889 use LWP::UserAgent;
  6         384664  
  6         317  
12 6     6   66 use URI;
  6         13  
  6         7365  
13              
14             =head1 NAME
15              
16             Geo::Coder::CA - Provides a Geo-Coding functionality using L for both Canada and the US.
17              
18             =head1 VERSION
19              
20             Version 0.15
21              
22             =cut
23              
24             our $VERSION = '0.15';
25              
26             =head1 SYNOPSIS
27              
28             use Geo::Coder::CA;
29              
30             my $geo_coder = Geo::Coder::CA->new();
31             my $location = $geo_coder->geocode(location => '9235 Main St, Richibucto, New Brunswick, Canada');
32              
33             =head1 DESCRIPTION
34              
35             Geo::Coder::CA provides an interface to geocoder.ca.
36             L no longer seems to work.
37              
38             =head1 METHODS
39              
40             =head2 new
41              
42             $geo_coder = Geo::Coder::CA->new();
43             my $ua = LWP::UserAgent->new();
44             $ua->env_proxy(1);
45             $geo_coder = Geo::Coder::CA->new(ua => $ua);
46              
47             =cut
48              
49             sub new {
50 3     3 1 421832 my($class, %args) = @_;
51              
52 3 100       16 if(!defined($class)) {
    50          
53             # Geo::Coder::CA::new() used rather than Geo::Coder::CA->new()
54 1         2 $class = __PACKAGE__;
55             } elsif(ref($class)) {
56             # clone the given object
57 0         0 return bless { %{$class}, %args }, ref($class);
  0         0  
58             }
59              
60 3         10 my $ua = $args{ua};
61 3 50       10 if(!defined($ua)) {
62 3         33 $ua = LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");
63 3         7372 $ua->default_header(accept_encoding => 'gzip,deflate');
64             }
65 3   50     185 my $host = $args{host} || 'geocoder.ca';
66              
67 3         28 return bless { ua => $ua, host => $host }, $class;
68             }
69              
70             =head2 geocode
71              
72             $location = $geo_coder->geocode(location => $location);
73             # @location = $geo_coder->geocode(location => $location);
74              
75             print 'Latitude: ', $location->{'latt'}, "\n";
76             print 'Longitude: ', $location->{'longt'}, "\n";
77              
78             =cut
79              
80             sub geocode {
81 6     6 1 2347 my $self = shift;
82 6         25 my $params = $self->_get_params('location', @_);
83              
84 6         21 my $location = $params->{location};
85 6 50 66     27 if((!defined($location)) || (length($location) == 0)) {
86 6         38 Carp::croak('Usage: geocode(location => $location)');
87 5         2524 return;
88             }
89              
90             # Fail when the input is just a set of numbers
91 0 0       0 if($params->{'location'} !~ /\D/) {
92 0         0 Carp::croak('Usage: ', __PACKAGE__, ': invalid input to geocode(), ', $params->{location});
93 0         0 return;
94             }
95              
96 0 0       0 if (Encode::is_utf8($location)) {
97 0         0 $location = Encode::encode_utf8($location);
98             }
99              
100 0         0 my $uri = URI->new("https://$self->{host}/some_location");
101 0         0 $location =~ s/\s/+/g;
102 0         0 my %query_parameters = ('locate' => $location, 'json' => 1, 'strictmode' => 1);
103 0         0 $uri->query_form(%query_parameters);
104 0         0 my $url = $uri->as_string();
105              
106 0         0 my $res = $self->{ua}->get($url);
107              
108 0 0       0 if($res->is_error()) {
109 0         0 Carp::carp("$url API returned error: ", $res->status_line());
110 0         0 return;
111             }
112             # $res->content_type('text/plain'); # May be needed to decode correctly
113              
114 0         0 my $json = JSON::MaybeXS->new()->utf8();
115 0 0       0 if(my $rc = $json->decode($res->decoded_content())) {
116 0 0       0 if($rc->{'error'}) {
117             # Sorry - you lose the error code, but HTML::GoogleMaps::V3 relies on this
118             # TODO - send patch to the H:G:V3 author
119 0         0 return;
120             }
121 0 0 0     0 if(defined($rc->{'latt'}) && defined($rc->{'longt'})) {
122 0         0 return $rc; # No support for list context, yet
123             }
124              
125             # if($location =~ /^(\w+),\+*(\w+),\+*(USA|US|United States)$/i) {
126             # $query_parameters{'locate'} = "$1 County, $2, $3";
127             # $uri->query_form(%query_parameters);
128             # $url = $uri->as_string();
129             #
130             # $res = $self->{ua}->get($url);
131             #
132             # if($res->is_error()) {
133             # Carp::croak("geocoder.ca API returned error: " . $res->status_line());
134             # return;
135             # }
136             # return $json->decode($res->content());
137             # }
138             }
139              
140             # my @results = @{ $data || [] };
141             # wantarray ? @results : $results[0];
142             }
143              
144             =head2 ua
145              
146             Accessor method to get and set UserAgent object used internally. You
147             can call I for example, to get the proxy information from
148             environment variables:
149              
150             $geo_coder->ua()->env_proxy(1);
151              
152             You can also set your own User-Agent object:
153              
154             my $ua = LWP::UserAgent::Throttled->new();
155             $ua->throttle('geocoder.ca' => 1);
156             $geo_coder->ua($ua);
157              
158             =cut
159              
160             sub ua {
161 0     0 1 0 my $self = shift;
162 0 0       0 if (@_) {
163 0         0 $self->{ua} = shift;
164             }
165 0         0 $self->{ua};
166             }
167              
168             =head2 reverse_geocode
169              
170             $location = $geo_coder->reverse_geocode(latlng => '37.778907,-122.39732');
171              
172             Similar to geocode except it expects a latitude/longitude parameter.
173              
174             =cut
175              
176             sub reverse_geocode {
177 0     0 1 0 my $self = shift;
178 0         0 my $params = $self->_get_params('latlng', @_);
179              
180             my $latlng = $params->{latlng}
181 0 0       0 or Carp::croak('Usage: reverse_geocode(latlng => $latlng)');
182              
183 0         0 return $self->geocode(location => $latlng, reverse => 1);
184             }
185              
186             =head2 run
187              
188             You can also run this module from the command line:
189              
190             perl CA.pm 1600 Pennsylvania Avenue NW, Washington DC
191              
192             =cut
193              
194             __PACKAGE__->run(@ARGV) unless caller();
195              
196             sub run {
197 0     0 1 0 require Data::Dumper;
198              
199 0         0 my $class = shift;
200              
201 0         0 my $location = join(' ', @_);
202              
203 0         0 my @rc = $class->new()->geocode($location);
204              
205 0 0       0 if(scalar(@rc)) {
206 0         0 print Data::Dumper->new([\@rc])->Dump();
207             } else {
208 0         0 die "$0: geo-coding failed";
209             }
210             }
211              
212             # Helper routine to parse the arguments given to a function.
213             # Processes arguments passed to methods and ensures they are in a usable format,
214             # allowing the caller to call the function in anyway that they want
215             # e.g. foo('bar'), foo(arg => 'bar'), foo({ arg => 'bar' }) all mean the same
216             # when called _get_params('arg', @_);
217             sub _get_params
218             {
219 6     6   12 shift; # Discard the first argument (typically $self)
220 6         10 my $default = shift;
221              
222             # Directly return hash reference if the first parameter is a hash reference
223 6 100       25 return $_[0] if(ref $_[0] eq 'HASH');
224              
225 4         8 my %rc;
226 4         9 my $num_args = scalar @_;
227              
228             # Populate %rc based on the number and type of arguments
229 4 100 66     50 if(($num_args == 1) && (defined $default)) {
    50 66        
    100          
    50          
    0          
230             # %rc = ($default => shift);
231 1         6 return { $default => shift };
232             } elsif($num_args == 1) {
233 0         0 Carp::croak('Usage: ', __PACKAGE__, '->', (caller(1))[3], '()');
234             } elsif(($num_args == 0) && (defined($default))) {
235 1         18 Carp::croak('Usage: ', __PACKAGE__, '->', (caller(1))[3], "($default => \$val)");
236             } elsif(($num_args % 2) == 0) {
237 2         18 %rc = @_;
238             } elsif($num_args == 0) {
239 0         0 return;
240             } else {
241 0         0 Carp::croak('Usage: ', __PACKAGE__, '->', (caller(1))[3], '()');
242             }
243              
244 3         44 return \%rc;
245             }
246             =head1 AUTHOR
247              
248             Nigel Horne, C<< >>
249              
250             Based on L.
251              
252             This library is free software; you can redistribute it and/or modify
253             it under the same terms as Perl itself.
254              
255             Lots of thanks to the folks at geocoder.ca.
256              
257             =head1 BUGS
258              
259             Please report any bugs or feature requests to the author.
260             This module is provided as-is without any warranty.
261              
262             Should be called Geo::Coder::NA for North America.
263              
264             =head1 SEE ALSO
265              
266             L, L
267              
268             =head1 LICENSE AND COPYRIGHT
269              
270             Copyright 2017-2025 Nigel Horne.
271              
272             This program is released under the following licence: GPL2
273              
274             =cut
275              
276             1;