File Coverage

blib/lib/Geo/Coder/Bing/Bulk.pm
Criterion Covered Total %
statement 42 116 36.2
branch 11 60 18.3
condition 3 18 16.6
subroutine 10 19 52.6
pod 7 7 100.0
total 73 220 33.1


line stmt bran cond sub pod time code
1             package Geo::Coder::Bing::Bulk;
2              
3 2     2   93912 use strict;
  2         5  
  2         77  
4 2     2   10 use warnings;
  2         5  
  2         65  
5              
6 2     2   12 use Carp qw(croak);
  2         8  
  2         150  
7 2     2   1880 use Encode ();
  2         31774  
  2         49  
8 2     2   3561 use HTTP::Request::Common ();
  2         91792  
  2         69  
9 2     2   2763 use JSON;
  2         41971  
  2         13  
10 2     2   4021 use LWP::UserAgent;
  2         60001  
  2         89  
11 2     2   22 use URI;
  2         7  
  2         3095  
12              
13             our $VERSION = '0.04';
14             $VERSION = eval $VERSION;
15              
16             sub new {
17 4     4 1 1221 my ($class, @params) = @_;
18 4 100       19 my %params = (@params % 2) ? (key => @params) : @params;
19              
20 4 50       13 croak q('key' is required) unless $params{key};
21              
22 4         11 my $self = bless \ %params, $class;
23              
24 4   33     63 $self->ua(
25             $params{ua} || LWP::UserAgent->new(agent => "$class/$VERSION")
26             );
27              
28 4 50       18 if ($self->{debug}) {
    100          
    50          
29 2     0   9 my $dump_sub = sub { $_[0]->dump(maxlength => 0); return };
  0         0  
  0         0  
30 2         5 $self->ua->set_my_handler(request_send => $dump_sub);
31 2         84 $self->ua->set_my_handler(response_done => $dump_sub);
32             }
33             elsif (exists $self->{compress} ? $self->{compress} : 1) {
34 2         5 $self->ua->default_header(accept_encoding => 'gzip,deflate');
35             }
36              
37 4 50 33     174 croak q('https' requires LWP::Protocol::https)
38             if $self->{https} and not $self->ua->is_protocol_supported('https');
39              
40 4         6 $self->{status} = '';
41              
42 4         14 return $self;
43             }
44              
45 0     0 1 0 sub response { $_[0]->{response} }
46              
47             sub ua {
48 10     10 1 3712 my ($self, $ua) = @_;
49 10 100       25 if ($ua) {
50 4 50 33     31 croak q('ua' must be (or derived from) an LWP::UserAgent')
51             unless ref $ua and $ua->isa(q(LWP::UserAgent));
52 4         10 $self->{ua} = $ua;
53             }
54 10         24 return $self->{ua};
55             }
56              
57             sub upload {
58 0     0 1   my $self = shift;
59              
60 0 0 0       my $locs = (1 == @_ and 'ARRAY' eq ref $_[0]) ? $_[0] : \@_;
61 0 0         return unless @$locs;
62              
63 0           my $uri = URI->new(
64             'http://spatial.virtualearth.net/REST/v1/Dataflows/Geocode',
65             );
66 0 0         $uri->scheme('https') if $self->{https};
67 0           $uri->query_form(
68             key => $self->{key},
69             input => 'pipe',
70             );
71              
72 0           my $req = HTTP::Request::Common::POST(
73             $uri, content_type => 'text/plain',
74             );
75              
76 0           my $id = 0;
77 0           for my $loc (@$locs) {
78 0           (my $str = $loc) =~ tr/|\n\r/ /s;
79 0           $req->add_content_utf8("$id||$str\n");
80 0           $id++;
81             }
82             # Prevents LWP warning about wrong content length.
83 0           $req->content_length(length(${$req->content_ref}));
  0            
84              
85 0           my $res = $self->{response} = $self->ua->request($req);
86 0 0         return unless $res->is_success;
87              
88             # Change the content type of the response from 'application/json' so
89             # HTTP::Message will decode the character encoding.
90 0           $res->content_type('text/plain');
91              
92 0           my $content = $res->decoded_content;
93 0 0         return unless $content;
94              
95 0           my $data = eval { from_json($content) };
  0            
96 0 0         return unless $data;
97              
98 0           return $self->{id} = $data->{resourceSets}[0]{resources}[0]{id};
99             }
100              
101             sub is_pending {
102 0     0 1   my ($self) = @_;
103              
104 0           my $status = $self->_status;
105 0 0 0       return 1 if not $status or 'pending' eq $status;
106             }
107              
108             sub _status {
109 0     0     my ($self) = @_;
110              
111 0 0 0       return unless $self->{content} or $self->{id};
112              
113 0           my $uri = URI->new(
114             'http://spatial.virtualearth.net/REST/v1/Dataflows/Geocode/' .
115             $self->{id}
116             );
117 0 0         $uri->scheme('https') if $self->{https};
118 0           $uri->query_form(
119             key => $self->{key},
120             );
121              
122 0           my $res = $self->{response} = $self->ua->get($uri);
123 0 0         return unless $res->is_success;
124              
125             # Change the content type of the response from 'application/json' so
126             # HTTP::Message will decode the character encoding.
127 0           $res->content_type('text/plain');
128              
129 0           my $content = $res->decoded_content;
130 0 0         return unless $content;
131              
132 0           my $data = eval { from_json($content) };
  0            
133 0 0         return unless $data;
134              
135 0           my $resources = $data->{resourceSets}[0]{resources}[0];
136 0 0         return unless $resources->{status};
137              
138 0 0         $self->{failed} = 1 if $resources->{failedEntityCount};
139 0 0         $self->{succeeded} = 1 if $resources->{processedEntityCount};
140              
141 0           return $self->{status} = lc $resources->{status};
142             }
143              
144 0     0 1   sub download { $_[0]->_download('succeeded') }
145 0     0 1   sub failed { $_[0]->_download('failed') }
146              
147             sub _download {
148 0     0     my ($self, $type) = @_;
149              
150 0 0         return unless 'completed' eq $self->{status};
151 0 0         return unless $self->{$type};
152              
153 0           my $uri = URI->new(
154             'http://spatial.virtualearth.net/REST/v1/Dataflows/Geocode/' .
155             $self->{id} . '/output/' . $type
156             );
157 0 0         $uri->scheme('https') if $self->{https};
158 0           $uri->query_form(
159             key => $self->{key},
160             );
161              
162 0           my $res = $self->{response} = $self->ua->get($uri);
163 0 0         return unless $res->is_success;
164              
165 0           my $content_ref = $res->decoded_content(ref => 1);
166 0 0         return unless $$content_ref;
167              
168 0           return $self->_parse_output($content_ref);
169             }
170              
171             # Convert the pipe-delimited output to a data structure conforming
172             # to the data schema described here [1].
173             # [1] http://msdn.microsoft.com/en-us/library/ff701736.aspx
174             my %field_mapping = (
175             0 => 'Id',
176             2 => 'Query',
177             12 => [ Address => 'AddressLine' ],
178             13 => [ Address => 'AdminDistrict' ],
179             14 => [ Address => 'CountryRegion' ],
180             15 => [ Address => 'District' ],
181             16 => [ Address => 'FormattedAddress' ],
182             17 => [ Address => 'Locality' ],
183             18 => [ Address => 'PostalCode' ],
184             19 => [ Address => 'PostalTown' ],
185             20 => [ RooftopLocation => 'Latitude' ],
186             21 => [ RooftopLocation => 'Longitude' ],
187             22 => [ InterpolatedLocation => 'Latitude' ],
188             23 => [ InterpolatedLocation => 'Longitude' ],
189             24 => 'Confidence',
190             25 => 'DisplayName',
191             26 => 'EntityType',
192             27 => 'StatusCode',
193             28 => 'FaultReason',
194             );
195              
196             sub _parse_output {
197 0     0     my ($self, $ref) = @_;
198              
199 0           my @data;
200 0           while ($$ref =~ /([^\n\r]+)/g) {
201 0           my @fields = split '\|', $1, 31;
202 0           my $data = {};
203 0           for my $i (keys %field_mapping) {
204 0           my $val = $fields[$i];
205 0 0         if (length $val) {
206 0           my $key = $field_mapping{$i};
207 0 0         if (ref $key) {
208 0           $data->{$key->[0]}{$key->[1]} = $val;
209             }
210             else {
211 0           $data->{$key} = $val;
212             }
213             }
214             }
215 0           push @data, $data;
216             }
217              
218 0           return \@data;
219             }
220              
221              
222             1;
223              
224             __END__