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   2567495 use 5.10.1;
  21         335  
4              
5 21     21   125 use warnings;
  21         42  
  21         609  
6 21     21   112 use strict;
  21         57  
  21         444  
7 21     21   101 use Carp;
  21         65  
  21         1373  
8 21     21   153 use Time::HiRes;
  21         42  
  21         221  
9 21     21   12320 use HTML::Entities;
  21         123170  
  21         1496  
10              
11 21     21   171 use constant DEBUG => 0; # Default debugging level
  21         43  
  21         73513  
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.32
23              
24             =cut
25              
26             our $VERSION = '0.32';
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 552 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       13 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         3 $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         24 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             gecoder => 'cached',
171             wantarray => 0,
172             result => $rc
173             };
174 0           CORE::push @{$self->{'log'}}, $log;
  0            
175 0 0         print __PACKAGE__, ': ', __LINE__, ": cached\n" if($self->{'debug'});
176 0           return $rc;
177             }
178             }
179 0 0 0       if(defined($self->_cache($location)) && (ref($self->_cache($location)) eq 'ARRAY') && (my @rc = @{$self->_cache($location)})) {
  0   0        
180 0 0         if(scalar(@rc)) {
181 0           my $allempty = 1;
182 0           foreach (@rc) {
183 0 0         if(ref($_) eq 'HASH') {
    0          
184 0 0         if(defined($_->{geometry}{location}{lat})) {
185 0           $allempty = 0;
186 0           delete $_->{'geocoder'};
187             } else {
188 0           delete $_->{'geometry'};
189             }
190             } elsif(ref($_) eq 'Geo::Location::Point') {
191 0           $allempty = 0;
192 0           delete $_->{'geocoder'};
193             }
194             }
195 0           my $log = {
196             line => $call_details[2],
197             location => $location,
198             timetaken => 0,
199             gecoder => 'cached',
200             wantarray => wantarray,
201             result => \@rc
202             };
203 0           CORE::push @{$self->{'log'}}, $log;
  0            
204 0 0         print __PACKAGE__, ': ', __LINE__, ": cached\n" if($self->{'debug'});
205 0 0         if($allempty) {
206 0           return;
207             }
208 0 0         return (wantarray) ? @rc : $rc[0];
209             }
210             }
211              
212 0           my $error;
213              
214 0           ENCODER: foreach my $g(@{$self->{geocoders}}) {
  0            
215 0           my $geocoder = $g;
216 0 0         if(ref($geocoder) eq 'HASH') {
217 0 0 0       if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
218 0 0         print "limit: $limit\n" if($self->{'debug'});
219 0 0         if($limit <= 0) {
220 0           next;
221             }
222 0           $geocoder->{'limit'}--;
223             }
224 0 0         if(my $regex = $geocoder->{'regex'}) {
225 0 0         print 'consider ', ref($geocoder->{geocoder}), ": $regex\n" if($self->{'debug'});
226 0 0         if($location !~ $regex) {
227 0           next;
228             }
229             }
230 0           $geocoder = $g->{'geocoder'};
231             }
232 0           my @rc;
233 0           my $timetaken = Time::HiRes::time();
234 0           eval {
235             # e.g. over QUERY LIMIT with this one
236             # TODO: remove from the list of geocoders
237 0 0         print 'trying ', ref($geocoder), "\n" if($self->{'debug'});
238 0 0         if(ref($geocoder) eq 'Geo::GeoNames') {
239 0 0         print 'username => ', $geocoder->username(), "\n" if($self->{'debug'});
240 0 0         die 'lost username' if(!defined($geocoder->username()));
241 0           @rc = $geocoder->geocode($location);
242             } else {
243 0           @rc = $geocoder->geocode(%params);
244             }
245             };
246 0 0         if($@) {
247 0           my $log = {
248             line => $call_details[2],
249             location => $location,
250             geocoder => ref($geocoder),
251             timetaken => Time::HiRes::time() - $timetaken,
252             wantarray => wantarray,
253             error => $@
254             };
255 0           CORE::push @{$self->{'log'}}, $log;
  0            
256 0           Carp::carp(ref($geocoder), " '$location': $@");
257 0           $error = $@;
258 0           next ENCODER;
259             }
260 0           $timetaken = Time::HiRes::time() - $timetaken;
261 0 0 0       if((scalar(@rc) == 0) ||
      0        
      0        
      0        
262 0           ((ref($rc[0]) eq 'HASH') && (scalar(keys %{$rc[0]}) == 0)) ||
263 0           ((ref($rc[0]) eq 'ARRAY') && (scalar(keys %{$rc[0][0]}) == 0))) {
264 0           my $log = {
265             line => $call_details[2],
266             location => $location,
267             timetaken => $timetaken,
268             geocoder => ref($geocoder),
269             wantarray => wantarray,
270             result => 'not found',
271             };
272 0           CORE::push @{$self->{'log'}}, $log;
  0            
273 0           next ENCODER;
274             }
275 0           POSSIBLE_LOCATION: foreach my $l(@rc) {
276 0 0         if(ref($l) eq 'ARRAY') {
277             # Geo::GeoNames
278             # FIXME: should consider all locations in the array
279 0           $l = $l->[0];
280             }
281 0 0         if(!defined($l)) {
282 0           my $log = {
283             line => $call_details[2],
284             location => $location,
285             timetaken => $timetaken,
286             geocoder => ref($geocoder),
287             wantarray => wantarray,
288             result => 'not found',
289             };
290 0           CORE::push @{$self->{'log'}}, $log;
  0            
291 0           next ENCODER;
292             }
293             print ref($geocoder), ': ',
294 0 0         Data::Dumper->new([\$l])->Dump() if($self->{'debug'} >= 2);
295 0 0         last if(ref($l) eq 'Geo::Location::Point');
296 0 0         next if(ref($l) ne 'HASH');
297 0 0         if($l->{'error'}) {
298             my $log = {
299             line => $call_details[2],
300             location => $location,
301             timetaken => $timetaken,
302             geocoder => ref($geocoder),
303             wantarray => wantarray,
304 0           error => $l->{'error'}
305             };
306 0           CORE::push @{$self->{'log'}}, $log;
  0            
307 0           next ENCODER;
308             } else {
309             # Try to create a common interface, helps with HTML::GoogleMaps::V3
310 0 0         if(!defined($l->{geometry}{location}{lat})) {
311 0           my ($lat, $long);
312 0 0 0       if($l->{lat} && defined($l->{lon})) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
313             # OSM/RandMcNalley
314             # This would have been nice, but it doesn't compile
315             # ($lat, $long) = $l->{'lat', 'lon'};
316 0           $lat = $l->{lat};
317 0           $long = $l->{lon};
318             } elsif($l->{BestLocation}) {
319             # Bing
320 0           $lat = $l->{BestLocation}->{Coordinates}->{Latitude};
321 0           $long = $l->{BestLocation}->{Coordinates}->{Longitude};
322             } elsif($l->{point}) {
323             # Bing
324 0           $lat = $l->{point}->{coordinates}[0];
325 0           $long = $l->{point}->{coordinates}[1];
326             } elsif($l->{latt}) {
327             # geocoder.ca
328 0           $lat = $l->{latt};
329 0           $long = $l->{longt};
330             } elsif($l->{latitude}) {
331             # postcodes.io
332             # Geo::Coder::Free
333 0           $lat = $l->{latitude};
334 0           $long = $l->{longitude};
335 0 0         if(my $type = $l->{'local_type'}) {
336 0           $l->{'type'} = lcfirst($type); # e.g. village
337             }
338             } elsif($l->{'properties'}{'geoLatitude'}) {
339             # ovi
340 0           $lat = $l->{properties}{geoLatitude};
341 0           $long = $l->{properties}{geoLongitude};
342             } elsif($l->{'results'}[0]->{'geometry'}) {
343 0 0         if($l->{'results'}[0]->{'geometry'}->{'location'}) {
344             # DataScienceToolkit
345 0           $lat = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lat'};
346 0           $long = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lng'};
347             } else {
348             # OpenCage
349 0           $lat = $l->{'results'}[0]->{'geometry'}->{'lat'};
350 0           $long = $l->{'results'}[0]->{'geometry'}->{'lng'};
351             }
352             } elsif($l->{'RESULTS'}) {
353             # GeoCodeFarm
354 0           $lat = $l->{'RESULTS'}[0]{'COORDINATES'}{'latitude'};
355 0           $long = $l->{'RESULTS'}[0]{'COORDINATES'}{'longitude'};
356             } elsif(defined($l->{result}{addressMatches}[0]->{coordinates}{y})) {
357             # US Census
358             # This would have been nice, but it doesn't compile
359             # ($lat, $long) = $l->{result}{addressMatches}[0]->{coordinates}{y, x};
360 0           $lat = $l->{result}{addressMatches}[0]->{coordinates}{y};
361 0           $long = $l->{result}{addressMatches}[0]->{coordinates}{x};
362             } elsif($l->{lat}) {
363             # Geo::GeoNames
364 0           $lat = $l->{lat};
365 0           $long = $l->{lng};
366             } elsif($l->{features}) {
367             # Geo::Coder::Mapbox
368 0           $lat = $l->{features}[0]->{center}[1];
369 0           $long = $l->{features}[0]->{center}[0];
370             }
371              
372 0 0 0       if(defined($lat) && defined($long)) {
373 0           $l->{geometry}{location}{lat} = $lat;
374 0           $l->{geometry}{location}{lng} = $long;
375             } else {
376 0           delete $l->{'geometry'};
377             }
378              
379 0 0         if($l->{'standard'}{'countryname'}) {
380             # geocoder.xyz
381 0           $l->{'address'}{'country'} = $l->{'standard'}{'countryname'};
382             }
383             }
384 0 0         if(defined($l->{geometry}{location}{lat})) {
385 0 0         print $l->{geometry}{location}{lat}, '/', $l->{geometry}{location}{lng}, "\n" if($self->{'debug'});
386 0           $l->{geocoder} = $geocoder;
387 0   0       $l->{'lat'} //= $l->{geometry}{location}{lat};
388 0   0       $l->{'lng'} //= $l->{geometry}{location}{lng};
389 0           my $log = {
390             line => $call_details[2],
391             location => $location,
392             timetaken => $timetaken,
393             geocoder => ref($geocoder),
394             wantarray => wantarray,
395             result => $l
396             };
397 0           CORE::push @{$self->{'log'}}, $log;
  0            
398 0           last POSSIBLE_LOCATION;
399             }
400             }
401             }
402              
403 0 0         if(scalar(@rc)) {
404 0 0         print 'Number of matches from ', ref($geocoder), ': ', scalar(@rc), "\n" if($self->{'debug'});
405 0 0         print Data::Dumper->new([\@rc])->Dump() if($self->{'debug'} >= 2);
406 0 0         if(defined($rc[0])) { # check it's not an empty hash
407 0 0         if(wantarray) {
408 0           $self->_cache($location, \@rc);
409 0           return @rc;
410             }
411 0           $self->_cache($location, $rc[0]);
412 0           return $rc[0];
413             }
414             }
415             }
416             # Can't do this because we need to return undef in this case
417             # if($error) {
418             # return { error => $error };
419             # }
420 0 0         print "No matches\n" if($self->{'debug'});
421 0 0         if(wantarray) {
422 0           $self->_cache($location, ());
423 0           return ();
424             }
425 0           $self->_cache($location, undef);
426             }
427              
428             =head2 ua
429              
430             Accessor method to set the UserAgent object used internally by each of the Geo-Coders.
431             You can call I,
432             for example,
433             to set the proxy information from environment variables:
434              
435             my $geocoder_list = Geo::Coder::List->new();
436             my $ua = LWP::UserAgent->new();
437             $ua->env_proxy(1);
438             $geocoder_list->ua($ua);
439              
440             Note that unlike Geo::Coders there is no read method since that would be pointless.
441              
442             =cut
443              
444             sub ua {
445 0     0 1   my $self = shift;
446              
447 0 0         if(my $ua = shift) {
448 0           foreach my $g(@{$self->{geocoders}}) {
  0            
449 0           my $geocoder = $g;
450 0 0         if(ref($g) eq 'HASH') {
451 0           $geocoder = $g->{'geocoder'};
452 0 0         if(!defined($geocoder)) {
453 0           Carp::croak('No geocoder found');
454             }
455             }
456 0           $geocoder->ua($ua);
457             }
458 0           return $ua;
459             }
460             }
461              
462             =head2 reverse_geocode
463              
464             Similar to geocode except it expects a latitude/longitude parameter.
465              
466             print $geocoder_list->reverse_geocode(latlng => '37.778907,-122.39732');
467              
468             =cut
469              
470             sub reverse_geocode {
471 0     0 1   my $self = shift;
472 0           my %params;
473              
474 0 0         if(ref($_[0]) eq 'HASH') {
    0          
    0          
475 0           %params = %{$_[0]};
  0            
476             } elsif(ref($_[0])) {
477 0           Carp::croak('Usage: reverse_geocode(location => $location)');
478             } elsif(@_ % 2 == 0) {
479 0           %params = @_;
480             } else {
481 0           $params{'latlng'} = shift;
482             }
483              
484 0 0         my $latlng = $params{'latlng'}
485             or Carp::croak('Usage: reverse_geocode(latlng => $location)');
486              
487 0           my $latitude;
488             my $longitude;
489              
490 0 0         if($latlng) {
491 0           ($latitude, $longitude) = split(/,/, $latlng);
492             } else {
493 0   0       $latitude //= $params{'lat'};
494 0   0       $longitude //= $params{'lon'};
495 0   0       $longitude //= $params{'long'};
496             }
497              
498 0 0         if(my $rc = $self->_cache($latlng)) {
499 0           return $rc;
500             }
501              
502 0           foreach my $g(@{$self->{geocoders}}) {
  0            
503 0           my $geocoder = $g;
504 0 0         if(ref($geocoder) eq 'HASH') {
505 0 0 0       if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
506 0 0         print "limit: $limit\n" if($self->{'debug'});
507 0 0         if($limit <= 0) {
508 0           next;
509             }
510 0           $geocoder->{'limit'}--;
511             }
512 0           $geocoder = $g->{'geocoder'};
513             }
514 0 0         print 'trying ', ref($geocoder), "\n" if($self->{'debug'});
515 0 0 0       if(wantarray) {
    0          
516 0           my @rc;
517 0 0         if(my @locs = $geocoder->reverse_geocode(%params)) {
518 0 0         print Data::Dumper->new([\@locs])->Dump() if($self->{'debug'} >= 2);
519 0           foreach my $loc(@locs) {
520 0 0         if(my $name = $loc->{'display_name'}) {
    0          
521             # OSM
522 0           CORE::push @rc, $name;
523             } elsif($loc->{'city'}) {
524             # Geo::Coder::CA
525 0           my $name;
526 0 0         if(my $usa = $loc->{'usa'}) {
527 0           $name = $usa->{'usstnumber'};
528 0 0         if(my $staddress = $usa->{'usstaddress'}) {
529 0 0         $name .= ' ' if($name);
530 0           $name .= $staddress;
531             }
532 0 0         if(my $city = $usa->{'uscity'}) {
533 0 0         $name .= ', ' if($name);
534 0           $name .= $city;
535             }
536 0 0         if(my $state = $usa->{'state'}) {
537 0 0         $name .= ', ' if($name);
538 0           $name .= $state;
539             }
540 0 0         $name .= ', ' if($name);
541 0           $name .= 'USA';
542             } else {
543 0           $name = $loc->{'stnumber'};
544 0 0         if(my $staddress = $loc->{'staddress'}) {
545 0 0         $name .= ' ' if($name);
546 0           $name .= $staddress;
547             }
548 0 0         if(my $city = $loc->{'city'}) {
549 0 0         $name .= ', ' if($name);
550 0           $name .= $city;
551             }
552 0 0         if(my $state = $loc->{'prov'}) {
553 0 0         $state .= ', ' if($name);
554 0           $name .= $state;
555             }
556             }
557 0           CORE::push @rc, $name;
558             }
559             }
560             }
561 0 0         if(wantarray) {
562 0           $self->_cache($latlng, \@rc);
563 0           return @rc;
564             }
565 0 0         if(scalar($rc[0])) { # check it's not an empty hash
566 0           $self->_cache($latlng, $rc[0]);
567 0           return $rc[0];
568             }
569             } elsif(my $rc = $self->_cache($latlng) // $geocoder->reverse_geocode(%params)) {
570 0 0         return $rc if(!ref($rc));
571 0 0         print Data::Dumper->new([$rc])->Dump() if($self->{'debug'} >= 2);
572 0 0         if(my $name = $rc->{'display_name'}) {
    0          
573             # OSM
574 0           return $self->_cache($latlng, $name);
575             } elsif($rc->{'city'}) {
576             # Geo::Coder::CA
577 0           my $name;
578 0 0         if(my $usa = $rc->{'usa'}) {
579             # TODO: Use Lingua::Conjunction
580 0           $name = $usa->{'usstnumber'};
581 0 0         if(my $staddress = $usa->{'usstaddress'}) {
582 0 0         $name .= ' ' if($name);
583 0           $name .= $staddress;
584             }
585 0 0         if(my $city = $usa->{'uscity'}) {
586 0 0         $name .= ', ' if($name);
587 0           $name .= $city;
588             }
589 0 0         if(my $state = $usa->{'state'}) {
590 0 0         $name .= ', ' if($name);
591 0           $name .= $state;
592             }
593 0           return $self->_cache($latlng, "$name, USA");
594             } else {
595             # TODO: Use Lingua::Conjunction
596 0           $name = $rc->{'stnumber'};
597 0 0         if(my $staddress = $rc->{'staddress'}) {
598 0 0         $name .= ' ' if($name);
599 0           $name .= $staddress;
600             }
601 0 0         if(my $city = $rc->{'city'}) {
602 0 0         $name .= ', ' if($name);
603 0           $name .= $city;
604             }
605 0 0         if(my $state = $rc->{'prov'}) {
606 0 0         $state = ", $state" if($name);
607 0           return $self->_cache($latlng, "$name $state");
608             }
609             }
610 0           return $self->_cache($latlng, $name);
611             }
612             }
613             }
614 0           return;
615             }
616              
617             =head2 log
618              
619             Returns the log of events to help you debug failures,
620             optimize lookup order and fix quota breakage.
621              
622             my @log = @{$geocoderlist->log()};
623              
624             =cut
625              
626             sub log {
627 0     0 1   my $self = shift;
628              
629 0           return $self->{'log'};
630             }
631              
632             =head2 flush
633              
634             Clear the log.
635              
636             =cut
637              
638             sub flush {
639 0     0 1   my $self = shift;
640              
641 0           delete $self->{'log'};
642             }
643              
644             sub _cache {
645 0     0     my $self = shift;
646 0           my $key = shift;
647              
648 0 0         if(my $value = shift) {
649             # Put something into the cache
650 0           $locations{$key} = $value;
651 0           my $rc = $value;
652 0 0         if($self->{'cache'}) {
653 0           my $duration;
654 0 0         if(ref($value) eq 'ARRAY') {
    0          
655 0           foreach my $item(@{$value}) {
  0            
656 0 0         if(ref($item) eq 'HASH') {
657 0           $item->{'geocoder'} = ref($item->{'geocoder'}); # It's an object, not the name
658 0 0         if(!$self->{'debug'}) {
659 0           while(my($k, $v) = each %{$item}) {
  0            
660 0 0         delete $item->{$k} unless($k eq 'geometry');
661             }
662             }
663 0 0         if(!defined($item->{geometry}{location}{lat})) {
664 0 0         if(defined($item->{geometry})) {
665             # Maybe a temporary lookup failure,
666             # so do a research tomorrow
667 0           $duration = '1 day';
668             } else {
669             # Probably the place doesn't exist
670 0           $duration = '1 week';
671             }
672 0           $rc = undef;
673             }
674             }
675             }
676 0 0         if(!defined($duration)) {
677             # Has matched - it won't move
678 0           $duration = '1 month';
679             }
680             } elsif(ref($value) eq 'HASH') {
681 0           $value->{'geocoder'} = ref($value->{'geocoder'}); # It's an object, not the name
682 0 0         if(!$self->{'debug'}) {
683 0           while(my($k, $v) = each %{$value}) {
  0            
684 0 0         delete $value->{$k} unless ($k eq 'geometry');
685             }
686             }
687 0 0         if(defined($value->{geometry}{location}{lat})) {
    0          
688 0           $duration = '1 month'; # It won't move :-)
689             } elsif(defined($value->{geometry})) {
690             # Maybe a temporary lookup failure, so do a research
691             # tomorrow
692 0           $duration = '1 day';
693 0           $rc = undef;
694             } else {
695             # Probably the place doesn't exist
696 0           $duration = '1 week';
697 0           $rc = undef;
698             }
699             } else {
700 0           $duration = '1 month';
701             }
702 0 0         print Data::Dumper->new([$value])->Dump() if($self->{'debug'});
703 0           $self->{'cache'}->set($key, $value, $duration);
704             }
705 0           return $rc;
706             }
707              
708             # Retrieve from the cache
709 0           my $rc = $locations{$key}; # In the L1 cache?
710 0 0 0       if((!defined($rc)) && $self->{'cache'}) { # In the L2 cache?
711 0           $rc = $self->{'cache'}->get($key);
712             }
713 0 0         if(defined($rc)) {
714 0 0         if(ref($rc) eq 'HASH') { # else - it will be an array of hashes
715 0 0         if(!defined($rc->{geometry}{location}{lat})) {
716 0           return;
717             }
718 0   0       $rc->{'lat'} //= $rc->{geometry}{location}{lat};
719 0   0       $rc->{'lng'} //= $rc->{geometry}{location}{lng};
720             }
721             }
722 0           return $rc;
723             }
724              
725             =head1 AUTHOR
726              
727             Nigel Horne, C<< >>
728              
729             =head1 BUGS
730              
731             Please report any bugs or feature requests to C,
732             or through the web interface at
733             L.
734             I will be notified, and then you'll
735             automatically be notified of progress on your bug as I make changes.
736              
737             reverse_geocode() doesn't update the logger.
738             reverse_geocode() should support L objects.
739              
740             =head1 SEE ALSO
741              
742             L
743             L
744             L
745              
746             =head1 SUPPORT
747              
748             You can find documentation for this module with the perldoc command.
749              
750             perldoc Geo::Coder::List
751              
752             You can also look for information at:
753              
754             =over 4
755              
756             =item * RT: CPAN's request tracker
757              
758             L
759              
760             =item * MetaCPAN
761              
762             L
763              
764             =back
765              
766             =head1 LICENSE AND COPYRIGHT
767              
768             Copyright 2016-2023 Nigel Horne.
769              
770             This program is released under the following licence: GPL2
771              
772             =cut
773              
774             1;