File Coverage

blib/lib/Geo/Coder/US/Census.pm
Criterion Covered Total %
statement 42 83 50.6
branch 5 30 16.6
condition 1 5 20.0
subroutine 12 15 80.0
pod 5 5 100.0
total 65 138 47.1


line stmt bran cond sub pod time code
1             package Geo::Coder::US::Census;
2              
3 4     4   491075 use strict;
  4         39  
  4         114  
4 4     4   21 use warnings;
  4         11  
  4         95  
5              
6 4     4   22 use Carp;
  4         8  
  4         189  
7 4     4   2087 use Encode;
  4         57787  
  4         343  
8 4     4   2219 use JSON::MaybeXS;
  4         22418  
  4         238  
9 4     4   1751 use HTTP::Request;
  4         81487  
  4         177  
10 4     4   2895 use LWP::UserAgent;
  4         117807  
  4         167  
11 4     4   1936 use LWP::Protocol::https;
  4         392681  
  4         191  
12 4     4   31 use URI;
  4         10  
  4         102  
13 4     4   2377 use Geo::StreetAddress::US;
  4         208126  
  4         3833  
14              
15             =head1 NAME
16              
17             Geo::Coder::US::Census - Provides a Geo-Coding functionality for the US using L
18              
19             =head1 VERSION
20              
21             Version 0.06
22              
23             =cut
24              
25             our $VERSION = '0.06';
26              
27             =head1 SYNOPSIS
28              
29             use Geo::Coder::US::Census;
30              
31             my $geo_coder = Geo::Coder::US::Census->new();
32             my $location = $geo_coder->geocode(location => '4600 Silver Hill Rd., Suitland, MD');
33             # Sometimes the server gives a 500 error on this
34             $location = $geo_coder->geocode(location => '4600 Silver Hill Rd., Suitland, MD, USA');
35              
36             =head1 DESCRIPTION
37              
38             Geo::Coder::US::Census provides an interface to geocoding.geo.census.gov. Geo::Coder::US no longer seems to work.
39              
40             =head1 METHODS
41              
42             =head2 new
43              
44             $geo_coder = Geo::Coder::US::Census->new();
45             my $ua = LWP::UserAgent->new();
46             $ua->env_proxy(1);
47             $geo_coder = Geo::Coder::US::Census->new(ua => $ua);
48              
49             =cut
50              
51             sub new {
52 2     2 1 2977 my($class, %param) = @_;
53              
54 2         8 my $ua = $param{ua};
55 2 50       10 if(!defined($ua)) {
56 2         24 $ua = LWP::UserAgent->new(agent => __PACKAGE__ . "/$VERSION");
57 2         6338 $ua->default_header(accept_encoding => 'gzip,deflate');
58             }
59 2   50     140 my $host = $param{host} || 'geocoding.geo.census.gov/geocoder/locations/address';
60              
61 2         20 return bless { ua => $ua, host => $host }, $class;
62             }
63              
64             =head2 geocode
65              
66             $location = $geo_coder->geocode(location => $location);
67             # @location = $geo_coder->geocode(location => $location);
68              
69             print 'Latitude: ', $location->{'latt'}, "\n";
70             print 'Longitude: ', $location->{'longt'}, "\n";
71              
72             =cut
73              
74             sub geocode {
75 1     1 1 695 my $self = shift;
76 1         2 my %param;
77              
78 1 50       8 if(ref($_[0]) eq 'HASH') {
    50          
    50          
79 0         0 %param = %{$_[0]};
  0         0  
80             } elsif(ref($_[0])) {
81 0         0 Carp::croak('Usage: geocode(location => $location)');
82             } elsif(@_ % 2 == 0) {
83 1         3 %param = @_;
84             } else {
85 0         0 $param{location} = shift;
86             }
87              
88             my $location = $param{location}
89 1 50       28 or Carp::croak('Usage: geocode(location => $location)');
90              
91 0 0         if (Encode::is_utf8($location)) {
92 0           $location = Encode::encode_utf8($location);
93             }
94              
95 0 0         if($location =~ /,?(.+),\s*(United States|US|USA)$/i) {
96 0           $location = $1;
97             }
98              
99             # Remove county from the string, if that's included
100             # Assumes not more than one town in a state with the same name
101             # in different counties - but the census Geo-Coding doesn't support that
102             # anyway
103             # Some full state names include spaces, e.g South Carolina
104             # Some roads include full stops, e.g. S. West Street
105 0 0         if($location =~ /^(\d+\s+[\w\s\.]+),\s*([\w\s]+),\s*[\w\s]+,\s*([A-Za-z\s]+)$/) {
106 0           $location = "$1, $2, $3";
107             }
108              
109 0           my $uri = URI->new("https://$self->{host}");
110 0           my $hr = Geo::StreetAddress::US->parse_address($location);
111              
112 0 0 0       if((!defined($hr->{'city'})) || (!defined($hr->{'state'}))) {
113 0           Carp::carp(__PACKAGE__ . ": city and state are mandatory ($location)");
114 0           return;
115             }
116              
117             my %query_parameters = (
118             'benchmark' => 'Public_AR_Current',
119             'city' => $hr->{'city'},
120             'format' => 'json',
121 0           'state' => $hr->{'state'},
122             );
123 0 0         if($hr->{'street'}) {
124 0 0         if($hr->{'number'}) {
125 0           $query_parameters{'street'} = $hr->{'number'} . ' ' . $hr->{'street'} . ' ' . $hr->{'type'};
126             } else {
127 0           $query_parameters{'street'} = $hr->{'street'} . ' ' . $hr->{'type'};
128             }
129 0 0         if($hr->{'suffix'}) {
130 0           $query_parameters{'street'} .= ' ' . $hr->{'suffix'};
131             }
132             }
133              
134 0           $uri->query_form(%query_parameters);
135 0           my $url = $uri->as_string();
136              
137 0           my $res = $self->{ua}->get($url);
138              
139 0 0         if($res->is_error()) {
140 0           Carp::carp("$url API returned error: " . $res->status_line());
141 0           return;
142             }
143              
144 0           my $json = JSON::MaybeXS->new->utf8();
145 0           return $json->decode($res->decoded_content());
146              
147             # my @results = @{ $data || [] };
148             # wantarray ? @results : $results[0];
149             }
150              
151             =head2 ua
152              
153             Accessor method to get and set UserAgent object used internally. You
154             can call I for example, to get the proxy information from
155             environment variables:
156              
157             $geo_coder->ua()->env_proxy(1);
158              
159             You can also set your own User-Agent object:
160              
161             $geo_coder->ua(LWP::UserAgent::Throttled->new());
162              
163             =cut
164              
165             sub ua {
166 0     0 1   my $self = shift;
167 0 0         if (@_) {
168 0           $self->{ua} = shift;
169             }
170 0           $self->{ua};
171             }
172              
173             =head2 reverse_geocode
174              
175             # $location = $geo_coder->reverse_geocode(latlng => '37.778907,-122.39732');
176              
177             # Similar to geocode except it expects a latitude/longitude parameter.
178              
179             Not supported.
180              
181             =cut
182              
183             sub reverse_geocode {
184             # my $self = shift;
185              
186             # my %param;
187             # if (@_ % 2 == 0) {
188             # %param = @_;
189             # } else {
190             # $param{latlng} = shift;
191             # }
192              
193             # my $latlng = $param{latlng}
194             # or Carp::croak("Usage: reverse_geocode(latlng => \$latlng)");
195              
196             # return $self->geocode(location => $latlng, reverse => 1);
197 0     0 1   Carp::croak('Reverse geocode is not supported');
198             }
199              
200             =head2 run
201              
202             You can also run this module from the command line:
203              
204             perl Census.pm 1600 Pennsylvania Avenue NW, Washington DC
205              
206             =cut
207              
208             __PACKAGE__->run(@ARGV) unless caller();
209              
210             sub run {
211 0     0 1   require Data::Dumper;
212              
213 0           my $class = shift;
214              
215 0           my $location = join(' ', @_);
216              
217 0           my @rc = $class->new()->geocode($location);
218              
219 0 0         die "$0: geocoding failed" unless(scalar(@rc));
220              
221 0           print Data::Dumper->new([\@rc])->Dump();
222             }
223              
224             =head1 AUTHOR
225              
226             Nigel Horne
227              
228             Based on L.
229              
230             This library is free software; you can redistribute it and/or modify
231             it under the same terms as Perl itself.
232              
233             Lots of thanks to the folks at geocoding.geo.census.gov.
234              
235             =head1 BUGS
236              
237             Should be called Geo::Coder::NA for North America.
238              
239             =head1 SEE ALSO
240              
241             L, L
242              
243             https://www.census.gov/data/developers/data-sets/Geocoding-services.html
244              
245             =head1 LICENSE AND COPYRIGHT
246              
247             Copyright 2017-2023 Nigel Horne.
248              
249             This program is released under the following licence: GPL2
250              
251             =cut
252              
253             1;