File Coverage

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


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