File Coverage

File:blib/lib/Geo/Coder/List.pm
Coverage:93.5%

linestmtbrancondsubtimecode
1package Geo::Coder::List;
2
3# Geo::Coder::List - Aggregate and chain multiple geocoding backends
4
5
25
25
1832903
34
use 5.10.1;
6
7
25
25
25
46
16
211
use strict;
8
25
25
25
31
25
377
use warnings;
9
10
25
25
25
38
23
666
use Carp;
11
25
25
25
296
2913
478
use Data::Dumper;
12# One definition of what counts as a location: Geo::Location::Predicate holds
13# the rule the facade and its backends apply, so what is refused here is refused
14# there for the same reason.  It is called by name rather than imported - an
15# import copies the code reference into this package, and then a test could not
16# swap the rule for a spy to prove that it is the rule being consulted.
17#
18# The rule is an optional dependency, not a prerequisite: it is a distribution of
19# its own (Geo-Location-Predicate), declared in Makefile.PL under META_MERGE's
20# recommends, so an installer fetches it by default and a standalone install that
21# leaves it out still gets the one answer this module can give alone - a
22# reference is refused.  It is looked for when it is first needed rather than
23# when this module is loaded, so importing this module reads no file it may not
24# need, and a rule installed after this module is loaded is still found.
25my $HAVE_RULE;  # undef until the rule is first looked for
26
25
25
25
4782
58356
1001
use HTML::Entities;
27
25
25
25
6773
2225254
515
use Object::Configure 0.13;
28
25
25
25
100
127
385
use Params::Get 0.04;
29
25
25
25
42
26
453
use Readonly;
30
25
25
25
43
17
438
use Scalar::Util qw(blessed reftype);
31
25
25
25
36
19
70
use Time::HiRes;
32
33 - 41
=head1 NAME

Geo::Coder::List - Call many Geo-Coders

=head1 VERSION

Version 0.39

=cut
42
43our $VERSION = '0.39';
44
45# ── Module-level constants (not user-configurable) ────────────────────────────
46
47# Default verbosity: 0 = silent, 1 = basic trace, 2 = full Data::Dumper dumps
48
25
25
25
979
19
649
use constant DEBUG_DEFAULT => 0;
49
50# Internal sentinel class: a cached not-found result stored in L1.
51# Using a blessed ref lets _cache() distinguish "never looked up" from
52# "looked up and found nothing" without requiring a special undef convention.
53
25
25
25
38
20
60535
use constant _NOT_FOUND_CLASS => __PACKAGE__ . '::_NotFound';
54
55# The singleton value stored in L1 when a location is confirmed missing
56my $NOT_FOUND_SENTINEL = bless {}, _NOT_FOUND_CLASS;
57
58# String placed in the 'geocoder' field of a result served from cache
59Readonly::Scalar my $CACHE_SOURCE => 'cache';
60
61# A latlng argument to reverse_geocode(): two decimal numbers and a comma.
62# The classes are spelt out rather than written \d and \s: those match
63# Arabic-Indic and other Unicode digits, which no backend reads as numbers,
64# and the /a flag that would restrict them needs Perl 5.14.
65Readonly::Scalar my $LATLNG_RE => qr/^[ \t]*[-+]?(?:[0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)[ \t]*,[ \t]*[-+]?(?:[0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)[ \t]*$/;
66
67# String used as the 'result' value in log entries for a geocoder miss
68Readonly::Scalar my $RESULT_NONE => 'not found';
69
70# ── Configurable defaults ─────────────────────────────────────────────────────
71#
72# Any key here can be overridden at run time via an environment variable named
73# GEO__CODER__LIST__<key>, which Object::Configure reads automatically in new().
74# Example:
75#   export GEO__CODER__LIST__cache_hit_duration='7 days'
76
77my %config = (
78        debug               => DEBUG_DEFAULT,
79        # How long a confirmed location stays in the L2 cache
80        cache_hit_duration  => '1 month',
81        # How long a transient/partial failure is cached (retry tomorrow)
82        cache_part_duration => '1 day',
83        # How long a definite not-found is cached (place probably does not exist)
84        cache_miss_duration => '1 week',
85);
86
87# =============================================================================
88# PUBLIC API
89# =============================================================================
90
91 - 349
=head1 SYNOPSIS

    use Geo::Coder::List;
    use Geo::Coder::OSM;

    # 1. The simplest use: one list, one geocoder
    my $list = Geo::Coder::List->new()->push(Geo::Coder::OSM->new());

    my $place = $list->geocode('10 Downing St, London, UK');
    if($place) {
        print "Latitude:  $place->{geometry}{location}{lat}\n";
        print "Longitude: $place->{geometry}{location}{lng}\n";
    } else {
        print "Not found\n";
    }

    # 2. Several geocoders, tried in order.  Geo::Coder::CA is asked only
    #    about places in Canada or the USA, and at most 1000 times.
    use Geo::Coder::CA;

    my $chain = Geo::Coder::List->new()
        ->push({ regex => qr/(Canada|USA)$/, geocoder => Geo::Coder::CA->new(), limit => 1000 })
        ->push(Geo::Coder::OSM->new());

    my $ottawa = $chain->geocode('Ottawa, ON, Canada');    # asks CA first
    my $paris  = $chain->geocode('Paris, France');         # CA is skipped

    # 3. A cache that lives longer than the object (any CHI driver works)
    use CHI;

    my $cached = Geo::Coder::List->new(
        cache => CHI->new(driver => 'File', root_dir => '/var/cache/geocode'),
    )->push(Geo::Coder::OSM->new());

    # 4. Every candidate, not only the best one: call geocode() in list context
    my @candidates = $list->geocode('Springfield, USA');
    print scalar(@candidates), " places are called Springfield\n";

    # 5. From a position to an address
    my $address = $list->reverse_geocode(latlng => '51.5034,-0.1276');
    print "$address\n" if defined($address);

    # 6. Bad input is fatal, so check it when the text comes from a user
    my $text_from_a_form = $ARGV[0];
    my $result = eval { $list->geocode($text_from_a_form) };
    warn "Not a place: $@" if $@;

    # 7. See what happened, then clear the record
    $list->geocode('Rome, Italy');
    for my $entry (@{ $list->log() }) {
        printf "%s: %s in %.3fs\n", $entry->{location}, $entry->{geocoder}, $entry->{timetaken};
    }
    $list->flush();

=head1 DESCRIPTION

L<Geo::Coder::All> and L<Geo::Coder::Many> are great modules but neither
quite does what I want.

A I<geocoder> turns the name of a place, such as C<'Paris, France'>, into
a position on the Earth (a latitude and a longitude).  There are many
geocoding services.  Each one knows some places better than others, each
one has its own limits, and each one returns its answer in its own format.

C<Geo::Coder::List> lets you use many geocoders as if they were one:

=over 4

=item * B<A chain.>
You add geocoders with C<push()>.  When you ask for a place, the geocoders
are asked one at a time, in the order you added them.  The first one that
gives a usable answer wins, and the others are not asked.

=item * B<Routing.>
A geocoder can be given a regular expression.  It is then asked only
about places whose names match it.

=item * B<Limits.>
A geocoder can be given a limit: the number of questions it may answer.
When the limit is used up, the geocoder is skipped.

=item * B<One answer format.>
Every answer is changed into the same structure, whichever service gave
it.  The structure is the one used by L<HTML::GoogleMaps::V3> and
L<HTML::OSM>:

    $result->{geometry}{location}{lat}   # latitude
    $result->{geometry}{location}{lng}   # longitude
    $result->{lat}                       # the same latitude, for convenience
    $result->{lng}                       # the same longitude, for convenience
    $result->{lon}                       # the same longitude, another common name
    $result->{geocoder}                  # the geocoder object that answered,
                                         # or the string 'cache'

=item * B<A cache in two levels.>
Level 1 (L1) is a hash inside the object.  It is always used, and it lasts
as long as the object.  Level 2 (L2) is optional.  It can be a plain hash
or any object with C<get()> and C<set()> methods, such as a L<CHI> cache.
L2 can last longer than the object and can be shared between programs.
How long L2 keeps an answer depends on the answer:

    found                      1 month   (cache_hit_duration)
    found, but no latitude     1 day     (cache_part_duration)
    not found                  1 week    (cache_miss_duration)

A place that was not found is remembered in L1 only, never in L2.

=back

=head1 SUPPORTED ANSWER FORMATS

Each geocoding service answers in its own format.  C<geocode()> finds the
latitude and longitude in each of the formats below and copies them into
the common structure.  Any geocoder whose answers use one of these formats
works without extra code.  The provider's own fields are kept as well.

    Service (module)                Where the position is found
    ------------------------------  -----------------------------------------
    OpenStreetMap, RandMcNally      lat, lon
    Bing Maps                       BestLocation.Coordinates.Latitude/Longitude
    Bing Maps (other form)          point.coordinates [lat, lng]
    geocoder.ca (Geo::Coder::CA)    latt, longt
    postcodes.io, Geo::Coder::Free  latitude, longitude
    HERE / Ovi                      properties.geoLatitude/geoLongitude
    DataScienceToolkit              results[0].geometry.location.lat/lng
    OpenCage                        results[0].geometry.lat/lng
    GeocodeFarm                     RESULTS[0].COORDINATES.latitude/longitude
    US Census Bureau                result.addressMatches[0].coordinates.y/x
    GeoNames                        lat, lng
    Mapbox                          features[0].center [lng, lat]
    GeoApify                        features[0].geometry.coordinates [lng, lat]
    Google, and anything already    geometry.location.lat/lng
    in the common structure

Notice that Mapbox and GeoApify give the longitude B<first>.

Some services need special handling, which is done for you:

=over 4

=item * B<Geo::Location::Point.>
A geocoder may answer with a L<Geo::Location::Point> object.  Its
C<geometry> is filled in from its own C<lat> and C<lng> (or C<lon>).

=item * B<postcodes.io.>
Its C<local_type> field is copied to C<type>, with the first letter made
lower case.

=item * B<geocoder.xyz.>
Its C<standard.countryname> field is copied to C<address.country>.

=item * B<GeoNames.>
L<Geo::GeoNames> is called with the place name alone, not with
C<location =E<gt> ...>.  If it has no C<username>, it fails with
C<lost username>, which is logged, and the next geocoder is asked.
GeoNames puts its answers in arrays; every answer in them is considered.

=item * B<US Census Bureau.>
An answer with no C<addressMatches> means "not found", and the next
geocoder is asked.

=item * B<GeoApify.>
An answer with an empty C<features> list means "not found", and the next
geocoder is asked.

=item * B<Errors.>
An answer with a top-level C<error> key is treated as a refusal, not as a
place.  The error is logged, and the next geocoder is asked.

=back

Every answer that went through this process also has a C<debug> key.  It
holds the line number, in this module, of the code that read the answer.
It is there to help when a new format is not read correctly.

=head1 SUBROUTINES/METHODS

=head2 new

Creates a new C<Geo::Coder::List> object with no geocoders in it.

Settings can also come from the environment, through
L<Object::Configure>.  The variable name is C<GEO__CODER__LIST__> followed
by the setting name.  For example, C<GEO__CODER__LIST__carp_on_warn=1>
makes warnings go through L<Carp>, and
C<GEO__CODER__LIST__cache_hit_duration='7 days'> changes how long a found
place stays in L2.

When you call C<new()> on an object that already exists, you get a
I<clone>: a new object with the same settings and the same geocoders,
merged with any arguments you give.  The clone has its own list of
geocoders and its own limits, and it starts with an empty log.  The clone
B<shares> the L1 and L2 caches with the original.

    use Geo::Coder::List;
    use CHI;

    # With an L2 cache
    my $geocoder = Geo::Coder::List->new(
        cache => CHI->new(driver => 'Memory', global => 1),
    );

    # A clone of it that prints what it is doing
    my $verbose = $geocoder->new(debug => 1);

Arguments can be given as a list of pairs or as one hash reference.

=over 4

=item * C<cache> - Optional.  The L2 cache: a hash reference, or an object
with C<get($key)> and C<set($key, $value, $ttl)> methods.

=item * C<debug> - Optional.  C<0> (the default) prints nothing, C<1>
prints what is tried, C<2> also prints every answer in full.

=item * C<cache_hit_duration>, C<cache_part_duration>,
C<cache_miss_duration> - Optional.  How long L2 keeps each kind of
answer, in any form your L2 cache understands, such as C<'1 week'>.

=item * C<geocoders> - Optional.  An array reference of geocoders, as if
each had been given to C<push()>.

=item * C<logger> - Optional.  Where warnings and errors go.  See
L<Object::Configure>.

=item * C<carp_on_warn> - Optional.  When true, warnings go through
L<Carp>.

=back

If C<new> is called as a function, C<Geo::Coder::List::new()>, with no
arguments, it still returns an object.  Called as a function with
arguments, it warns and returns C<undef>.

=head3 API SPECIFICATION

=head4 INPUT

    {
        cache               => { type => 'hashref|object', optional => 1 },
        debug               => { type => 'integer', optional => 1, min => 0, max => 2 },
        cache_hit_duration  => { type => 'string', optional => 1 },
        cache_part_duration => { type => 'string', optional => 1 },
        cache_miss_duration => { type => 'string', optional => 1 },
        geocoders           => { type => 'arrayref', optional => 1 },
        logger              => { type => 'object|coderef|arrayref|string', optional => 1 },
        carp_on_warn        => { type => 'boolean', optional => 1 },
    }

=head4 OUTPUT

    {
        type => 'object',
        isa  => 'Geo::Coder::List',
    }

    # undef only when called as Geo::Coder::List::new() with arguments

=cut
350
351sub new
352{
353
406
1201325
        my $class = shift;
354
406
627
        my $params = Params::Get::get_params(undef, \@_) || {};
355
356        # Handle the rare Geo::Coder::List::new() (function-style) invocation
357
406
5091
        if(!defined($class)) {
358
8
8
30
27
                if(scalar keys %{$params} > 0) {
359                        # Using ::new() with arguments is not supported
360
4
37
                        carp(__PACKAGE__, ' use ->new() not ::new() to instantiate');
361
4
714
                        return;
362                }
363                # With no arguments there is nothing to lose, so build a default
364                # object; with arguments the caller has mistaken the calling form and
365                # is told so above, rather than having the arguments silently ignored
366
4
3
                $class = __PACKAGE__;
367        } elsif(blessed($class)) {
368                # Shallow clone merged with new params; log is always fresh so the
369                # clone starts with an empty event history independent of the original
370                # The chain is copied, hashref entries included, so pushing onto the
371                # clone or spending one of its limits leaves the original's alone
372
9
9
9
9
540
20
                my $clone = bless { %{$class}, %{$params}, log => [] }, ref($class);
373                $clone->{'geocoders'} = [
374
9
5
1
9
12
29
1
16
                        map { ref($_) eq 'HASH' ? { %{$_} } : $_ } @{$clone->{'geocoders'} // []}
375                ];
376
9
17
                return $clone;
377        }
378
379        # Let Object::Configure overlay defaults from environment / config files
380
393
505
        $params = Object::Configure::configure($class, $params);
381
382        # Fill in any %config defaults the caller did not explicitly supply
383
393
825620
        for my $key (keys %config) {
384
1572
1935
                $params->{$key} //= $config{$key};
385        }
386
387        # Bless and return; params override scalar defaults but locations/log
388        # are always initialised fresh so callers cannot inject stale state
389        return bless {
390                debug     => DEBUG_DEFAULT,
391                geocoders => [],
392
393
393
300
1503
                %{$params},
393                locations => {},
394                log       => [],
395        }, $class;
396}
397
398# =============================================================================
399
400 - 457
=head2 push

Adds one geocoder to the end of the chain.  Geocoders are asked in the
order in which they were added.

C<push> returns the object itself, so you can add several geocoders in
one statement.

There are two ways to add a geocoder:

=over 4

=item * B<A geocoder object.>
It is asked about every place.

=item * B<A hash reference>, with these keys:

=over 4

=item * C<geocoder> - Required.  The geocoder object.

=item * C<regex> - Optional.  A regular expression, made with C<qr//>.
The geocoder is asked only about places whose names match it.

=item * C<limit> - Optional.  A whole number.  The geocoder answers at
most this many questions.  Each question it is asked makes the number one
smaller.  When the number reaches C<0>, the geocoder is skipped.  A place
that does not match C<regex> does not use up the limit.

=back

=back

    my $list = Geo::Coder::List->new()
        ->push({ regex => qr/USA$/, geocoder => Geo::Coder::US::Census->new(), limit => 100 })
        ->push(Geo::Coder::OSM->new());

C<push> croaks if C<$geocoder> is missing or C<undef>.

=head3 API SPECIFICATION

=head4 INPUT

    {
        geocoder => { type => 'object|hashref', optional => 0 },
    }

    # A hashref geocoder holds the keys geocoder (an object, required),
    # regex (a Regexp, optional) and limit (an integer, optional)

=head4 OUTPUT

    {
        type => 'object',
        isa  => 'Geo::Coder::List',
    }

=cut
458
459sub push
460{
461        # Deliberately NOT using Params::Get here: passing the hashref through it
462        # would stringify the compiled qr// object, destroying the regex.
463
308
2315
        my ($self, $geocoder) = @_;
464
465        # A geocoder argument is mandatory
466
308
324
        croak(__PACKAGE__, '::push: Usage: ($geocoder)') unless defined($geocoder);
467
468        # Append to the ordered chain and return $self for chaining
469
303
303
144
252
        CORE::push @{$self->{geocoders}}, $geocoder;
470
471
303
323
        return $self;
472}
473
474# The rule when it is installed, and the one thing that can be said without it.
475# A reference is not a location however it would print: when the rule is there it
476# says so, and when it is not this refuses it - the hole this module used to have
477# was testing the string a reference turns into.  Whether a string of digits is a
478# place is the rule's question, so without the rule it is not asked: the string
479# is looked up, as it was before the rule existed.
480sub _could_be_a_location {
481
250
7739
        my $location = shift;
482
483
250
197
        if(!defined($HAVE_RULE)) {
484
7
6
                $HAVE_RULE = eval {
485
7
1890
                        require Geo::Location::Predicate;
486
7
1213
                        Geo::Location::Predicate->can('_valid_location') ? 1 : 0;
487                } ? 1 : 0;
488        }
489
490        # When the rule is installed it decides everything, references included, so
491        # that this module cannot be stricter or looser than the facade.  Without it
492        # there are two answers this module can give on its own.
493
250
374
        return Geo::Location::Predicate::_valid_location($location) ? 1 : 0 if $HAVE_RULE;
494
495
0
0
        return 0 if ref($location);
496
0
0
        return 0 unless defined($location) && length($location);
497
0
0
        return 1;
498}
499
500# =============================================================================
501
502 - 595
=head2 geocode

Finds the position of a place from its name.

    my $result = $list->geocode(location => 'Paris, France');
    my $result = $list->geocode('Paris, France');            # the same
    my $result = $list->geocode({ location => 'Paris, France' });  # the same

    if($result) {
        printf "lat=%.4f lng=%.4f from %s\n",
            $result->{geometry}{location}{lat},
            $result->{geometry}{location}{lng},
            ref($result->{geocoder}) || $result->{geocoder};
    }

These are the steps, in order:

=over 4

=item 1. If C<location> is missing, C<geocode> croaks.  If it is
C<undef> or an empty string, C<geocode> warns and returns nothing.

=item 2. Runs of white space are made into one space, and HTML entities
are decoded: C<'Caf&eacute;'> becomes C<'Caf'> followed by the single
character e-acute.  A reference to a string is treated as the string.

=item 3. The text is checked.  A reference is never a place.  When
L<Geo::Location::Predicate> is installed, it decides what else is not a
place; for example, a string of only digits is not.  If the text is not a
place, C<geocode> croaks.  The check is done after step 2, so
C<'&#49;&#50;'> is refused as the digits C<'12'>.

=item 4. The cache is checked: first L1, then L2.  If the place is there,
the cached answer is returned, with C<geocoder> set to C<'cache'>.  If L1
remembers that the place was not found, nothing is returned and no
geocoder is asked.

=item 5. Each geocoder in the chain is asked in turn.  A geocoder is
skipped if the place does not match its C<regex>, or if its C<limit> is
used up.  If a geocoder dies, the error is logged, a warning is given, and
the next geocoder is asked.

=item 6. The first answer that has both a latitude and a longitude wins.
It is changed into the common structure (see L</DESCRIPTION>), stored in
the cache, and returned.

=item 7. If no geocoder gives a usable answer, the place is remembered as
"not found" in L1, and nothing is returned.

=back

B<Scalar context> returns one hash reference, or C<undef>.

B<List context> returns every answer from the geocoder that won, or an
empty list.  This is useful when a name, such as C<'Springfield'>, belongs
to many places.  Answers that the geocoder returned inside an array are
taken out of it, so the list never contains array references.  Answers
without a position may be in the list too; check C<geometry> before you
use each one.

    my @all = $list->geocode('Springfield, USA');

The C<geocoder> key of an answer holds the geocoder object that gave it.
When the answer came from the cache, it holds the string C<'cache'>.

=head3 API SPECIFICATION

=head4 INPUT

    {
        location => { type => 'string', optional => 0, min => 1 },
    }

=head4 OUTPUT

    {
        type => 'hashref',
        optional => 1,
    }

    # Return::Set schema, scalar context: a hashref, or undef when not found
    #   {
    #       geometry => { location => { lat => Num, lng => Num } },
    #       lat      => Num,
    #       lng      => Num,
    #       lon      => Num,                   # the same as lng
    #       geocoder => OBJECT | 'cache',
    #       debug    => Int,                   # line that read the answer
    #       ...                                # fields from the provider
    #   }
    #
    # List context: a list of hashrefs as above, or an empty list

=cut
596
597sub geocode {
598
247
17791
        my $self = shift;
599
600        # The geocoders are called inside eval; the caller's $@ is not ours to change
601
247
136
        local $@;
602
603        # Params::Get enforces 'location'; calling geocode() with no args causes
604        # get_params itself to croak with "Usage:" matching t/carp.t expectations
605
247
277
        my $params = Params::Get::get_params('location', @_);
606
245
2753
        if(!defined($params)) {
607
0
0
                $self->_error(__PACKAGE__, ' usage: geocode(location => $location)');
608
0
0
                return;
609        }
610
611
245
156
        my $location = $params->{'location'};
612
613        # A scalar reference is one way of passing the string it points at, and
614        # Params::Get dereferences it when it is the only argument.  Do it here as
615        # well, in every calling form, so that it means the string however the
616        # method is called.
617
245
2
202
3
        $location = ${$location} if ref($location) eq 'SCALAR';
618
619        # Reject empty or whitespace-only location strings
620
245
426
        if((!defined($location)) || (length($location) == 0)) {
621
6
9
                $self->_warn(__PACKAGE__, ' usage: geocode(location => $location)');
622
6
2516
                return;
623        }
624
625        # Collapse runs of whitespace and expand any HTML entities before the rule
626        # is asked: '&#49;&#50;' is the string of digits '12' once decoded, and it
627        # is the decoded string that is looked up.  A reference is left as it is
628        # for the rule to refuse.
629
239
190
        if(!ref($location)) {
630
228
1976
                $location =~ s/\s\s+/ /g;
631
228
874
                $location = decode_entities($location);
632        }
633
634        # What counts as a location is Geo::Location::Predicate's answer, not a
635        # second copy of it here: a reference is not a location however it would print,
636        # and a string of digits is what a numeric column holds when the wrong one has
637        # been read.
638
239
214
        if(!_could_be_a_location($location)) {
639
18
82
                $self->_error('Usage: ', __PACKAGE__, ': invalid input to geocode(), ',
640                        ref($location) ? 'a reference is not a location' : $location);
641
0
0
                return;
642        }
643
644        # Propagate the cleaned-up string so geocoders also receive the decoded form
645
221
908
        $params->{'location'} = $location;
646
647
221
379
        $self->_debug("location: $location\n");
648
649        # Capture the caller's line number once for all log entries in this call
650
221
213
        my @call_details = caller(0);
651
652        # ── L1 / L2 cache lookup ──────────────────────────────────────────────
653
654        # _cache() returns undef for both "not in cache" and "cached not-found";
655        # the not-found sentinel is handled internally so callers see undef either way
656
221
1544
        my $cached = $self->_cache($location);
657
658        # Only a reference is a geocode result.  A plain string in the cache under
659        # this key - a reverse_geocode() address, or a corrupted store - is not,
660        # and is looked up again rather than returned as if it were one.
661
221
229
        if(defined($cached) && !ref($cached)) {
662
1
1
                undef $cached;
663        }
664
665
221
159
        if(defined $cached) {
666                # A defined value means we have a genuine cached positive result
667
28
5
30
5
                my @rc = ref($cached) eq 'ARRAY' ? @{$cached} : ($cached);
668
669                # Mark every element as coming from cache.  Shallow-copy HASH results
670                # first so that neither the L1 cache entry nor any caller-held reference
671                # to the same hashref is mutated in place.
672
28
26
                for my $r (@rc) {
673
28
19
                        next unless ref($r);
674
28
26
33
57
                        $r = { %{$r} } if ref($r) eq 'HASH';
675
28
33
                        $r->{'geocoder'} = $CACHE_SOURCE;
676                }
677
678                # Scalar context: return the first (and usually only) element
679
28
27
                if(!wantarray) {
680
22
10
                        my $rc = $rc[0];
681
22
22
14
55
                        CORE::push @{$self->{'log'}}, {
682                                line      => $call_details[2],
683                                location  => $location,
684                                timetaken => 0,
685                                geocoder  => $CACHE_SOURCE,
686                                wantarray => 0,
687                                result    => $rc,
688                        };
689
22
22
                        $self->_debug(__PACKAGE__, ': ', __LINE__, ": cached\n");
690
22
36
                        return $rc;
691                }
692
693                # List context: return all cached candidates
694
6
6
6
14
                CORE::push @{$self->{'log'}}, {
695                        line      => $call_details[2],
696                        location  => $location,
697                        timetaken => 0,
698                        geocoder  => $CACHE_SOURCE,
699                        wantarray => 1,
700                        result    => \@rc,
701                };
702
6
6
                $self->_debug(__PACKAGE__, ': ', __LINE__, ": cached\n");
703
704                # Determine if every element is empty; if so return nothing
705
6
2
                my $allempty = 1;
706
6
6
                for my $r (@rc) {
707
6
8
                        if(ref($r) eq 'HASH') {
708
5
6
                                $allempty = 0 if defined $r->{geometry}{location}{lat};
709                        } elsif(ref($r) eq 'Geo::Location::Point') {
710
1
1
                                $allempty = 0;
711                        }
712                }
713
6
6
                return if $allempty;
714
4
8
                return @rc;
715        }
716
717        # Also check if this location is cached as a definite not-found in L1,
718        # without going through _cache() (which masks the sentinel as undef)
719
193
160
        if(exists $self->{'locations'}{$location}) {
720
6
5
                my $stored = $self->{'locations'}{$location};
721
6
16
                if(ref($stored) && ref($stored) eq _NOT_FOUND_CLASS) {
722
6
8
                        $self->_debug("No matches (cached)\n");
723
6
12
                        return wantarray ? () : undef;
724                }
725        }
726
727        # ── Try each geocoder in turn ─────────────────────────────────────────
728
729
187
187
85
170
        ENCODER: foreach my $g (@{$self->{geocoders}}) {
730
193
100
                my $geocoder = $g;
731
732                # Unpack a hashref entry and apply regex / limit guards
733
193
192
                if(ref($geocoder) eq 'HASH') {
734                        # Skip this entry if the location does not match its regex.  This
735                        # comes before the limit so that a location the geocoder is never
736                        # asked about does not spend one of its queries.
737
19
25
                        if(my $regex = $geocoder->{'regex'}) {
738
6
16
                                $self->_debug('consider ', ref($geocoder->{geocoder}), ": $regex\n");
739
6
18
                                if($location !~ $regex) {
740
4
6
                                        next;
741                                }
742                        }
743
744                        # Decrement and check the per-geocoder query limit
745
15
31
                        if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
746
14
23
                                $self->_debug("limit: $limit\n");
747
14
19
                                if($limit <= 0) {
748
8
8
                                        next;
749                                }
750
6
4
                                $geocoder->{'limit'}--;
751                        }
752
753                        # Unwrap the actual geocoder object from the hashref
754
7
7
                        $geocoder = $g->{'geocoder'};
755                }
756
757                # Start timing before the network call
758
181
77
                my @rc;
759
181
641
                my $timetaken = Time::HiRes::time();
760
761
181
524
                eval {
762                        # Geo::GeoNames uses a positional argument, not a hash
763
181
179
                        $self->_debug('trying ', ref($geocoder), "\n");
764
181
161
                        if(ref($geocoder) eq 'Geo::GeoNames') {
765
2
2
                                $self->_debug('username => ', $geocoder->username(), "\n");
766
2
1
                                die 'lost username' if(!defined($geocoder->username()));
767
1
2
                                @rc = $geocoder->geocode($location);
768                        } else {
769
179
179
86
277
                                @rc = $geocoder->geocode(%{$params});
770                        }
771                };
772
773
181
1360
                if($@) {
774                        # Log the failure and move on; do not abort the whole chain
775
12
40
                        my $log = {
776                                line      => $call_details[2],
777                                location  => $location,
778                                geocoder  => ref($geocoder),
779                                timetaken => Time::HiRes::time() - $timetaken,
780                                wantarray => wantarray,
781                                error     => $@,
782                        };
783
12
12
8
12
                        CORE::push @{$self->{'log'}}, $log;
784
12
23
                        $self->_warn(ref($geocoder), " '$location': $@");
785
12
4130
                        next ENCODER;
786                }
787
788
169
149
                $timetaken = Time::HiRes::time() - $timetaken;
789
790                # Geo::Coder::US::Census sometimes returns a truthy but empty result
791
169
174
                if((ref($geocoder) eq 'Geo::Coder::US::Census') &&
792                   !(defined($rc[0]->{result}{addressMatches}[0]->{coordinates}{y}))) {
793
2
5
                        my $log = {
794                                line      => $call_details[2],
795                                location  => $location,
796                                timetaken => $timetaken,
797                                geocoder  => ref($geocoder),
798                                wantarray => wantarray,
799                                result    => $RESULT_NONE,
800                        };
801
2
2
2
2
                        CORE::push @{$self->{'log'}}, $log;
802
2
3
                        next ENCODER;
803                }
804
805                # Geo::GeoNames wraps its results in arrays.  Every element of such an
806                # array is a candidate, so they are unwrapped into one list here, and a
807                # wrapped empty hash is then caught by the check below like any other
808
167
20153
5
123
9447
8
                @rc = map { ref($_) eq 'ARRAY' ? @{$_} : $_ } @rc;
809
810                # Reject empty result sets and trivially empty hashes
811
167
255
                if((scalar(@rc) == 0) ||
812
130
178
                   ((ref($rc[0]) eq 'HASH') && (scalar(keys %{$rc[0]}) == 0))) {
813
22
53
                        my $log = {
814                                line      => $call_details[2],
815                                location  => $location,
816                                timetaken => $timetaken,
817                                geocoder  => ref($geocoder),
818                                wantarray => wantarray,
819                                result    => $RESULT_NONE,
820                        };
821
22
22
9
23
                        CORE::push @{$self->{'log'}}, $log;
822
22
23
                        next ENCODER;
823                }
824
825                # ── Normalise each candidate result ──────────────────────────────
826
827                # Track which element was successfully normalised so we return it,
828                # not blindly return $rc[0] when a later element was the good one
829
145
74
                my $good_result;
830
831
145
100
                POSSIBLE_LOCATION: foreach my $l (@rc) {
832                        # Skip undefined or empty-string candidates
833
152
224
                        if((!defined($l)) || ($l eq '')) {
834
5
12
                                my $log = {
835                                        line      => $call_details[2],
836                                        location  => $location,
837                                        timetaken => $timetaken,
838                                        geocoder  => ref($geocoder),
839                                        wantarray => wantarray,
840                                        result    => $RESULT_NONE,
841                                };
842
5
5
5
3
                                CORE::push @{$self->{'log'}}, $log;
843
5
9
                                next ENCODER;
844                        }
845
846                        # Skip bare scalars (e.g. integer 0, plain strings) that are
847                        # not references; they cannot be hash-dereferenced below
848
147
127
                        next unless ref($l);
849
850                        # Stamp the source geocoder on the result before normalisation
851
139
133
                        $l->{'geocoder'} = ref($geocoder);
852
853                        $self->_debug(ref($geocoder), ': ',
854
139
134
                                Data::Dumper->new([\$l])->Dump()) if($self->{'debug'} >= 2);
855
856                        # Geo::Location::Point objects carry their own accessors;
857                        # upgrade the geocoder field and populate the canonical geometry
858                        # structure so callers can rely on geometry.location.{lat,lng}
859
139
131
                        if(ref($l) eq 'Geo::Location::Point') {
860
4
2
                                $l->{'geocoder'} = $geocoder;
861
862                                # Populate canonical geometry structure from the GLP's own fields
863
4
11
                                if(!defined($l->{geometry}{location}{lat}) && defined($l->{lat})) {
864
3
2
                                        $l->{geometry}{location}{lat} = $l->{lat};
865
3
5
                                        $l->{geometry}{location}{lng} = $l->{lng} // $l->{lon};
866                                }
867
868                                # Convenience aliases (idempotent if already set by GLP)
869
4
7
                                $l->{'lat'} //= $l->{geometry}{location}{lat};
870
4
4
                                $l->{'lng'} //= $l->{geometry}{location}{lng};
871
4
8
                                $l->{'lon'} //= $l->{geometry}{location}{lng};
872
873
4
4
3
10
                                CORE::push @{$self->{'log'}}, {
874                                        line      => $call_details[2],
875                                        location  => $location,
876                                        timetaken => $timetaken,
877                                        geocoder  => ref($geocoder),
878                                        wantarray => wantarray,
879                                        result    => $l,
880                                };
881
4
7
                                $good_result = $l;
882
4
3
                                last POSSIBLE_LOCATION;
883                        }
884
885                        # Only HASH results need normalisation
886
135
112
                        next if(ref($l) ne 'HASH');
887
888
135
102
                        if($l->{'error'}) {
889                                # A top-level 'error' key signals a provider-level failure
890                                my $log = {
891                                        line      => $call_details[2],
892                                        location  => $location,
893                                        timetaken => $timetaken,
894                                        geocoder  => ref($geocoder),
895                                        wantarray => wantarray,
896
2
5
                                        error     => $l->{'error'},
897                                };
898
2
2
2
3
                                CORE::push @{$self->{'log'}}, $log;
899
2
4
                                next ENCODER;
900                        } else {
901                                # Map provider-specific fields to the canonical geometry structure
902
133
165
                                if(!defined($l->{geometry}{location}{lat})) {
903
127
64
                                        my ($lat, $long);
904
905
127
279
                                        if(defined($l->{lat}) && defined($l->{lon})) {
906                                                # OSM / RandMcNally: top-level lat/lon fields
907
99
63
                                                $lat   = $l->{lat};
908
99
65
                                                $long  = $l->{lon};
909
99
71
                                                $l->{'debug'} = __LINE__;
910                                        } elsif($l->{BestLocation}) {
911                                                # Bing Maps: BestLocation.Coordinates.{Latitude,Longitude}
912
2
2
                                                $lat   = $l->{BestLocation}->{Coordinates}->{Latitude};
913
2
2
                                                $long  = $l->{BestLocation}->{Coordinates}->{Longitude};
914
2
2
                                                $l->{'debug'} = __LINE__;
915                                        } elsif($l->{point}) {
916                                                # Bing Maps alternative: point.coordinates[lat, lng]
917
3
4
                                                $lat   = $l->{point}->{coordinates}[0];
918
3
2
                                                $long  = $l->{point}->{coordinates}[1];
919
3
4
                                                $l->{'debug'} = __LINE__;
920                                        } elsif(defined($l->{latt})) {
921                                                # geocoder.ca: latt / longt fields
922
2
2
                                                $lat   = $l->{latt};
923
2
2
                                                $long  = $l->{longt};
924
2
2
                                                $l->{'debug'} = __LINE__;
925                                        } elsif(defined($l->{latitude})) {
926                                                # postcodes.io, Geo::Coder::Free: latitude / longitude
927
3
2
                                                $lat   = $l->{latitude};
928
3
4
                                                $long  = $l->{longitude};
929
3
5
                                                if(my $type = $l->{'local_type'}) {
930                                                        # Carry the local_type hint forward as a normalised 'type'
931
1
2
                                                        $l->{'type'} = lcfirst($type);
932                                                }
933
3
3
                                                $l->{'debug'} = __LINE__;
934                                        } elsif(defined($l->{'properties'}{'geoLatitude'})) {
935                                                # HERE / Ovi: properties.geoLatitude / geoLongitude
936
1
1
                                                $lat   = $l->{properties}{geoLatitude};
937
1
1
                                                $long  = $l->{properties}{geoLongitude};
938
1
2
                                                $l->{'debug'} = __LINE__;
939                                        } elsif($l->{'results'}[0]->{'geometry'}) {
940
3
5
                                                if($l->{'results'}[0]->{'geometry'}->{'location'}) {
941                                                        # DataScienceToolkit mirrors the Google Maps shape
942
1
1
                                                        $lat   = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lat'};
943
1
1
                                                        $long  = $l->{'results'}[0]->{'geometry'}->{'location'}->{'lng'};
944
1
2
                                                        $l->{'debug'} = __LINE__;
945                                                } else {
946                                                        # OpenCage places lat/lng directly under geometry
947
2
3
                                                        $lat   = $l->{'results'}[0]->{'geometry'}->{'lat'};
948
2
1
                                                        $long  = $l->{'results'}[0]->{'geometry'}->{'lng'};
949
2
3
                                                        $l->{'debug'} = __LINE__;
950                                                }
951                                        } elsif($l->{'RESULTS'}) {
952                                                # GeoCodeFarm: RESULTS[0].COORDINATES.{latitude,longitude}
953
2
3
                                                $lat   = $l->{'RESULTS'}[0]{'COORDINATES'}{'latitude'};
954
2
2
                                                $long  = $l->{'RESULTS'}[0]{'COORDINATES'}{'longitude'};
955
2
2
                                                $l->{'debug'} = __LINE__;
956                                        } elsif(defined($l->{result}{addressMatches}[0]->{coordinates}{y})) {
957                                                # US Census Bureau: result.addressMatches[0].coordinates.{y,x}
958
1
2
                                                $lat   = $l->{result}{addressMatches}[0]->{coordinates}{y};
959
1
2
                                                $long  = $l->{result}{addressMatches}[0]->{coordinates}{x};
960
1
1
                                                $l->{'debug'} = __LINE__;
961                                        } elsif(defined($l->{lat})) {
962                                                # Geo::GeoNames: lat / lng (reached only after lat+lon check fails)
963
3
4
                                                $lat   = $l->{lat};
964
3
3
                                                $long  = $l->{lng};
965
3
4
                                                $l->{'debug'} = __LINE__;
966                                        } elsif($l->{features}) {
967
4
8
                                                if($l->{features}[0]->{center}) {
968                                                        # Geo::Coder::Mapbox: center is [lng, lat]
969
1
1
                                                        $lat   = $l->{features}[0]->{center}[1];
970
1
1
                                                        $long  = $l->{features}[0]->{center}[0];
971
1
19
                                                        $l->{'debug'} = __LINE__;
972                                                } elsif($l->{'features'}[0]{'geometry'}{'coordinates'}) {
973                                                        # Geo::Coder::GeoApify: coordinates is [lng, lat]
974
1
1
                                                        $lat   = $l->{'features'}[0]{'geometry'}{'coordinates'}[1];
975
1
1
                                                        $long  = $l->{'features'}[0]{'geometry'}{'coordinates'}[0];
976
1
1
                                                        $l->{'debug'} = __LINE__;
977                                                } else {
978                                                        # GeoApify signals not-found via empty features, not an error
979
2
6
                                                        next ENCODER;
980                                                }
981                                        } else {
982
4
7
                                                $l->{'debug'} = __LINE__;
983                                        }
984
985
125
158
                                        if(defined($lat) && defined($long)) {
986                                                # Populate the canonical geometry structure
987
121
121
                                                $l->{geometry}{location}{lat} = $lat;
988
121
87
                                                $l->{geometry}{location}{lng} = $long;
989                                                # Compatibility aliases expected by callers
990
121
75
                                                $l->{'lat'} = $lat;
991
121
580
                                                $l->{'lon'} = $long;
992                                        } else {
993                                                # No coordinates extracted; clean up any partial data
994
4
5
                                                delete $l->{'geometry'};
995
4
2
                                                delete $l->{'lat'};
996
4
3
                                                delete $l->{'lon'};
997                                        }
998
999                                        # geocoder.xyz provides a country name under 'standard'
1000
125
194
                                        if($l->{'standard'}{'countryname'}) {
1001
1
2
                                                $l->{'address'}{'country'} = $l->{'standard'}{'countryname'};
1002                                        }
1003                                }
1004
1005                                # A result whose geometry has lat but no lng may still carry the
1006                                # longitude at the top level, some geocoders using the legacy
1007                                # 'long' key for it
1008
131
125
                                if(defined($l->{geometry}{location}{lat})) {
1009
127
123
                                        $l->{geometry}{location}{lng} //= $l->{'lng'} // $l->{'lon'} // $l->{'long'};
1010                                }
1011
1012                                # Only a result with both coordinates is usable; anything less is
1013                                # passed over so that a later element, or the next geocoder, can
1014                                # answer instead
1015
131
206
                                if(defined($l->{geometry}{location}{lat}) && defined($l->{geometry}{location}{lng})) {
1016                                        $self->_debug($l->{geometry}{location}{lat}, '/',
1017
125
158
                                                $l->{geometry}{location}{lng}, "\n");
1018
1019                                        # Store the geocoder object (not just its name) on the result
1020
125
99
                                        $l->{geocoder} = $geocoder;
1021
125
98
                                        $l->{'lat'} //= $l->{geometry}{location}{lat};
1022
125
204
                                        $l->{'lng'} //= $l->{geometry}{location}{lng};
1023
125
93
                                        $l->{'lon'} //= $l->{geometry}{location}{lng};
1024
1025
125
290
                                        my $log = {
1026                                                line      => $call_details[2],
1027                                                location  => $location,
1028                                                timetaken => $timetaken,
1029                                                geocoder  => ref($geocoder),
1030                                                wantarray => wantarray,
1031                                                result    => $l,
1032                                        };
1033
125
125
73
97
                                        CORE::push @{$self->{'log'}}, $log;
1034
1035                                        # Record which element succeeded, then exit the inner loop
1036
125
109
                                        $good_result = $l;
1037
125
97
                                        last POSSIBLE_LOCATION;
1038                                }
1039                        }
1040                }
1041
1042                # Only attempt to return / cache if normalisation actually succeeded
1043
136
117
                next ENCODER unless defined $good_result;
1044
1045
129
149
                $self->_debug('Number of matches from ', ref($geocoder), ': ',
1046                        scalar(@rc), "\n");
1047
1048
129
126
                if($self->{'debug'} >= 2) {
1049                        # Use 'local' to avoid permanently altering the global Maxdepth
1050
2
2
                        local $Data::Dumper::Maxdepth = 10;
1051
2
4
                        $self->_debug(Data::Dumper->new([\@rc])->Dump());
1052                }
1053
1054                # Only a result with both coordinates is accepted above, so this can
1055                # fail only if the normalisation itself is wrong
1056
129
207
                if((!defined($good_result->{lat})) || (!defined($good_result->{lng}))) {
1057
0
0
                        $self->_warn(Data::Dumper->new([\@rc])->Dump());
1058
0
0
                        $self->_error("BUG: '$location': HASH exists but is not sensible");
1059                }
1060
1061
129
104
                if(wantarray) {
1062                        # A bare scalar the loop passed over is not a result
1063
9
10015
17
3844
                        @rc = grep { ref } @rc;
1064
9
13
                        $self->_cache($location, \@rc);
1065
9
163
                        return @rc;
1066                }
1067
1068
120
122
                $self->_cache($location, $good_result);
1069
120
333
                return $good_result;
1070        }
1071
1072        # ── No geocoder produced a usable result ──────────────────────────────
1073
1074
58
52
        $self->_debug("No matches\n");
1075
1076        # Cache the not-found result so repeated calls do not hammer all backends
1077
58
58
        $self->_cache($location, undef);
1078
1079
58
122
        return wantarray ? () : undef;
1080}
1081
1082# =============================================================================
1083
1084 - 1129
=head2 ua

Gives a user agent (the object that makes the web requests) to every
geocoder in the chain.  Use it to set a proxy or a time-out for all of
them at once.

    use LWP::UserAgent;

    my $ua = LWP::UserAgent->new(timeout => 10);
    $ua->env_proxy(1);
    $list->ua($ua);

If C<$ua> has both C<clone()> and C<agent()> methods, as
L<LWP::UserAgent> does, each geocoder is given its own copy.  The
C<agent> string of each copy is set to the geocoder's class name and
version, such as C<'Geo::Coder::OSM/0.03'>, so each service can see which
module is calling it.  Otherwise every geocoder is given C<$ua> itself.

Add the geocoders before you call C<ua>: geocoders added later do not get
the user agent.

There is no way to read the user agent back, because each geocoder may
have a different one.

If C<$ua> is missing or false, nothing changes and nothing is returned.
C<ua> croaks with C<No geocoder found> if an entry in the chain has no
geocoder.

=head3 API SPECIFICATION

=head4 INPUT

    {
        ua => { type => 'object', optional => 1 },
    }

=head4 OUTPUT

    {
        type => 'object',
        optional => 1,
    }

    # The same $ua that was passed in, or nothing when $ua is false

=cut
1130
1131sub ua
1132{
1133
35
2225
        my ($self, $ua) = @_;
1134
1135        # Nothing to propagate if no UA was supplied
1136
35
65
        return unless $ua;
1137
1138        # Push the UA into every geocoder in the chain
1139
28
28
16
27
        foreach my $g (@{$self->{geocoders}}) {
1140
31
45
                my $geocoder = (ref($g) eq 'HASH') ? $g->{geocoder} : $g;
1141                # Guard against a misconfigured entry that has no geocoder object
1142
31
82
                Carp::croak('No geocoder found') unless defined $geocoder;
1143
1144                # When the incoming UA supports clone(), create a per-geocoder copy
1145                # and set the geocoder's own agent string on that copy.
1146                # Some APIs (e.g. OSM Nominatim) require a specific User-Agent and
1147                # refuse requests that carry the generic libwww-perl default.
1148                # The agent string is derived from the geocoder class name and version
1149                # (e.g. 'Geo::Coder::OSM/0.03') without reading the geocoder's current
1150                # UA, which would trigger any spy or hook installed on its ua() method.
1151
27
89
                if($ua->can('clone') && $ua->can('agent')) {
1152
13
17
                        my $per_ua  = $ua->clone();
1153
13
36
                        my $class   = ref($geocoder);
1154
13
13
22
55
                        my $version = eval { $geocoder->VERSION() } // '';
1155
13
23
                        $per_ua->agent($version ? "$class/$version" : $class);
1156
13
55
                        $geocoder->ua($per_ua);
1157                } else {
1158
14
16
                        $geocoder->ua($ua);
1159                }
1160        }
1161
1162        # Return the UA so callers can verify what was set (API contract)
1163
24
50
        return $ua;
1164}
1165
1166# =============================================================================
1167
1168 - 1234
=head2 reverse_geocode

Finds the address of a position: the opposite of C<geocode>.

    my $address = $list->reverse_geocode(latlng => '51.5034,-0.1276');
    my $address = $list->reverse_geocode('51.5034,-0.1276');    # the same
    print "Address: $address\n" if defined($address);

    my @addresses = $list->reverse_geocode(latlng => '51.5034,-0.1276');

C<latlng> is the latitude, a comma, and the longitude, in that order.  It
must be two plain decimal numbers written with the digits C<0> to C<9>.
Spaces, tabs and a C<+> or C<-> sign are allowed.  Anything else, such as
C<'51.5;-0.1'>, C<'1e5,2'> or C<'51.5'>, makes C<reverse_geocode> croak
before any geocoder is asked.

The geocoders are asked in turn, as for C<geocode>.  C<limit> is honoured;
C<regex> is not.  If a geocoder complains that it does not know the
C<latlng> parameter, it is asked again with only C<lat> and C<lon>.  If a
geocoder dies for any other reason, a warning is given and the next
geocoder is asked.

B<Scalar context> returns one address string, or C<undef>.

B<List context> returns every address from the first geocoder that has
any, or an empty list.

An address is taken from the first of these that the answer has:

=over 4

=item * C<display_name> (OpenStreetMap, and others that copy it)

=item * the fields of geocoder.ca: C<stnumber>, C<staddress>, C<city> and
C<prov> for Canada, giving for example
C<'1 Sussex Dr, Ottawa, ON'>; or the C<usa> fields for the United States,
giving for example C<'1600 Pennsylvania Ave, Washington, DC, USA'>

=item * C<features[0].properties.formatted> (GeoApify); only the first
feature is used

=back

If a geocoder returns a plain string, the string is the address.

Addresses are cached under the C<latlng> string, so asking again for the
same position does not ask any geocoder.

=head3 API SPECIFICATION

=head4 INPUT

    {
        latlng => { type => 'string', optional => 0, matches => qr/^[ \t]*[-+]?(?:[0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)[ \t]*,[ \t]*[-+]?(?:[0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)[ \t]*$/ },
    }

=head4 OUTPUT

    {
        type => 'string',
        optional => 1,
    }

    # Scalar context: an address string, or undef when not found
    # List context: a list of address strings, or an empty list

=cut
1235
1236sub reverse_geocode {
1237
86
9504
        my $self = shift;
1238
1239        # The geocoders are called inside eval; the caller's $@ is not ours to change
1240
86
51
        local $@;
1241
1242
86
110
        my $params = Params::Get::get_params('latlng', \@_);
1243
1244
84
746
        my $latlng = $params->{'latlng'} or Carp::croak('Usage: reverse_geocode(latlng => $location)');
1245
1246        # Only a numeric pair is a position; anything else would be split into
1247        # nonsense coordinates and sent to every backend
1248
82
483
        Carp::croak('Usage: reverse_geocode(latlng => $location)')
1249                if(ref($latlng) || ($latlng !~ $LATLNG_RE));
1250
1251        # Split into components; populate convenience keys for geocoders that want them
1252
69
90
        my ($latitude, $longitude) = split(/,/, $latlng);
1253
69
143
        $params->{'lat'} //= $latitude;
1254
69
103
        $params->{'lon'} //= $longitude;
1255
1256        # Check L1 / L2 cache before hitting any backend.  A list-context call
1257        # caches every address as an arrayref and a scalar-context call caches
1258        # one string, so the entry is unpacked for the context asking for it.
1259
69
74
        if(defined(my $rc = $self->_cache($latlng))) {
1260
7
13
                if(ref($rc) eq 'ARRAY') {
1261
4
3
6
7
                        return wantarray ? @{$rc} : $rc->[0];
1262                }
1263
3
6
                return $rc;
1264        }
1265
1266
62
61
        my @call_details = caller(0);
1267
1268
62
62
416
53
        foreach my $g (@{$self->{geocoders}}) {
1269
65
37
                my $geocoder = $g;
1270
1271                # Apply the per-geocoder limit guard for hashref entries
1272
65
64
                if(ref($geocoder) eq 'HASH') {
1273
6
14
                        if(exists($geocoder->{'limit'}) && defined(my $limit = $geocoder->{'limit'})) {
1274
5
9
                                $self->_debug("limit: $limit\n");
1275
5
5
                                if($limit <= 0) {
1276
4
5
                                        next;
1277                                }
1278
1
1
                                $geocoder->{'limit'}--;
1279                        }
1280
2
2
                        $geocoder = $g->{'geocoder'};
1281                }
1282
1283
61
100
                $self->_debug('trying ', ref($geocoder), "\n");
1284
1285
61
57
                if(wantarray) {
1286                        # ── List context: collect all address strings from this geocoder ───
1287
22
11
                        my @rc;
1288                        my @locs;
1289
22
22
22
16
30
33
                        eval { @locs = $geocoder->reverse_geocode(%{$params}) };
1290
1291                        # Some geocoders (e.g. Geo::Coder::GeoApify) use strict parameter
1292                        # validation and reject the 'latlng' key as unknown.  Retry without
1293                        # it -- lat and lon are already in %params from the split above.
1294                        # Other geocoders (e.g. Geo::Coder::CA) require 'latlng', so the
1295                        # first attempt must include it.
1296
22
104
                        if($@ =~ /Unknown parameter.*latlng|latlng.*[Uu]nknown/s) {
1297
2
2
1
4
                                my %no_latlng = %{$params};
1298
2
2
                                delete $no_latlng{'latlng'};
1299
2
2
                                $@ = '';
1300
2
2
2
3
                                eval { @locs = $geocoder->reverse_geocode(%no_latlng) };
1301                        }
1302
1303
22
31
                        if($@) {
1304
3
3
1
9
                                CORE::push @{$self->{'log'}}, {
1305                                        line      => $call_details[2],
1306                                        location  => $latlng,
1307                                        geocoder  => ref($geocoder),
1308                                        timetaken => 0,
1309                                        wantarray => 1,
1310                                        error     => $@,
1311                                };
1312
3
7
                                $self->_warn(ref($geocoder), " '$latlng': $@");
1313
3
959
                                next;
1314                        }
1315
1316
19
25
                        $self->_debug(Data::Dumper->new([\@locs])->Dump()) if($self->{'debug'} >= 2);
1317
1318
19
21
                        foreach my $loc (@locs) {
1319
27
21
                                next unless(defined($loc));
1320
1321                                # A plain string is the address itself, as it is in scalar
1322                                # context; anything else that is not a HASH has none to give
1323
26
26
                                if(!ref($loc)) {
1324
2
2
                                        CORE::push @rc, $loc if(length($loc));
1325
2
2
                                        next;
1326                                }
1327
24
40
                                next unless(reftype($loc) eq 'HASH');
1328
1329
23
29
                                if(my $name = $loc->{'display_name'}) {
1330                                        # OSM returns the full address in display_name
1331
19
19
                                        CORE::push @rc, $name;
1332                                } elsif($loc->{'city'}) {
1333                                        # Geo::Coder::CA: build the address from individual fields
1334
2
4
                                        CORE::push @rc, _build_ca_address($loc);
1335                                } elsif($loc->{features}) {
1336                                        # GeoApify: formatted string inside a features array
1337                                        CORE::push @rc,
1338
2
3
                                                $loc->{features}[0]->{properties}{formatted};
1339
2
2
                                        last;   # only one result from this provider
1340                                }
1341                        }
1342
1343
19
19
11
59
                        CORE::push @{$self->{'log'}}, {
1344                                line      => $call_details[2],
1345                                location  => $latlng,
1346                                geocoder  => ref($geocoder),
1347                                timetaken => 0,
1348                                wantarray => 1,
1349                                result    => \@rc,
1350                        };
1351
1352                        # A geocoder with no address for the position has not answered;
1353                        # the next one may
1354
19
25
                        next unless(scalar(@rc));
1355
1356
17
20
                        $self->_cache($latlng, \@rc);
1357
17
53
                        return @rc;
1358
1359                } else {
1360                        # ── Scalar context: return the first address string ────────────────
1361                        my $rc = $self->_cache($latlng)
1362
39
39
39
40
22
57
                                // eval { $geocoder->reverse_geocode(%{$params}) };
1363
1364                        # Same strict-validation fallback as the list-context path above
1365
39
206
                        if($@ =~ /Unknown parameter.*latlng|latlng.*[Uu]nknown/s) {
1366
7
7
6
12
                                my %no_latlng = %{$params};
1367
7
8
                                delete $no_latlng{'latlng'};
1368
7
5
                                $@ = '';
1369
7
7
5
33
                                $rc = eval { $geocoder->reverse_geocode(%no_latlng) };
1370                        }
1371
1372
39
78
                        if($@) {
1373
9
9
7
30
                                CORE::push @{$self->{'log'}}, {
1374                                        line      => $call_details[2],
1375                                        location  => $latlng,
1376                                        geocoder  => ref($geocoder),
1377                                        timetaken => 0,
1378                                        wantarray => 0,
1379                                        error     => $@,
1380                                };
1381
9
27
                                $self->_warn(ref($geocoder), " '$latlng': $@");
1382
9
2732
                                next;
1383                        }
1384
1385                        # A bare string needs no further processing
1386
30
27
                        next unless defined $rc;
1387
28
24
                        if(!ref($rc)) {
1388
3
3
2
11
                                CORE::push @{$self->{'log'}}, {
1389                                        line      => $call_details[2],
1390                                        location  => $latlng,
1391                                        geocoder  => ref($geocoder),
1392                                        timetaken => 0,
1393                                        wantarray => 0,
1394                                        result    => $rc,
1395                                };
1396
3
9
                                return $rc;
1397                        }
1398
1399
25
32
                        $self->_debug(Data::Dumper->new([$rc])->Dump()) if($self->{'debug'} >= 2);
1400
1401
25
36
                        if(my $name = $rc->{'display_name'}) {
1402                                # OSM
1403
20
20
17
61
                                CORE::push @{$self->{'log'}}, {
1404                                        line      => $call_details[2],
1405                                        location  => $latlng,
1406                                        geocoder  => ref($geocoder),
1407                                        timetaken => 0,
1408                                        wantarray => 0,
1409                                        result    => $name,
1410                                };
1411
20
15
                                return $self->_cache($latlng, $name);
1412                        }
1413
1414
5
7
                        if($rc->{'city'}) {
1415                                # Geo::Coder::CA
1416
3
5
                                my $name = _build_ca_address($rc);
1417
3
3
3
8
                                CORE::push @{$self->{'log'}}, {
1418                                        line      => $call_details[2],
1419                                        location  => $latlng,
1420                                        geocoder  => ref($geocoder),
1421                                        timetaken => 0,
1422                                        wantarray => 0,
1423                                        result    => $name,
1424                                };
1425
3
5
                                return $self->_cache($latlng, $name);
1426                        }
1427
1428
2
2
                        if($rc->{features}) {
1429                                # GeoApify
1430
2
3
                                my $name = $rc->{features}[0]->{properties}{formatted};
1431
2
2
2
6
                                CORE::push @{$self->{'log'}}, {
1432                                        line      => $call_details[2],
1433                                        location  => $latlng,
1434                                        geocoder  => ref($geocoder),
1435                                        timetaken => 0,
1436                                        wantarray => 0,
1437                                        result    => $name,
1438                                };
1439
2
2
                                return $self->_cache($latlng, $name);
1440                        }
1441                }
1442        }
1443
1444
17
42
        return;
1445}
1446
1447# =============================================================================
1448
1449 - 1511
=head2 log

Returns the record of what has happened since the object was made, or
since the last C<flush()>.  There is one entry for each question asked of
a geocoder, and one for each answer that C<geocode()> serves from the
cache.  C<reverse_geocode()> adds no entry when it answers from the
cache.

    for my $entry (@{ $list->log() }) {
        printf "%s: %.3fs via %s\n",
            $entry->{location},
            $entry->{timetaken},
            $entry->{geocoder};
    }

Each entry is a hash reference with these keys:

=over 4

=item * C<location> - the place name or C<latlng> that was asked about

=item * C<geocoder> - the class name of the geocoder, or C<'cache'>

=item * C<timetaken> - how long the geocoder took, in seconds

=item * C<line> - the line of your program that made the call

=item * C<wantarray> - true if the call was made in list context

=item * C<result> - the answer, when there was one; the string
C<'not found'> when the geocoder had none

=item * C<error> - the error, when the geocoder died or returned an
error; this can be an exception object, not only a string

=back

The returned array reference is the object's own record, not a copy.

=head3 API SPECIFICATION

=head4 INPUT

    []

=head4 OUTPUT

    {
        type => 'arrayref',
    }

    # Each element:
    #   {
    #       line      => Int,
    #       location  => Str,
    #       timetaken => Num,
    #       geocoder  => Str,                     # class name, or 'cache'
    #       wantarray => Bool,
    #       result    => HASHREF | ARRAYREF | Str,  # on success
    #       error     => Str | OBJECT | HASHREF,    # on failure
    #   }

=cut
1512
1513sub log {
1514
44
3945
        my $self = shift;
1515
1516        # Guard against the state left by flush(); always return a valid arrayref
1517
44
99
        return $self->{'log'} // [];
1518}
1519
1520# =============================================================================
1521
1522 - 1545
=head2 flush

Empties the log and returns the object itself, so that it can be chained.

    $list->geocode('Paris, France');
    my $entries = $list->log();
    $list->flush()->geocode('London, UK');

C<flush> does B<not> empty the cache.

=head3 API SPECIFICATION

=head4 INPUT

    []

=head4 OUTPUT

    {
        type => 'object',
        isa  => 'Geo::Coder::List',
    }

=cut
1546
1547sub flush {
1548
16
109
        my $self = shift;
1549
1550        # Reset to an empty arrayref so log() always returns a valid reference
1551
16
18
        $self->{'log'} = [];
1552
1553
16
15
        return $self;
1554}
1555
1556# =============================================================================
1557# PRIVATE HELPERS
1558# =============================================================================
1559
1560# _build_ca_address
1561#
1562# Purpose:    Assemble a printable address string from a Geo::Coder::CA
1563#             reverse-geocode response.  The CA response uses different keys
1564#             for US addresses (nested under 'usa') vs Canadian ones.
1565#
1566# Entry:      $loc - HASHREF from Geo::Coder::CA reverse_geocode()
1567#
1568# Exit:       Returns a plain string, or empty string if nothing was found.
1569#
1570# Notes:      Street number and name are joined with a space; other parts
1571#             (city, province/state, country) are joined with ', '.
1572
1573sub _build_ca_address
1574{
1575
16
16892
        my $loc = $_[0];
1576
16
12
        my $name  = '';
1577
1578
16
20
        if(my $usa = $loc->{'usa'}) {
1579                # US address layout inside a CA result
1580
5
9
                $name  = $usa->{'usstnumber'} // '';
1581                # Street name follows number with a space; if no number, no leading space
1582
5
10
                $name .= ($name ? ' ' : '') . $usa->{'usstaddress'} if $usa->{'usstaddress'};
1583                # City, state, country each separated by ', '; skip separator if name empty
1584
5
12
                $name .= ($name ? ', ' : '') . $usa->{'uscity'}     if $usa->{'uscity'};
1585
5
7
                $name .= ($name ? ', ' : '') . $usa->{'state'}      if $usa->{'state'};
1586                # Country is always appended for the US branch
1587
5
6
                $name .= ($name ? ', ' : '') . 'USA';
1588        } else {
1589                # Canadian address layout
1590
11
23
                $name  = $loc->{'stnumber'} // '';
1591                # Street name follows number with a space; if no number, no leading space
1592
11
22
                $name .= ($name ? ' ' : '') . $loc->{'staddress'}   if $loc->{'staddress'};
1593
11
21
                $name .= ($name ? ', ' : '') . $loc->{'city'}        if $loc->{'city'};
1594
11
20
                $name .= ($name ? ', ' : '') . $loc->{'prov'}        if $loc->{'prov'};
1595        }
1596
1597
16
26
        return $name;
1598}
1599
1600# -----------------------------------------------------------------------------
1601
1602# _cache
1603#
1604# Read from or write to the two-level cache.
1605#             L1 is an in-process HASH (always active).
1606#             L2 is an optional CHI-compatible object or a plain HASH ref.
1607#
1608# Entry (write): _cache($key, $value)
1609#             $value may be undef, which is stored as $NOT_FOUND_SENTINEL so
1610#             subsequent reads can distinguish "cached not-found" from "never
1611#             looked up".  Detect the write path by testing scalar(@_) before
1612#             shifting, not by truthiness of the value.
1613#
1614# Entry (read):  _cache($key)
1615#
1616# Exit (write):  Returns $value (undef if the location was not found).
1617# Exit (read):   Returns the cached value, or undef if not in cache.
1618#             $NOT_FOUND_SENTINEL is never surfaced to callers; undef is
1619#             returned in its place so callers handle both cases identically.
1620#
1621# Side effects: May update $self->{locations} (L1) and $self->{'cache'} (L2).
1622#
1623# Notes:      Cache TTLs are taken from $self->{cache_*_duration}, which are
1624#             initialised from %config and overridable via Object::Configure.
1625#             Not-found sentinels are stored only in L1 to avoid leaking
1626#             internal implementation details into an external L2 store.
1627
1628sub _cache {
1629
602
906
        my $self = shift;
1630
602
289
        my $key  = shift;
1631
1632        # ── Write path ────────────────────────────────────────────────────────
1633        # Detect a write call by the presence of a third argument (even if undef).
1634        # Testing truthiness of the value would silently swallow not-found results.
1635
1636
602
451
        if(scalar(@_)) {
1637
261
121
                my $value = shift;
1638
1639                # Store a sentinel for not-found so we can skip backends on repeat calls
1640
261
189
                my $stored = defined($value) ? $value : $NOT_FOUND_SENTINEL;
1641
261
288
                $self->{locations}->{$key} = $stored;
1642
1643
261
122
                my $rc = $value;
1644
1645
261
251
                if($self->{'cache'}) {
1646                        # L2 is written from copies: the caller is returned $value and L1
1647                        # holds it, so what is stripped for storage must not be stripped
1648                        # from what they see
1649
35
22
                        my $l2value = $value;
1650
35
18
                        my $duration;
1651
1652
35
66
                        if(ref($value) eq 'ARRAY') {
1653
10
11
10
6
12
8
                                $l2value = [ map { _l2_copy($_, $self->{'debug'}) } @{$value} ];
1654
1655
10
10
23
10
                                foreach my $item (@{$l2value}) {
1656
11
10
                                        next unless ref($item) eq 'HASH';
1657
1658
9
9
                                        unless(_has_lat($item)) {
1659                                                # Geometry without a lat may be a transient failure, so
1660                                                # retry soon; no geometry at all is a miss
1661                                                $duration //= defined($item->{geometry})
1662                                                        ? $self->{'cache_part_duration'}
1663
5
12
                                                        : $self->{'cache_miss_duration'};
1664
5
4
                                                $rc = undef;
1665                                        }
1666                                }
1667
1668                                # All items were clean: use the full hit duration
1669
10
16
                                $duration //= $self->{'cache_hit_duration'};
1670
1671                        } elsif(ref($value) eq 'HASH') {
1672
21
31
                                $l2value = _l2_copy($value, $self->{'debug'});
1673
1674
21
29
                                if(_has_lat($l2value)) {
1675                                        # Confirmed location: cache for a full month
1676
14
18
                                        $duration = $self->{'cache_hit_duration'};
1677                                } elsif(defined($l2value->{geometry})) {
1678                                        # Partial geometry: may be a transient failure, retry soon
1679
4
3
                                        $duration = $self->{'cache_part_duration'};
1680
4
5
                                        $rc = undef;
1681                                } else {
1682                                        # No geometry at all: place probably does not exist
1683
3
2
                                        $duration = $self->{'cache_miss_duration'};
1684
3
3
                                        $rc = undef;
1685                                }
1686                        } else {
1687                                # Scalar string or a blessed object (e.g. Geo::Location::Point)
1688
4
7
                                $l2value = _l2_copy($value, 1);
1689
4
4
                                $duration = $self->{'cache_hit_duration'};
1690                        }
1691
1692
35
47
                        $self->_debug(Data::Dumper->new([$l2value])->Dump()) if($self->{'debug'});
1693
1694                        # Do not push the not-found sentinel into L2
1695
35
71
                        if(!defined($value)) {
1696                                # value is not-found; L1 sentinel is sufficient
1697                        } elsif(ref($self->{'cache'}) eq 'HASH') {
1698
15
18
                                $self->{'cache'}->{$key} = $l2value;
1699                        } else {
1700                                # L2 is only an optimisation: a store that is down must not
1701                                # lose the caller the result that has already been found
1702
16
9
                                local $@;
1703
16
16
15
14
27
80
                                eval { $self->{'cache'}->set($key, $l2value, $duration); 1 }
1704                                        or $self->_warn(__PACKAGE__, ": cache set failed for '$key': $@");
1705                        }
1706                }
1707
1708
261
514
                return $rc;
1709        }
1710
1711        # ── Read path ─────────────────────────────────────────────────────────
1712
1713        # Check L1 first (in-process, no serialisation cost)
1714
341
232
        my $rc = $self->{'locations'}->{$key};
1715
1716        # Fall through to L2 only when L1 has no entry for this key
1717
341
513
        if((!defined($rc)) && $self->{'cache'}) {
1718
12
33
                if(ref($self->{'cache'}) eq 'HASH') {
1719
8
10
                        $rc = $self->{'cache'}->{$key};
1720                } else {
1721                        # A store that cannot be read is treated as a miss, so the lookup
1722                        # goes to the geocoders rather than failing
1723
4
5
                        local $@;
1724
4
4
3
7
                        $rc = eval { $self->{'cache'}->get($key) };
1725
4
17
                        $self->_warn(__PACKAGE__, ": cache get failed for '$key': $@") if($@);
1726                }
1727        }
1728
1729
341
665
        return unless defined $rc;
1730
1731        # Translate the not-found sentinel back to undef for the caller
1732
52
112
        return if ref($rc) && (ref($rc) eq _NOT_FOUND_CLASS);
1733
1734        # Restore the convenience aliases that were stripped before L2 storage
1735
43
47
        if(ref($rc) eq 'HASH') {
1736
29
33
                return unless defined($rc->{geometry}{location}{lat});
1737
27
42
                $rc->{'lat'} //= $rc->{geometry}{location}{lat};
1738
27
28
                $rc->{'lng'} //= $rc->{geometry}{location}{lng};
1739
27
25
                $rc->{'lon'} //= $rc->{geometry}{location}{lng};
1740        }
1741
1742
41
37
        return $rc;
1743}
1744
1745# A copy of one result fit for L2.  The geocoder object becomes its class name,
1746# since a handle it holds may not serialise, and unless $keep_all only geometry
1747# is kept, to keep the entry small.  Anything that is not a HASH-based
1748# reference is returned as it is.
1749sub _l2_copy {
1750
42
4580
        my ($item, $keep_all) = @_;
1751
1752
42
113
        return $item unless ref($item) && (reftype($item) eq 'HASH');
1753
1754
34
34
21
61
        my $copy = { %{$item} };
1755
34
65
        $copy = bless $copy, ref($item) if blessed($item);
1756
1757
34
52
        $copy->{'geocoder'} = ref($copy->{'geocoder'}) if ref($copy->{'geocoder'});
1758
1759        # A blessed object keeps its fields: its accessors may rely on them
1760
34
70
        unless($keep_all || blessed($item)) {
1761
28
28
78
28
24
22
49
23
                delete @{$copy}{grep { $_ ne 'geometry' } keys %{$copy}};
1762        }
1763
1764
34
38
        return $copy;
1765}
1766
1767# Whether a result carries a latitude, looked for without auto-vivifying the
1768# path to it in a structure that may be shared with the caller
1769sub _has_lat {
1770
36
5476
        my $item = shift;
1771
1772
36
55
        return 0 unless ref($item->{geometry}) eq 'HASH';
1773
28
46
        return 0 unless ref($item->{geometry}{location}) eq 'HASH';
1774
21
44
        return defined($item->{geometry}{location}{lat}) ? 1 : 0;
1775}
1776
1777# Emit a debug message somewhere
1778sub _debug {
1779
845
1423
        my $self = shift;
1780
1781        # Callers may end the message with a newline or not; it is printed with
1782        # exactly one, and given to the logger with none
1783
845
2324
509
2578
        my $message = join('', map { $_ // '' } @_);
1784
845
1152
        $message .= "\n" unless($message =~ /\n\z/);
1785
1786
845
637
        if(my $logger = $self->{logger}) {
1787
825
1109
                (my $line = $message) =~ s/\n\z//;
1788
825
775
                $logger->debug($line);
1789        }
1790
845
18366
        if($self->{debug}) {
1791
25
64
                print $message;
1792        }
1793}
1794
1795# Emit a warning message somewhere
1796sub _warn {
1797
34
126
        my $self = shift;
1798
1799
34
45
        if(my $logger = $self->{logger}) {
1800
28
43
                $logger->warn(@_);
1801        } else {
1802
6
101
                Carp::carp(@_);
1803        }
1804}
1805
1806# Emit an error message somewhere
1807sub _error {
1808
20
91
        my $self = shift;
1809
1810
20
24
        if(my $logger = $self->{logger}) {
1811
19
34
                $logger->error(@_);
1812
19
7136
                die @_;
1813        } else {
1814
1
10
                Carp::croak(@_);
1815        }
1816}
1817
1818# =============================================================================
1819# DOCUMENTATION
1820# =============================================================================
1821
1822 - 2288
=head1 COMMON PITFALLS

=over 4

=item * B<Scalar context and list context give different answers.>

C<geocode()> and C<reverse_geocode()> return one answer in scalar
context and all answers in list context.  Perl decides the context from
the code around the call, and some places give list context when you may
not expect it:

    my %row = (name => $name, place => $list->geocode($name));  # list context!
    print 'Found: ', $list->geocode($name);                     # list context!
    return $list->geocode($name);                               # your caller decides

When nothing is found, list context returns an I<empty list>, so in the
first example the keys and values of C<%row> move out of step.  Use
C<scalar()> to be sure:

    my %row = (name => $name, place => scalar($list->geocode($name)));

=item * B<C<undef> means "not found" and also "bad empty input".>

C<geocode()> returns C<undef> both when no geocoder knows the place and
when C<location> is C<undef> or empty (it also warns in that case).  Other
bad input, such as a reference or a string of digits, does not return
C<undef>: it croaks.  Wrap the call in C<eval> when the text comes from
outside your program.

=item * B<"Not found" is remembered for the life of the object.>

When no geocoder finds a place, L1 remembers that, and later calls for
the same place return C<undef> at once without asking any geocoder, even
if a geocoder was only down for a moment.  C<flush()> does not forget it,
and nor does a clone, which shares L1.  Make a new object with
C<Geo::Coder::List-E<gt>new()> to ask again.  L2 never stores "not
found".

=item * B<An answer from L2 has only its position.>

To keep L2 small, only C<geometry> is stored there (unless C<debug> is
set).  An answer that comes from L2, for example in a new run of your
program, has C<geometry>, C<lat>, C<lng>, C<lon> and C<geocoder> (set to
C<'cache'>), but none of the provider's other fields, such as an address
or a place type.  Answers from L1 keep every field.

=item * B<The C<geocoder> key is not always an object.>

It is the geocoder object when a geocoder answered, and the string
C<'cache'> when the cache answered.  Do not call methods on it without
checking.  To get a printable name in both cases:

    my $source = ref($result->{geocoder}) || $result->{geocoder};

=item * B<Changing an answer changes the cache.>

The first answer for a place is the same hash that L1 stores.  If you
change it, later answers for that place, which are copies of the L1 entry,
include your change.  Copy the answer before you change it.

=item * B<The hash you give to C<push()> is changed.>

C<limit> counts down inside the hash reference that you pushed.  If you
push the same hash reference into two lists, they share one limit.  A
clone made with C<< $list->new() >> has its own copy.

=item * B<The cache key is the exact text.>

C<'Paris, France'>, C<'paris, france'> and C<'Paris,France'> are three
different keys, and each one asks the geocoders again.  Only runs of
white space are made into one space before the text is used as a key.

=item * B<HTML entities are always decoded.>

C<'AT&amp;T Park'> is looked up as C<'AT&T Park'>.  There is no way to
look up text that really contains C<&amp;>.

=item * B<Strings of digits depend on an optional module.>

When L<Geo::Location::Predicate> is installed, C<geocode('12345')> croaks.
When it is not installed, C<'12345'> is sent to the geocoders.  If you
handle postcodes or ZIP codes, add the country, such as
C<'12345, USA'>.

=item * B<C<reverse_geocode()> ignores C<regex>.>

A geocoder added with C<regex> is still asked to reverse geocode every
position.  Only C<limit> is honoured.

=item * B<The log grows until you empty it.>

Every call adds to the log.  In a long-running program, such as a web
server, call C<flush()> from time to time.

=item * B<Debug output goes to standard output.>

With C<debug> set, messages are printed to C<STDOUT>, not C<STDERR>, so
they mix with your program's own output.  The same messages are always
given to the configured C<logger> at debug level, whether C<debug> is set
or not, so a logger is the way to collect them somewhere else.

=back

=head1 ENCODING

=over 4

=item * B<Place names> (C<location> in C<geocode()>) may contain any
Unicode text, including accented letters, other alphabets and emoji.
Give them to C<geocode()> as Perl I<character> strings: if your text is
UTF-8 bytes from a file or a web form, decode it first, for example with
C<Encode::decode('UTF-8', $bytes)>.  HTML entities in the text are decoded
into characters, so text that mixes UTF-8 bytes that are not yet decoded
with entities becomes garbled.  The text is passed to each geocoder unchanged apart from
that; whether a geocoder understands emoji is up to the geocoder.

=item * B<White space.>  Runs of ASCII spaces, tabs and new lines are
always made into one space.  Unicode spaces, such as the no-break space,
may or may not be, depending on how Perl stores the string; do not rely on
it.

=item * B<Positions> (C<latlng> in C<reverse_geocode()>) must be ASCII.
Only the digits C<0> to C<9> are accepted.  Other Unicode digits, such as
Eastern Arabic or full-width digits, are refused.

=item * B<Cache keys> are the cleaned text, so they can contain any
character.  L1 and a plain-hash L2 accept them all.  An L2 object must
accept character-string keys; L<CHI> does.

=item * B<Answers> are returned as the geocoder gave them.  Most
geocoders return character strings.

=item * B<Debug output> prints place names to C<STDOUT>.  If they contain
characters above 255, Perl warns C<Wide character in print> unless
C<STDOUT> has an encoding layer, such as
C<binmode(STDOUT, ':encoding(UTF-8)')>.

=back

=head1 AUTHOR

Nigel Horne, C<< <njh at nigelhorne.com> >>

=head1 BUGS

Please report any bugs or feature requests to
C<bug-geo-coder-list at rt.cpan.org>, or through the web interface at
L<https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Geo-Coder-List>.

Known limitations:

=over 4

=item * C<reverse_geocode()> does not yet support L<Geo::Location::Point> objects.

=item * C<reverse_geocode()> does not honour C<regex>.

=back

=head1 SEE ALSO

=over 4

=item * L<Test Dashboard|https://nigelhorne.github.io/Geo-Coder-List/coverage/>

=item * L<CHI>

=item * L<Geo::Coder::All>

=item * L<Geo::Coder::GooglePlaces>

=item * L<Geo::Coder::Many>

=item * L<Geo::Location::Predicate>

=item * L<HTML::GoogleMaps::V3>

=item * L<HTML::OSM>

=item * L<Configure an Object at Runtime|Object::Configure>

=item * L<Readonly>

=back

=head1 SUPPORT

You can find documentation for this module with the perldoc command:

    perldoc Geo::Coder::List

=over 4

=item * RT: CPAN's request tracker

L<https://rt.cpan.org/NoAuth/Bugs.html?Dist=Geo-Coder-List>

=item * MetaCPAN

L<https://metacpan.org/release/Geo-Coder-List>

=back

=encoding utf-8

=head1 FORMAL SPECIFICATION

The specifications below use the Z notation.  They describe what each
method does to the state of the object; they do not describe how.

=head2 State

    [Geocoder, Regex, Text, Result, LogEntry, Duration]

    Key         == Text
    NotFound    ::= notFound
    Entry       ::= plain⟨⟨Geocoder⟩⟩
                  | routed⟨⟨Geocoder × â„™ Text × (ℤ ∪ {∞})⟩⟩
                    -- the geocoder, the texts its regex matches, its limit

    â”Œâ”€ GeoCoderList ─────────────────────────────────────────────────
    â”‚ geocoders : seq Entry
    â”‚ L1        : Key ⇸ (Result ∪ NotFound)
    â”‚ L2        : Key ⇸ Result
    â”‚ log       : seq LogEntry
    â”‚ debug     : 0 ‥ 2
    â”‚ hit, part, miss : Duration
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ notFound ∉ ran L2
    â”‚ ∀ r : ran L2 • dom r ⊆ {geometry, geocoder}  âˆ¨  debug > 0
    â””────────────────────────────────────────────────────────────────

    usable : â„™ Result
    usable = { r : Result | r.geometry.location.lat ≠ ⊥
                          âˆ§ r.geometry.location.lng ≠ ⊥ }

    clean : Text → Text
    -- runs of white space become one space; HTML entities are decoded

    isPlace : (Text ∪ Reference) → 𝔹
    -- Geo::Location::Predicate::_valid_location when installed;
    -- otherwise true for every non-empty Text and false for a Reference

=head2 new

    â”Œâ”€ New ──────────────────────────────────────────────────────────
    â”‚ GeoCoderList′
    â”‚ args? : Key ⇸ Value
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ geocoders′ = args?(geocoders) if geocoders ∈ dom args?, else ⟨⟩
    â”‚ L1′  = ∅
    â”‚ log′ = ⟨⟩
    â”‚ L2′  = args?(cache) if cache ∈ dom args?, else ∅
    â”‚ hit′ = 1 month ∧ part′ = 1 day ∧ miss′ = 1 week   -- unless given
    â””────────────────────────────────────────────────────────────────

    â”Œâ”€ Clone ────────────────────────────────────────────────────────
    â”‚ GeoCoderList ; GeoCoderList′
    â”‚ args? : Key ⇸ Value
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ geocoders′ = geocoders            -- equal values, distinct copies
    â”‚ L1′ = L1 ∧ L2′ = L2               -- the same stores, shared
    â”‚ log′ = ⟨⟩
    â””────────────────────────────────────────────────────────────────

=head2 push

    â”Œâ”€ Push ─────────────────────────────────────────────────────────
    â”‚ ΔGeoCoderList
    â”‚ g? : Entry
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ g? ≠ ⊥
    â”‚ geocoders′ = geocoders ⁀ ⟨g?⟩
    â”‚ L1′ = L1 ∧ L2′ = L2 ∧ log′ = log
    â””────────────────────────────────────────────────────────────────

    PushUndef ≙ [ ΞGeoCoderList ; g? : Entry | g? = ⊥ ] ⇒ croak

=head2 geocode

    asks : Entry × Text ⇸ 𝔹
    asks(plain g, t)          = true
    asks(routed(g, M, n), t)  = t ∈ M ∧ n > 0

    spend : Entry × Text → Entry
    spend(routed(g, M, n), t) = routed(g, M, n − 1)   if t ∈ M ∧ n > 0
    spend(e, t)               = e                      otherwise

    â”Œâ”€ Geocode ──────────────────────────────────────────────────────
    â”‚ ΔGeoCoderList
    â”‚ loc?    : Text ∪ Reference
    â”‚ result! : Result ∪ {⊥}
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ let t == clean(loc?) •
    â”‚   Â¬ isPlace(t) ⇒ croak
    â”‚
    â”‚   t ∈ dom L1 ∧ L1(t) ∈ Result
    â”‚     â‡’ result! = L1(t) ⊕ {geocoder ↦ cache}
    â”‚        âˆ§ geocoders′ = geocoders ∧ L1′ = L1
    â”‚
    â”‚   t ∈ dom L1 ∧ L1(t) = notFound
    â”‚     â‡’ result! = ⊥ ∧ geocoders′ = geocoders ∧ L1′ = L1
    â”‚
    â”‚   t ∉ dom L1
    â”‚     â‡’ (∃ i : dom geocoders •
    â”‚            asks(geocoders i, t)
    â”‚          âˆ§ (∀ j : 1 ‥ i − 1 • ¬ asks(geocoders j, t)
    â”‚                              âˆ¨ answer(geocoders j, t) ∉ usable)
    â”‚          âˆ§ answer(geocoders i, t) ∈ usable
    â”‚          âˆ§ result! = answer(geocoders i, t)
    â”‚          âˆ§ L1′ = L1 ⊕ {t ↦ result!}
    â”‚          âˆ§ L2′ = L2 ⊕ {t ↦ result! ↾ {geometry, geocoder}})
    â”‚      âˆ¨ (result! = ⊥ ∧ L1′ = L1 ⊕ {t ↦ notFound} ∧ L2′ = L2)
    â”‚
    â”‚   geocoders′ = ⟨ e : geocoders • spend(e, t) ⟩   -- for those asked
    â”‚   log′ = log ⁀ ⟨ one entry per geocoder asked, or one for the cache ⟩
    â””────────────────────────────────────────────────────────────────

=head2 ua

    â”Œâ”€ UA ───────────────────────────────────────────────────────────
    â”‚ ΞGeoCoderList
    â”‚ ua? : UserAgent ∪ {⊥}
    â”‚ ua! : UserAgent ∪ {⊥}
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ ua? = ⊥ ⇒ ua! = ⊥
    â”‚ ua? ≠ ⊥ ⇒ ua! = ua?
    â”‚         âˆ§ (∀ e : ran geocoders • geocoderOf(e) ≠ ⊥ ∨ croak)
    â”‚         âˆ§ (∀ e : ran geocoders •
    â”‚              geocoderOf(e).ua′ = clone(ua?)   if ua? can clone and agent
    â”‚              geocoderOf(e).ua′ = ua?          otherwise)
    â””────────────────────────────────────────────────────────────────

=head2 reverse_geocode

    LatLng == { s : Text | s ∈ ⟦ ^W*[-+]?D W*,W*[-+]?D W*$ ⟧ }
    -- D is a decimal number written in the ASCII digits 0-9;
    -- W is a space or a tab

    â”Œâ”€ ReverseGeocode ───────────────────────────────────────────────
    â”‚ ΔGeoCoderList
    â”‚ latlng?  : Text
    â”‚ address! : Text ∪ {⊥}
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ latlng? ∉ LatLng ⇒ croak
    â”‚
    â”‚ latlng? ∈ dom L1 ⇒ address! = first(L1(latlng?)) ∧ L1′ = L1
    â”‚
    â”‚ latlng? ∉ dom L1
    â”‚   â‡’ (∃ i : dom geocoders •
    â”‚          limitOf(geocoders i) > 0
    â”‚        âˆ§ addresses(geocoders i, latlng?) ≠ ⟨⟩
    â”‚        âˆ§ address! = head addresses(geocoders i, latlng?)
    â”‚        âˆ§ L1′ = L1 ⊕ {latlng? ↦ address!})
    â”‚    âˆ¨ (address! = ⊥ ∧ L1′ = L1)
    â””────────────────────────────────────────────────────────────────

=head2 log

    â”Œâ”€ Log ──────────────────────────────────────────────────────────
    â”‚ ΞGeoCoderList
    â”‚ entries! : seq LogEntry
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ entries! = log
    â””────────────────────────────────────────────────────────────────

=head2 flush

    â”Œâ”€ Flush ────────────────────────────────────────────────────────
    â”‚ ΔGeoCoderList
    â”‚ self! : GeoCoderList
    â”œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
    â”‚ log′ = ⟨⟩
    â”‚ geocoders′ = geocoders ∧ L1′ = L1 ∧ L2′ = L2
    â”‚ self! = θGeoCoderList′
    â””────────────────────────────────────────────────────────────────

=head1 STATE DIAGRAM

An object has three parts that change over time: its chain of geocoders,
its cache (one state for each place name), and its log.  Each part is
shown on its own below.

=head2 The object and its chain

    +-------------+  push($g)        +--------------+  push($g)
    |   EMPTY     |----------------->|    READY     |<-----------+
    | no geocoder |  adds $g to the  | 1+ geocoders |------------+
    +-------------+  end of chain    +--------------+  adds $g to the end
          |                                 |
          | geocode($t)                     | new() on the object
          | reverse_geocode($p)             | clone: copies the chain and
          v                                 | limits, shares L1 and L2,
    returns undef / ()                      | empties the log
    nothing is asked, but a                 v
    geocode() miss is still          +--------------+
    remembered in L1                 | READY (clone)|
                                     +--------------+

=head2 The cache, for one place name $t

    +-----------+ geocode($t): a geocoder gives a usable answer
    |           |----------------------------------------------+
    |  UNKNOWN  |  - answer stored in L1                       |
    | not in L1 |  - copy with only geometry stored in L2,     |
    |           |    kept for cache_hit_duration (1 month)     |
    +-----------+  - log gets one entry per geocoder asked     |
       |  ^                                                    v
       |  |                                             +-----------+
       |  | L2 entry expires, and a new object          |  FOUND    |<--+
       |  | is used (L1 dies with its object)           |  L1 holds |   | geocode($t):
       |  +---------------------------------------------| an answer |---+ no geocoder
       |                                                +-----------+   asked; a copy
       | geocode($t): no geocoder gives a usable answer                 returned with
       |  - L1 stores "not found"; L2 is not written                    geocoder='cache';
       |  - log gets one entry per geocoder asked                       log gets one
       v                                                                cache entry
    +-----------+
    | NOT FOUND |<--+ geocode($t): nothing asked, undef / () returned,
    | in L1     |---+ nothing logged
    +-----------+
          |
          +-- leaves this state only when the object is destroyed;
              flush() and clones do not leave it

    Any state: geocode($t) with a reference, digits (when
    Geo::Location::Predicate is installed) or entity-encoded digits
    --> croaks; the state does not change.

=head2 The limit of one geocoder pushed with limit => N

    +--------------+ geocode($t), $t matches regex   +--------------+
    |  ACTIVE      |-------------------------------->|  ACTIVE      |
    |  limit = n   |  geocoder asked; limit = n - 1  |  limit = n-1 |
    +--------------+                                 +--------------+
       |      ^                                             |
       |      | geocode($t), $t does not match regex:       | ... when the
       |      | geocoder not asked, limit unchanged         | limit reaches 0
       +------+                                             v
                                                     +--------------+
                                                     |  EXHAUSTED   |<--+
                                                     |  limit <= 0  |---+
                                                     +--------------+ geocode() and
                                                                      reverse_geocode():
                                                                      skipped, never asked

=head2 The log

    +-----------+  geocode() or reverse_geocode()   +-------------+
    |  EMPTY    |---------------------------------->|  HAS        |<--+
    |  log = [] |  appends one entry per geocoder   |  ENTRIES    |---+ geocode() /
    +-----------+  asked, or one for a cache hit    +-------------+   reverse_geocode():
         ^                                                 |          appends entries
         |               flush(): log = [], returns $self  |
         +-------------------------------------------------+
         ^
         | new() and clone: start EMPTY

=head1 LICENSE AND COPYRIGHT

Copyright 2016-2026 Nigel Horne.

Usage is subject to the GPL2 licence terms.
If you use it,
please let me know.

=cut
2289
22901;