File Coverage

blib/lib/Geo/USCensus/Geocoding.pm
Criterion Covered Total %
statement 50 59 84.7
branch 7 14 50.0
condition 4 13 30.7
subroutine 7 7 100.0
pod 1 1 100.0
total 69 94 73.4


line stmt bran cond sub pod time code
1             package Geo::USCensus::Geocoding;
2              
3 3     3   70174 use strict;
  3         6  
  3         111  
4 3     3   14 use warnings;
  3         4  
  3         71  
5              
6 3     3   4406 use LWP::UserAgent;
  3         571245  
  3         120  
7 3     3   485347 use HTTP::Request::Common;
  3         6733  
  3         429  
8 3     3   1719 use Geo::USCensus::Geocoding::Result;
  3         11  
  3         119  
9 3     3   3210 use Text::CSV;
  3         39728  
  3         23  
10              
11             =head1 NAME
12              
13             Geo::USCensus::Geocoding - The U.S. Census Bureau geocoding service
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.01';
22             our $DEBUG = 0;
23              
24             =head1 SYNOPSIS
25              
26             use Geo::USCensus::Geocoding;
27              
28             my $request = {
29             # required fields
30             street => '123 Main Street',
31             city => 'San Francisco', # city
32             state => 'CA', # state
33             # optional fields
34             zip => '93102', # zip code
35             benchmark => 'Public_AR_ACS2013', # default is "Public_AR_Current"
36             vintage => 'Census2010_ACS2013', # default is "Current_Current"
37              
38             debug => 1, # will print the URL and some other info
39             };
40             my $result = Geo::USCensus::Geocoding->query($request);
41              
42             if ($result->is_match) {
43             print $result->address,"\n",
44             $result->latitude,", ",$result->longitude,"\n",
45             $result->censustract,"\n";
46             } else {
47             print "No match.\n";
48             }
49              
50             =head1 CLASS METHODS
51              
52             =head2 query HASHREF
53              
54             Send a request to the web service. See
55             L for API documentation. This
56             package will always use the batch method (which seems to be more reliable,
57             as of 2015) and the Geographies return type.
58              
59             Returns an object of class Geo::USCensus::Geocoding::Result.
60              
61             =cut
62              
63             my $ua = LWP::UserAgent->new;
64             my $url = 'http://geocoding.geo.census.gov/geocoder/geographies/addressbatch';
65              
66             my $csv = Text::CSV->new({eol => "\n", binary => 1});
67              
68             # for a current list of benchmark/vintage IDs, download
69             # http://geocoding.geo.census.gov/geocoder/benchmarks
70             # http://geocoding.geo.census.gov/geocoder/vintages?benchmark=
71             # with Accept: application/json
72              
73             sub query {
74 2     2 1 598 my $class = shift;
75 2         15 my %opt = (
76             returntype => 'geographies',
77             benchmark => 4, # "Current"
78             vintage => 4, # "Current"
79             );
80 2 50       13 if (ref $_[0] eq 'HASH') {
81 0         0 %opt = (%opt, %{ $_[0] });
  0         0  
82             } else {
83 2         24 %opt = (%opt, @_);
84             }
85              
86 2   50     20 $DEBUG = $opt{debug} || 0;
87              
88 2         22 my $result = Geo::USCensus::Geocoding::Result->new;
89              
90 2         3551 my @row = ( 1 ); # first element = row identifier
91             # at some point support multiple rows in a single query?
92 2 50       12 if (!$opt{street}) {
93 0         0 $result->error_message("Street address is required.");
94 0         0 return $result;
95             }
96 2 0 0     15 if (!$opt{zip} and (!$opt{city} or !$opt{state})) {
      33        
97 0         0 $result->error_message("Either city/state or zip code is required.");
98 0         0 return $result;
99             }
100 2         8 foreach (qw(street city state zip)) {
101 8   50     27 push @row, $opt{$_} || '';
102             }
103              
104 2         16 $csv->combine(@row);
105 2 50       373 warn "Sending:\n".$csv->string."\n" if $DEBUG;
106              
107             # they are not picky about content types, Accept headers, etc., but
108             # the uploaded file must have a _name_.
109 2         20 my $resp = $ua->request(POST $url,
110             'Content_Type' => 'form-data',
111             'Content' => [ benchmark => $opt{benchmark},
112             vintage => $opt{vintage},
113             returntype => $opt{returntype},
114             addressFile => [ undef, 'upload.csv',
115             Content => $csv->string
116             ],
117             ],
118             );
119 2 50       1600724 if ( $resp->is_success ) {
120 2         42 $result->content($resp->content);
121 2         67 my $status = $csv->parse($resp->content);
122 2         1267 my @fields = $csv->fields;
123 2 50 33     45 if (!$status or @fields < 3) {
124 0         0 $result->error_message("Unable to parse response:\n" . $resp->content);
125 0         0 return $result;
126             }
127 2 100       10 if ( $fields[2] eq 'Match' ) {
128 1         5 $result->is_match(1);
129 1         4 $result->match_level($fields[3]);
130 1         5 $result->address($fields[4]);
131 1         3 my ($long, $lat) = split(',', $fields[5]);
132 1         4 $result->longitude($long);
133 1         21 $result->latitude($lat);
134 1         3 $result->state($fields[8]);
135 1         4 $result->county($fields[9]);
136 1         7 $result->tract($fields[10]);
137 1         5 $result->block($fields[11]);
138             } else {
139 1         10 $result->is_match(0);
140             }
141             } else {
142 0         0 $result->error_message( $resp->status_line );
143             }
144              
145 2         71 return $result;
146             }
147              
148             =head1 AUTHOR
149              
150             Mark Wells, C<< >>
151              
152             =head1 SUPPORT
153              
154             Commercial support for this module is available from Freeside Internet
155             Services:
156              
157             L
158              
159             =back
160              
161              
162             =head1 LICENSE AND COPYRIGHT
163              
164             Copyright (C) 2014 Mark Wells.
165              
166             This program is free software; you can redistribute it and/or modify it
167             under the terms of either: the GNU General Public License as published
168             by the Free Software Foundation; or the Artistic License.
169              
170             See http://dev.perl.org/licenses/ for more information.
171              
172             =cut
173              
174             1;