File Coverage

blib/lib/Geo/Coder/List.pm
Criterion Covered Total %
statement 26 351 7.4
branch 4 272 1.4
condition 3 66 4.5
subroutine 8 15 53.3
pod 7 7 100.0
total 48 711 6.7


line stmt bran cond sub pod time code
1             package Geo::Coder::List;
2              
3 21     21   2656162 use 5.10.1;
  21         279  
4              
5 21     21   121 use warnings;
  21         42  
  21         586  
6 21     21   120 use strict;
  21         60  
  21         629  
7 21     21   114 use Carp;
  21         56  
  21         1226  
8 21     21   142 use Time::HiRes;
  21         40  
  21         213  
9 21     21   12749 use HTML::Entities;
  21         125273  
  21         1495  
10              
11 21     21   152 use constant DEBUG => 0; # Default debugging level
  21         43  
  21         74545  
12              
13             # TODO: investigate Geo, Coder::ArcGIS
14             # TODO: return a Geo::Location::Point object all the time
15              
16             =head1 NAME
17              
18             Geo::Coder::List - Call many Geo-Coders
19              
20             =head1 VERSION
21              
22             Version 0.31
23              
24             =cut
25              
26             our $VERSION = '0.31';
27             our %locations; # L1 cache, always used
28              
29             =head1 SYNOPSIS
30              
31             L
32             and
33             L
34             are great routines but neither quite does what I want.
35             This module's primary use is to allow many backends to be used by
36             L
37              
38             =head1 SUBROUTINES/METHODS
39              
40             =head2 new
41              
42             Creates a Geo::Coder::List object.
43              
44             Takes an optional argument 'cache' which takes an cache object that supports
45             get() and set() methods.
46             Takes an optional argument 'debug',
47             the higher the number,
48             the more debugging.
49             The licences of some geo coders,
50             such as Google,
51             specifically prohibit caching API calls,
52             so be careful to only use with those services that allow it.
53              
54             use Geo::Coder::List;
55             use CHI;
56              
57             my $geocoder->new(cache => CHI->new(driver => 'Memory', global => 1));
58              
59             =cut
60              
61             sub new {
62 4     4 1 628 my $proto = shift;
63 4   100     17 my $class = ref($proto) || $proto;
64              
65 4 50       13 my %args = (ref($_[0]) eq 'HASH') ? %{$_[0]} : @_;
  0         0  
66              
67 4 100       11 if(!defined($class)) {
    50          
68             # Using Geo::Coder::List::new(), not Geo::Coder::List->new()
69             # carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
70             # return;
71 1         35 $class = __PACKAGE__;
72             } elsif(ref($class)) {
73             # clone the given object
74 0         0 return bless { %{$class}, %args }, ref($class);
  0         0  
75             }
76              
77 4         41 return bless { debug => DEBUG, %args, geo_coders => [] }, $class;
78             }
79              
80             =head2 push
81              
82             Add an encoder to list of encoders.
83              
84             use Geo::Coder::List;
85             use Geo::Coder::GooglePlaces;
86             # ...
87             my $list = Geo::Coder::List->new()->push(Geo::Coder::GooglePlaces->new());
88              
89             Different encoders can be preferred for different locations.
90             For example this code uses geocode.ca for Canada and US addresses,
91             and OpenStreetMap for other places:
92              
93             my $geo_coderlist = Geo::Coder::List->new()
94             ->push({ regex => qr/(Canada|USA|United States)$/, geocoder => Geo::Coder::CA->new() })
95             ->push(Geo::Coder::OSM->new());
96              
97             # Uses Geo::Coder::CA, and if that fails uses Geo::Coder::OSM
98             my $location = $geo_coderlist->geocode(location => '1600 Pennsylvania Ave NW, Washington DC, USA');
99             # Only uses Geo::Coder::OSM
100             if($location = $geo_coderlist->geocode('10 Downing St, London, UK')) {
101             print 'The prime minister lives at co-ordinates ',
102             $location->{geometry}{location}{lat}, ',',
103             $location->{geometry}{location}{lng}, "\n";
104             }
105              
106             # It is also possible to limit the number of enquires used by a particular encoder
107             $geo_coderlist->push({ geocoder => Geo::Coder::GooglePlaces->new(key => '1234'), limit => 100) });
108              
109             =cut
110              
111             sub push {
112 0     0 1   my($self, $geocoder) = @_;
113              
114 0           push @{$self->{geocoders}}, $geocoder;
  0            
115              
116 0           return $self;
117             }
118              
119             =head2 geocode
120              
121             Runs geocode on all of the loaded drivers.
122             See L for an explanation.
123              
124             The name of the Geo-Coder that gave the result is put into the geocode element of the
125             return value,
126             if the value was retrieved from the cache the value will be undefined.
127              
128             if(defined($location->{'geocoder'})) {
129             print 'Location information retrieved using ', $location->{'geocoder'}, "\n";
130             }
131             =cut
132              
133             sub geocode {
134 0     0 1   my $self = shift;
135 0           my %params;
136              
137 0 0         if(ref($_[0]) eq 'HASH') {
    0          
    0          
138 0           %params = %{$_[0]};
  0            
139             } elsif(ref($_[0])) {
140 0           Carp::carp(__PACKAGE__, ' usage: geocode(location => $location) given ', ref($_[0]));
141 0           return;
142             } elsif(@_ % 2 == 0) {
143 0           %params = @_;
144             } else {
145 0           $params{'location'} = shift;
146             }
147              
148 0           my $location = $params{'location'};
149              
150 0 0 0       if((!defined($location)) || (length($location) == 0)) {
151 0           Carp::carp(__PACKAGE__, ' usage: geocode(location => $location)');
152 0           return;
153             }
154              
155 0           $location =~ s/\s\s+/ /g;
156 0           $location = decode_entities($location);
157 0 0         print "location: $location\n" if($self->{'debug'});
158              
159 0           my @call_details = caller(0);
160 0 0 0       if((!wantarray) && (my $rc = $self->_cache($location))) {
161 0 0         if(ref($rc) eq 'ARRAY') {
162 0           $rc = $rc->[0];
163             }
164 0 0         if(ref($rc) eq 'HASH') {
165 0           delete $rc->{'geocoder'};
166 0           my $log = {
167             line => $call_details[2],
168             location => $location,
169             timetaken => 0,
170             wantarray => wantarray,
171             result => $rc
172             };
173 0           CORE::push @{$self->{'log'}}, $log;
  0            
174 0 0         print __PACKAGE__, ': ', __LINE__, ": cached\n" if($self->{'debug'});
175 0           return $rc;
176             }
177             }
178 0 0 0       if(defined($self->_cache($location)) && (ref($self->_cache($location)) eq 'ARRAY') && (my @rc = @{$self->_cache($location)})) {
  0   0        
179 0 0         if(scalar(@rc)) {
180 0           my $allempty = 1;
181 0           foreach (@rc) {
182 0 0         if(ref($_) eq 'HASH') {
    0          
183 0 0         if(defined($_->{geometry}{location}{lat})) {
184 0           $allempty = 0;
185 0           delete $_->{'geocoder'};
186             } else {
187 0           delete $_->{'geometry'};
188             }
189             } elsif(ref($_) eq 'Geo::Location::Point') {
190 0           $allempty = 0;
191 0           delete $_->{'geocoder'};
192             }
193             }
194 0           my $log = {
195             line => $call_details[2],
196             location => $location,
197             timetaken => 0,
198             wantarray => wantarray,
199             result => \@rc
200             };
201 0           CORE::push @{$self->{'log'}}, $log;
  0            
202 0 0         print __PACKAGE__, ': ', __LINE__, ": cached\n" if($self->{'debug'});
203 0 0         if($allempty) {
204 0           return;
205             }
206 0 0         return (wantarray) ? @rc : $rc[0];
207             }
208             }
209              
210 0           my $error;
211              
212 0           ENCODER: foreach my $g(@{$self->{geocoders}}) {
  0            
213 0           my $geocoder = $g;
214 0 0         if(ref($geocoder) eq 'HASH') {
215 0 0 0       if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
216 0 0         print "limit: $limit\n" if($self->{'debug'});
217 0 0         if($limit <= 0) {
218 0           next;
219             }
220 0           $geocoder->{'limit'}--;
221             }
222 0 0         if(my $regex = $geocoder->{'regex'}) {
223 0 0         print 'consider ', ref($geocoder->{geocoder}), ": $regex\n" if($self->{'debug'});
224 0 0         if($location !~ $regex) {
225 0           next;
226             }
227             }
228 0           $geocoder = $g->{'geocoder'};
229             }
230 0           my @rc;
231 0           my $timetaken = Time::HiRes::time();
232 0           eval {
233             # e.g. over QUERY LIMIT with this one
234             # TODO: remove from the list of geocoders
235 0 0         print 'trying ', ref($geocoder), "\n" if($self->{'debug'});
236 0 0         if(ref($geocoder) eq 'Geo::GeoNames') {
237 0 0         print 'username => ', $geocoder->username(), "\n" if($self->{'debug'});
238 0 0         die 'lost username' if(!defined($geocoder->username()));
239 0           @rc = $geocoder->geocode($location);
240             } else {
241 0           @rc = $geocoder->geocode(%params);
242             }
243             };
244 0 0         if($@) {
245 0           my $log = {
246             line => $call_details[2],
247             location => $location,
248             geocoder => ref($geocoder),
249             timetaken => Time::HiRes::time() - $timetaken,
250             wantarray => wantarray,
251             error => $@
252             };
253 0           CORE::push @{$self->{'log'}}, $log;
  0            
254 0           Carp::carp(ref($geocoder), " '$location': $@");
255 0           $error = $@;
256 0           next ENCODER;
257             }
258 0           $timetaken = Time::HiRes::time() - $timetaken;
259 0 0 0       if((scalar(@rc) == 0) ||
      0        
      0        
      0        
260 0           ((ref($rc[0]) eq 'HASH') && (scalar(keys %{$rc[0]}) == 0)) ||
261 0           ((ref($rc[0]) eq 'ARRAY') && (scalar(keys %{$rc[0][0]}) == 0))) {
262 0           my $log = {
263             line => $call_details[2],
264             location => $location,
265             timetaken => $timetaken,
266             geocoder => ref($geocoder),
267             wantarray => wantarray,
268             result => 'not found',
269             };
270 0           CORE::push @{$self->{'log'}}, $log;
  0            
271 0           next ENCODER;
272             }
273 0           POSSIBLE_LOCATION: foreach my $l(@rc) {
274 0 0         if(ref($l) eq 'ARRAY') {
275             # Geo::GeoNames
276             # FIXME: should consider all locations in the array
277 0           $l = $l->[0];
278             }
279 0 0         if(!defined($l)) {
280 0           my $log = {
281             line => $call_details[2],
282             location => $location,
283             timetaken => $timetaken,
284             geocoder => ref($geocoder),
285             wantarray => wantarray,
286             result => 'not found',
287             };
288 0           CORE::push @{$self->{'log'}}, $log;
  0            
289 0           next ENCODER;
290             }
291             print ref($geocoder), ': ',
292 0 0         Data::Dumper->new([\$l])->Dump() if($self->{'debug'} >= 2);
293 0 0         last if(ref($l) eq 'Geo::Location::Point');
294 0 0         next if(ref($l) ne 'HASH');
295 0 0         if($l->{'error'}) {
296             my $log = {
297             line => $call_details[2],
298             location => $location,
299             timetaken => $timetaken,
300             geocoder => ref($geocoder),
301             wantarray => wantarray,
302 0           error => $l->{'error'}
303             };
304 0           CORE::push @{$self->{'log'}}, $log;
  0            
305 0           next ENCODER;
306             } else {
307             # Try to create a common interface, helps with HTML::GoogleMaps::V3
308 0 0         if(!defined($l->{geometry}{location}{lat})) {
309 0           my ($lat, $long);
310 0 0 0       if($l->{lat} && defined($l->{lon})) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
311             # OSM/RandMcNalley
312             # This would have been nice, but it doesn't compile
313             # ($lat, $long) = $l->{'lat', 'lon'};
314 0           $lat = $l->{lat};
315 0           $long = $l->{lon};
316             } elsif($l->{BestLocation}) {
317             # Bing
318 0           $lat = $l->{BestLocation}->{Coordinates}->{Latitude};
319 0           $long = $l->{BestLocation}->{Coordinates}->{Longitude};
320             } elsif($l->{point}) {
321             # Bing
322 0           $lat = $l->{point}->{coordinates}[0];
323 0           $long = $l->{point}->{coordinates}[1];
324             } elsif($l->{latt}) {
325             # geocoder.ca
326 0           $lat = $l->{latt};
327 0           $long = $l->{longt};
328             } elsif($l->{latitude}) {
329             # postcodes.io
330             # Geo::Coder::Free
331 0           $lat = $l->{latitude};
332 0           $long = $l->{longitude};
333 0 0         if(my $type = $l->{'local_type'}) {
334 0           $l->{'type'} = lcfirst($type); # e.g. village
335             }
336             } elsif($l->{'properties'}{'geoLatitude'}) {
337             # ovi
338 0           $lat = $l->{properties}{geoLatitude};
339 0           $long = $l->{properties}{geoLongitude};
340             } elsif($l->{'results'}[0]->{'geometry'}) {
341 0 0         if($l->{'results'}[0]->{'geometry'}->{'location'}) {
342             # DataScienceToolkit
343 0           $lat = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lat'};
344 0           $long = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lng'};
345             } else {
346             # OpenCage
347 0           $lat = $l->{'results'}[0]->{'geometry'}->{'lat'};
348 0           $long = $l->{'results'}[0]->{'geometry'}->{'lng'};
349             }
350             } elsif($l->{'RESULTS'}) {
351             # GeoCodeFarm
352 0           $lat = $l->{'RESULTS'}[0]{'COORDINATES'}{'latitude'};
353 0           $long = $l->{'RESULTS'}[0]{'COORDINATES'}{'longitude'};
354             } elsif(defined($l->{result}{addressMatches}[0]->{coordinates}{y})) {
355             # US Census
356             # This would have been nice, but it doesn't compile
357             # ($lat, $long) = $l->{result}{addressMatches}[0]->{coordinates}{y, x};
358 0           $lat = $l->{result}{addressMatches}[0]->{coordinates}{y};
359 0           $long = $l->{result}{addressMatches}[0]->{coordinates}{x};
360             } elsif($l->{lat}) {
361             # Geo::GeoNames
362 0           $lat = $l->{lat};
363 0           $long = $l->{lng};
364             } elsif($l->{features}) {
365             # Geo::Coder::Mapbox
366 0           $lat = $l->{features}[0]->{center}[1];
367 0           $long = $l->{features}[0]->{center}[0];
368             }
369              
370 0 0 0       if(defined($lat) && defined($long)) {
371 0           $l->{geometry}{location}{lat} = $lat;
372 0           $l->{geometry}{location}{lng} = $long;
373             } else {
374 0           delete $l->{'geometry'};
375             }
376              
377 0 0         if($l->{'standard'}{'countryname'}) {
378             # geocoder.xyz
379 0           $l->{'address'}{'country'} = $l->{'standard'}{'countryname'};
380             }
381             }
382 0 0         if(defined($l->{geometry}{location}{lat})) {
383 0 0         print $l->{geometry}{location}{lat}, '/', $l->{geometry}{location}{lng}, "\n" if($self->{'debug'});
384 0           $l->{geocoder} = $geocoder;
385 0   0       $l->{'lat'} //= $l->{geometry}{location}{lat};
386 0   0       $l->{'lng'} //= $l->{geometry}{location}{lng};
387 0           my $log = {
388             line => $call_details[2],
389             location => $location,
390             timetaken => $timetaken,
391             geocoder => ref($geocoder),
392             wantarray => wantarray,
393             result => $l
394             };
395 0           CORE::push @{$self->{'log'}}, $log;
  0            
396 0           last POSSIBLE_LOCATION;
397             }
398             }
399             }
400              
401 0 0         if(scalar(@rc)) {
402 0 0         print 'Number of matches from ', ref($geocoder), ': ', scalar(@rc), "\n" if($self->{'debug'});
403 0 0         print Data::Dumper->new([\@rc])->Dump() if($self->{'debug'} >= 2);
404 0 0         if(defined($rc[0])) { # check it's not an empty hash
405 0 0         if(wantarray) {
406 0           $self->_cache($location, \@rc);
407 0           return @rc;
408             }
409 0           $self->_cache($location, $rc[0]);
410 0           return $rc[0];
411             }
412             }
413             }
414             # Can't do this because we need to return undef in this case
415             # if($error) {
416             # return { error => $error };
417             # }
418 0 0         print "No matches\n" if($self->{'debug'});
419 0 0         if(wantarray) {
420 0           $self->_cache($location, ());
421 0           return ();
422             }
423 0           $self->_cache($location, undef);
424             }
425              
426             =head2 ua
427              
428             Accessor method to set the UserAgent object used internally by each of the Geo-Coders.
429             You can call I,
430             for example,
431             to set the proxy information from environment variables:
432              
433             my $geocoder_list = Geo::Coder::List->new();
434             my $ua = LWP::UserAgent->new();
435             $ua->env_proxy(1);
436             $geocoder_list->ua($ua);
437              
438             Note that unlike Geo::Coders there is no read method since that would be pointless.
439              
440             =cut
441              
442             sub ua {
443 0     0 1   my $self = shift;
444              
445 0 0         if(my $ua = shift) {
446 0           foreach my $g(@{$self->{geocoders}}) {
  0            
447 0           my $geocoder = $g;
448 0 0         if(ref($g) eq 'HASH') {
449 0           $geocoder = $g->{'geocoder'};
450 0 0         if(!defined($geocoder)) {
451 0           Carp::croak('No geocoder found');
452             }
453             }
454 0           $geocoder->ua($ua);
455             }
456 0           return $ua;
457             }
458             }
459              
460             =head2 reverse_geocode
461              
462             Similar to geocode except it expects a latitude/longitude parameter.
463              
464             print $geocoder_list->reverse_geocode(latlng => '37.778907,-122.39732');
465              
466             =cut
467              
468             sub reverse_geocode {
469 0     0 1   my $self = shift;
470 0           my %params;
471              
472 0 0         if(ref($_[0]) eq 'HASH') {
    0          
    0          
473 0           %params = %{$_[0]};
  0            
474             } elsif(ref($_[0])) {
475 0           Carp::croak('Usage: reverse_geocode(location => $location)');
476             } elsif(@_ % 2 == 0) {
477 0           %params = @_;
478             } else {
479 0           $params{'latlng'} = shift;
480             }
481              
482 0 0         my $latlng = $params{'latlng'}
483             or Carp::croak('Usage: reverse_geocode(latlng => $location)');
484              
485 0           my $latitude;
486             my $longitude;
487              
488 0 0         if($latlng) {
489 0           ($latitude, $longitude) = split(/,/, $latlng);
490             } else {
491 0   0       $latitude //= $params{'lat'};
492 0   0       $longitude //= $params{'lon'};
493 0   0       $longitude //= $params{'long'};
494             }
495              
496 0 0         if(my $rc = $self->_cache($latlng)) {
497 0           return $rc;
498             }
499              
500 0           foreach my $g(@{$self->{geocoders}}) {
  0            
501 0           my $geocoder = $g;
502 0 0         if(ref($geocoder) eq 'HASH') {
503 0 0 0       if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
504 0 0         print "limit: $limit\n" if($self->{'debug'});
505 0 0         if($limit <= 0) {
506 0           next;
507             }
508 0           $geocoder->{'limit'}--;
509             }
510 0           $geocoder = $g->{'geocoder'};
511             }
512 0 0         print 'trying ', ref($geocoder), "\n" if($self->{'debug'});
513 0 0 0       if(wantarray) {
    0          
514 0           my @rc;
515 0 0         if(my @locs = $geocoder->reverse_geocode(%params)) {
516 0 0         print Data::Dumper->new([\@locs])->Dump() if($self->{'debug'} >= 2);
517 0           foreach my $loc(@locs) {
518 0 0         if(my $name = $loc->{'display_name'}) {
    0          
519             # OSM
520 0           CORE::push @rc, $name;
521             } elsif($loc->{'city'}) {
522             # Geo::Coder::CA
523 0           my $name;
524 0 0         if(my $usa = $loc->{'usa'}) {
525 0           $name = $usa->{'usstnumber'};
526 0 0         if(my $staddress = $usa->{'usstaddress'}) {
527 0 0         $name .= ' ' if($name);
528 0           $name .= $staddress;
529             }
530 0 0         if(my $city = $usa->{'uscity'}) {
531 0 0         $name .= ', ' if($name);
532 0           $name .= $city;
533             }
534 0 0         if(my $state = $usa->{'state'}) {
535 0 0         $name .= ', ' if($name);
536 0           $name .= $state;
537             }
538 0 0         $name .= ', ' if($name);
539 0           $name .= 'USA';
540             } else {
541 0           $name = $loc->{'stnumber'};
542 0 0         if(my $staddress = $loc->{'staddress'}) {
543 0 0         $name .= ' ' if($name);
544 0           $name .= $staddress;
545             }
546 0 0         if(my $city = $loc->{'city'}) {
547 0 0         $name .= ', ' if($name);
548 0           $name .= $city;
549             }
550 0 0         if(my $state = $loc->{'prov'}) {
551 0 0         $state .= ', ' if($name);
552 0           $name .= $state;
553             }
554             }
555 0           CORE::push @rc, $name;
556             }
557             }
558             }
559 0 0         if(wantarray) {
560 0           $self->_cache($latlng, \@rc);
561 0           return @rc;
562             }
563 0 0         if(scalar($rc[0])) { # check it's not an empty hash
564 0           $self->_cache($latlng, $rc[0]);
565 0           return $rc[0];
566             }
567             } elsif(my $rc = $self->_cache($latlng) // $geocoder->reverse_geocode(%params)) {
568 0 0         return $rc if(!ref($rc));
569 0 0         print Data::Dumper->new([$rc])->Dump() if($self->{'debug'} >= 2);
570 0 0         if(my $name = $rc->{'display_name'}) {
    0          
571             # OSM
572 0           return $self->_cache($latlng, $name);
573             } elsif($rc->{'city'}) {
574             # Geo::Coder::CA
575 0           my $name;
576 0 0         if(my $usa = $rc->{'usa'}) {
577             # TODO: Use Lingua::Conjunction
578 0           $name = $usa->{'usstnumber'};
579 0 0         if(my $staddress = $usa->{'usstaddress'}) {
580 0 0         $name .= ' ' if($name);
581 0           $name .= $staddress;
582             }
583 0 0         if(my $city = $usa->{'uscity'}) {
584 0 0         $name .= ', ' if($name);
585 0           $name .= $city;
586             }
587 0 0         if(my $state = $usa->{'state'}) {
588 0 0         $name .= ', ' if($name);
589 0           $name .= $state;
590             }
591 0           return $self->_cache($latlng, "$name, USA");
592             } else {
593             # TODO: Use Lingua::Conjunction
594 0           $name = $rc->{'stnumber'};
595 0 0         if(my $staddress = $rc->{'staddress'}) {
596 0 0         $name .= ' ' if($name);
597 0           $name .= $staddress;
598             }
599 0 0         if(my $city = $rc->{'city'}) {
600 0 0         $name .= ', ' if($name);
601 0           $name .= $city;
602             }
603 0 0         if(my $state = $rc->{'prov'}) {
604 0 0         $state = ", $state" if($name);
605 0           return $self->_cache($latlng, "$name $state");
606             }
607             }
608 0           return $self->_cache($latlng, $name);
609             }
610             }
611             }
612 0           return;
613             }
614              
615             =head2 log
616              
617             Returns the log of events to help you debug failures,
618             optimize lookup order and fix quota breakage.
619              
620             my @log = @{$geocoderlist->log()};
621              
622             =cut
623              
624             sub log {
625 0     0 1   my $self = shift;
626              
627 0           return $self->{'log'};
628             }
629              
630             =head2 flush
631              
632             Clear the log.
633              
634             =cut
635              
636             sub flush {
637 0     0 1   my $self = shift;
638              
639 0           delete $self->{'log'};
640             }
641              
642             sub _cache {
643 0     0     my $self = shift;
644 0           my $key = shift;
645              
646 0 0         if(my $value = shift) {
647             # Put something into the cache
648 0           $locations{$key} = $value;
649 0           my $rc = $value;
650 0 0         if($self->{'cache'}) {
651 0           my $duration;
652 0 0         if(ref($value) eq 'ARRAY') {
    0          
653 0           foreach my $item(@{$value}) {
  0            
654 0 0         if(ref($item) eq 'HASH') {
655 0           $item->{'geocoder'} = ref($item->{'geocoder'}); # It's an object, not the name
656 0 0         if(!$self->{'debug'}) {
657 0           while(my($k, $v) = each %{$item}) {
  0            
658 0 0         delete $item->{$k} unless($k eq 'geometry');
659             }
660             }
661 0 0         if(!defined($item->{geometry}{location}{lat})) {
662 0 0         if(defined($item->{geometry})) {
663             # Maybe a temporary lookup failure,
664             # so do a research tomorrow
665 0           $duration = '1 day';
666             } else {
667             # Probably the place doesn't exist
668 0           $duration = '1 week';
669             }
670 0           $rc = undef;
671             }
672             }
673             }
674 0 0         if(!defined($duration)) {
675             # Has matched - it won't move
676 0           $duration = '1 month';
677             }
678             } elsif(ref($value) eq 'HASH') {
679 0           $value->{'geocoder'} = ref($value->{'geocoder'}); # It's an object, not the name
680 0 0         if(!$self->{'debug'}) {
681 0           while(my($k, $v) = each %{$value}) {
  0            
682 0 0         delete $value->{$k} unless ($k eq 'geometry');
683             }
684             }
685 0 0         if(defined($value->{geometry}{location}{lat})) {
    0          
686 0           $duration = '1 month'; # It won't move :-)
687             } elsif(defined($value->{geometry})) {
688             # Maybe a temporary lookup failure, so do a research
689             # tomorrow
690 0           $duration = '1 day';
691 0           $rc = undef;
692             } else {
693             # Probably the place doesn't exist
694 0           $duration = '1 week';
695 0           $rc = undef;
696             }
697             } else {
698 0           $duration = '1 month';
699             }
700 0 0         print Data::Dumper->new([$value])->Dump() if($self->{'debug'});
701 0           $self->{'cache'}->set($key, $value, $duration);
702             }
703 0           return $rc;
704             }
705              
706             # Retrieve from the cache
707 0           my $rc = $locations{$key}; # In the L1 cache?
708 0 0 0       if((!defined($rc)) && $self->{'cache'}) { # In the L2 cache?
709 0           $rc = $self->{'cache'}->get($key);
710             }
711 0 0         if(defined($rc)) {
712 0 0         if(ref($rc) eq 'HASH') { # else - it will be an array of hashes
713 0 0         if(!defined($rc->{geometry}{location}{lat})) {
714 0           return;
715             }
716 0   0       $rc->{'lat'} //= $rc->{geometry}{location}{lat};
717 0   0       $rc->{'lng'} //= $rc->{geometry}{location}{lng};
718             }
719             }
720 0           return $rc;
721             }
722              
723             =head1 AUTHOR
724              
725             Nigel Horne, C<< >>
726              
727             =head1 BUGS
728              
729             Please report any bugs or feature requests to C,
730             or through the web interface at
731             L.
732             I will be notified, and then you'll
733             automatically be notified of progress on your bug as I make changes.
734              
735             reverse_geocode() doesn't update the logger.
736             reverse_geocode() should support L objects.
737              
738             =head1 SEE ALSO
739              
740             L
741             L
742             L
743              
744             =head1 SUPPORT
745              
746             You can find documentation for this module with the perldoc command.
747              
748             perldoc Geo::Coder::List
749              
750             You can also look for information at:
751              
752             =over 4
753              
754             =item * RT: CPAN's request tracker
755              
756             L
757              
758             =item * MetaCPAN
759              
760             L
761              
762             =back
763              
764             =head1 LICENSE AND COPYRIGHT
765              
766             Copyright 2016-2023 Nigel Horne.
767              
768             This program is released under the following licence: GPL2
769              
770             =cut
771              
772             1;