File Coverage

blib/lib/Geo/Coder/Geocoder/US.pm
Criterion Covered Total %
statement 29 91 31.8
branch 0 26 0.0
condition 0 3 0.0
subroutine 10 17 58.8
pod n/a
total 39 137 28.4


line stmt bran cond sub pod time code
1             package Geo::Coder::Geocoder::US;
2              
3 1     1   466 use 5.006002;
  1         4  
4              
5 1     1   5 use strict;
  1         1  
  1         15  
6 1     1   4 use warnings;
  1         2  
  1         17  
7              
8 1     1   4 use Carp;
  1         1  
  1         47  
9 1     1   574 use LWP::UserAgent;
  1         39875  
  1         33  
10 1     1   684 use Text::CSV;
  1         16797  
  1         37  
11 1     1   8 use URI;
  1         7  
  1         44  
12             # use URI::Escape qw{ uri_escape };
13              
14             our $VERSION = '0.010';
15              
16 1         284 use constant RETRACTION_MESSAGE =>
17 1     1   8 'Geo::Coder::Geocoder::US has been retracted, because the underlying web site no longer exists';
  1         2  
18              
19             Carp::confess( RETRACTION_MESSAGE );
20             {
21              
22             ## my %valid_arg = map { $_ => 1 } qw{ debug interface ua };
23             my %valid_arg = map { $_ => 1 } qw{ debug ua };
24              
25             sub new {
26 0     0     my ( $class, %args ) = @_;
27 0           carp( RETRACTION_MESSAGE );
28              
29 0 0         ref $class
30             and $class = ref $class;
31              
32             ## exists $args{interface}
33             ## or $args{interface} = 'namedcsv';
34              
35             exists $args{ua}
36 0 0         or $args{ua} = LWP::UserAgent->new();
37              
38 0           my $self = \%args;
39 0           bless $self, $class;
40              
41 0           foreach my $key ( sort keys %args ) {
42 0 0         exists $valid_arg{$key}
43             or croak "Argument $key is invalid";
44 0           $self->$key( $args{$key} );
45             }
46              
47             # Fake up the interface attribute, which I would like to keep
48             # internally for a bit, but am not sure I want to expose.
49              
50 0           $args{interface} = 'namedcsv';
51 0           $args{_interface} = \&_geocode_namedcsv;
52              
53 0           return $self;
54             }
55              
56             }
57              
58             sub debug {
59 0     0     my ( $self, @args ) = @_;
60 0 0         if ( @args ) {
61 0           my $val = $self->{debug} = shift @args;
62 0           my $ua = $self->ua();
63 0 0         my ( $method, @args ) = $val ?
64             ( add_handler => \&_dump ) :
65             ( 'remove_handler' );
66 0           $ua->$method( request_send => @args );
67 0           $ua->$method( response_done => @args );
68 0           return $self;
69             } else {
70 0           return $self->{debug};
71             }
72             }
73              
74             {
75              
76 1     1   6 use constant BASE_URL => 'http://geocoder.us/';
  1         3  
  1         53  
77 1     1   5 use constant DELAY => 15;
  1         2  
  1         655  
78              
79             my $wait_for = time - DELAY;
80             ## my %valid_arg = map { $_ => 1 } qw{ location };
81              
82             sub geocode {
83 0     0     my ( $self, @args ) = @_;
84 0 0         my %parm = @args % 2 ? ( location => @args ) : @args;
85             defined $parm{location}
86 0 0         or croak "You must provide a location to geocode";
87              
88 0           my $uri = URI->new( BASE_URL );
89 0           $uri->path_segments( service => $self->{interface} );
90 0           $uri->query_form( address => $parm{location} );
91              
92             # $parm{location} = uri_escape( $parm{location} );
93              
94 0           my $now = time;
95 0           while ( $wait_for > $now ) {
96 0           sleep $wait_for - $now;
97 0           $now = time;
98             }
99 0           $wait_for = $now + DELAY;
100              
101             # my $rslt = $self->{response} = $self->{ua}->get(
102             # BASE_URL. 'service/' . $self->{interface} .
103             # '?address=' .
104             # $parm{location}
105             # );
106 0           my $rslt = $self->{response} = $self->{ua}->get( $uri );
107 0 0         $rslt->is_success()
108             or return;
109              
110 0           return $self->{_interface}->( $self, $rslt->content() );
111             }
112              
113             }
114              
115             =begin comment
116              
117             sub _geocode_csv {
118             my ( $self, $content ) = @_;
119             my $csv = $self->{_CSV} ||= Text::CSV->new( { binary => 1 } );
120             my @rtn;
121             foreach ( split qr{ \r \n? | \n }smx, $content ) {
122             $csv->parse( $_ )
123             or croak $csv->error_diag();
124             my %data;
125             # TODO field names consistent with Geo::Coder::Many.
126             @data{ qw< lat long address city state zip > } =
127             $csv->fields();
128             defined $data{long}
129             or %data = ( error => $data{lat} );
130             push @rtn, \%data;
131             }
132             return wantarray ? @rtn : $rtn[0];
133             }
134              
135             =end comment
136              
137             =cut
138              
139             sub _geocode_namedcsv {
140 0     0     my ( $self, $content ) = @_;
141 0   0       my $csv = $self->{_CSV} ||= Text::CSV->new( { binary => 1 } );
142 0           my @rtn;
143 0           foreach ( split qr{ \r \n? | \n }smx, $content ) {
144 0 0         $csv->parse( $_ )
145             or croak $csv->error_diag();
146 0           my %data;
147 0           foreach ( $csv->fields() ) {
148 0 0         s/ \A ( \w+ ) = //smx
149             or next;
150 0           $data{$1} = $_;
151             }
152 0           push @rtn, \%data;
153             }
154 0 0         return wantarray ? @rtn : $rtn[0];
155             }
156              
157             =begin comment
158              
159             sub _geocode_rest {
160             my ( $self, $content ) = @_;
161             my $rslt;
162             eval {
163             $rslt = $self->_get_xml_parser->parse( $content );
164             1;
165             } or return [ { error => $content } ];
166             _mung_tree( $rslt );
167             my @rtn = _extract_point( $rslt );
168             return wantarray ? @rtn : $rtn[0];
169             }
170              
171             sub _extract_point {
172             my ( $list ) = @_;
173             my @pts;
174             foreach my $tag ( @{ $list } ) {
175             'ARRAY' eq ref $tag
176             or next;
177             if ( $tag->[0] =~ m/ \b Point \z /smx ) {
178             my %pt;
179             foreach my $datum ( @{ $tag }[ 2 .. $#$tag ] ) {
180             my $name = $datum->[0];
181             $name =~ s/ [^:]* : //smx;
182             $pt{$name} = $datum->[2];
183             }
184             push @pts, \%pt;
185             } else {
186             push @pts, _extract_point( $tag );
187             }
188             }
189             return @pts;
190             }
191              
192             sub _mung_tree {
193             my ( $list ) = @_;
194             my @xfrm;
195             my $inx = 0;
196             while ( $inx <= $#$list ) {
197             my $tag = $list->[$inx++];
198             my $val = $list->[$inx++];
199             if ( 'ARRAY' eq ref $val ) {
200             my @info = @{ $val };
201             my $attr = shift @info;
202             _mung_tree( \@info );
203             splice @info, 0, 0, $tag, $attr;
204             push @xfrm, \@info;
205             } elsif ( ! ref $val && $val =~ m/ \S /smx ) {
206             $val =~ s/ \s+ / /smx;
207             $val =~ s/ \A \s+ //smx;
208             $val =~ s/ \s+ \z //smx;
209             if ( @xfrm && ! ref $xfrm[-1] ) {
210             $xfrm[-1] .= ' ' . $val;
211             } else {
212             push @xfrm, $val;
213             }
214             }
215             }
216             @{ $list } = @xfrm;
217             return $list;
218             }
219              
220             # $ curl 'http://geocoder.us/service/rest?address=1600+Pennsylvania+Ave,+Washington+DC'
221             #
222             #
223             # xmlns:dc="http://purl.org/dc/elements/1.1/"
224             # xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"
225             # xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
226             # >
227             #
228             # 1600 Pennsylvania Ave NW, Washington DC 20502
229             # -77.037684
230             # 38.898748
231             #
232             #
233              
234             {
235              
236             my $xml_parser_loaded;
237             sub _get_xml_parser {
238             my ( $self ) = @_;
239             return ( $self->{_XML_PARSER} ||= do {
240             defined $xml_parser_loaded
241             or eval {
242             require XML::Parser;
243             $xml_parser_loaded = 0;
244             1;
245             }
246             or $xml_parser_loaded = $@;
247             $xml_parser_loaded
248             and croak 'Unable to load XML::Parser';
249              
250             XML::Parser->new( Style => 'Tree' );
251             } );
252             }
253              
254             }
255              
256             sub interface {
257             my ( $self, @args ) = @_;
258             if ( @args ) {
259             my $interface = shift @args;
260             my $code = $self->can( "_geocode_$interface" )
261             or croak "'interface' style '$interface' is not supported";
262             $self->{interface} = $interface;
263             $self->{_interface} = $code;
264             return $self;
265             } else {
266             return $self->{interface};
267             }
268             }
269              
270             =end comment
271              
272             =cut
273              
274             sub response {
275 0     0     my ( $self ) = @_;
276 0           return $self->{response};
277             }
278              
279             sub ua {
280 0     0     my ( $self, @args ) = @_;
281 0 0         if ( @args ) {
282 0           my $ua = shift @args;
283 0           local $@ = undef;
284 0 0         eval { $ua->isa( 'LWP::UserAgent' ); 1 }
  0            
  0            
285             or croak "'ua' must be an LWP::UserAgent object";
286 0           $self->{ua} = $ua;
287 0           return $self;
288             } else {
289 0           return $self->{ua};
290             }
291             }
292              
293             sub _dump {
294 0     0     my ( $msg ) = @_;
295 0           print STDERR "\n", ref $msg, "\n";
296 0           print STDERR $msg->dump();
297 0           return;
298             }
299              
300              
301             1;
302              
303             __END__