File Coverage

File:blib/lib/Geo/Coder/Free/OpenAddresses.pm
Coverage:16.6%

linestmtbrancondsubpodtimecode
1package Geo::Coder::Free::OpenAddresses;
2
3# Includes both openaddresses and Whos On First data
4
5
15
15
15
30
13
177
use strict;
6
15
15
15
25
10
364
use warnings;
7
15
15
15
27
13
68
use autodie qw(:all);
8
9
15
15
15
32823
17
444
use Geo::Coder::Free::Utils qw(_abbreviate _normalize);
10
15
15
15
2082
23
91
use Geo::Coder::Free::DB::OpenAddr;     # SQLite database
11
15
15
15
111104
23
70
use Geo::Coder::Free::DB::openaddresses;        # The original CSV files
12
15
15
15
104914
6850
242
use Geo::Hash;
13
15
15
15
41
13
171
use Geo::Location::Point;
14
15
15
15
27
17
133
use Module::Info;
15
15
15
15
21
15
359
use Carp;
16
15
15
15
28
14
138
use File::Spec;
17
15
15
15
2900
3206
311
use File::pfopen;
18
15
15
15
32
15
165
use Locale::CA;
19
15
15
15
30
14
130
use Locale::US;
20
15
15
15
31
16
131
use Locale::SubCountry;
21
15
15
15
32
15
148
use CHI;
22
15
15
15
24
36
328
use Lingua::EN::AddressParse;
23
15
15
15
25
33
786
use Locale::Country;
24
15
15
15
51
11
308
use Geo::StreetAddress::US;
25
15
15
15
63
46
220
use Digest::MD5;
26
15
15
15
42
9
532
use Encode;
27
15
15
15
25
9
236
use Params::Get;
28
15
15
15
27
9
672
use Storable;
29
30# Some locations aren't found because of inconsistencies in the way things are stored - these are some values I know
31# FIXME: Should be in a configuration file
32our %known_locations = (
33        'Newport Pagnell, Buckinghamshire, England' => {
34                'latitude' => 52.08675,
35                'longitude' => -0.72270
36        },
37);
38
39our %unknown_locations;
40
41# Lazily-initialised Locale singletons.  Locale::US->new() and
42# Locale::CA->new() were being called up to five times per geocode()
43# invocation for US/Canadian addresses.  These module-level variables
44# persist across all object instances and eliminate repeated construction.
45my $_locale_us;
46my $_locale_ca;
47
48
15
15
15
26
13
614
use constant    LIBPOSTAL_UNKNOWN => 0;
49
15
15
15
21
11
268
use constant    LIBPOSTAL_INSTALLED => 1;
50
15
15
15
20
12
62467
use constant    LIBPOSTAL_NOT_INSTALLED => -1;
51our $libpostal_is_installed = LIBPOSTAL_UNKNOWN;
52
53 - 62
=head1 NAME

Geo::Coder::Free::OpenAddresses -
Provides a geocoding functionality to a local SQLite database containing geo-coding data.

=head1 VERSION

Version 0.42

=cut
63
64our $VERSION = '0.43';
65
66 - 120
=head1 SYNOPSIS

    use Geo::Coder::Free::OpenAddresses;

    # Use a local download of http://results.openaddresses.io/
    my $geocoder;
    if(my $openaddr = $ENV{'OPENADDR_HOME'}) {
        $geocoder = Geo::Coder::Free::OpenAddresses->new(openaddr => $openaddr);
    } else {
        $geocoder = Geo::Coder::Free::OpenAddresses->new(openaddr => '/usr/share/geo-coder-free/data');
    }
    my $location = $geocoder->geocode(location => '1600 Pennsylvania Avenue NW, Washington DC, USA');

    my @matches = $geocoder->geocode({ scantext => 'arbitrary text', region => 'GB' });

=head1 DESCRIPTION

Geo::Coder::Free::OpenAddresses provides an interface to the free geolocation databases at
L<http://results.openaddresses.io>,
L<https://github.com/whosonfirst-data>,
L<https://github.com/dr5hn/countries-states-cities-database.git> and
L<https://download.geofabrik.de/europe-latest.osm.bz2>.
The SQLite database is in a file held in $OPENADDR_HOME/openaddresses.sql.

Refer to the source URL for licencing information for these files.

To install,
run the createdatabases.PL script which imports the data into an SQLite database.
This process will take some time.

=head1 METHODS

=head2 new

    $geocoder = Geo::Coder::Free::OpenAddresses->new(openaddr => $ENV{'OPENADDR_HOME'});

Takes an optional parameter "openaddr", which is the directory of the file
openaddresses.sql.

Takes an optional parameter cache, which points to an object that understands get() and set() messages to store data in

=head3 API SPECIFICATION

=head4 input

    # Input schema (Params::Validate::Strict)
    openaddr => { type => 'scalar' }                                          # path to the directory containing openaddresses.sql
    cache    => { type => 'object', optional => 1, can => ['get', 'set'] }   # CHI-compatible cache object

=head4 output

    # Output schema (Return::Set)
    { type => 'object', isa => 'Geo::Coder::Free::OpenAddresses' }

=cut
121
122sub new {
123
30
1
19238
        my($proto, %param) = @_;
124
30
82
        my $class = ref($proto) || $proto;
125
126        # Geo::Coder::Free->new not Geo::Coder::Free::new
127
30
32
        return unless($class);
128
129
30
48
        if(my $openaddr = $param{'openaddr'}) {
130
28
355
                Carp::croak(__PACKAGE__, ": Can't find the directory $openaddr")
131                        if((!-d $openaddr) || (!-r $openaddr));
132
23
72
                return bless { openaddr => $openaddr, cache => $param{'cache'} }, $class;
133        }
134
2
9
        Carp::croak(__PACKAGE__, ": Usage: new(openaddr => '/path/to/openaddresses')");
135}
136
137 - 178
=head2 geocode

    $location = $geocoder->geocode(location => $location);

    print 'Latitude: ', $location->lat(), "\n";
    print 'Longitude: ', $location->long(), "\n";

    # TODO:
    # @locations = $geocoder->geocode('Portland, USA');
    # diag 'There are Portlands in ', join (', ', map { $_->{'state'} } @locations);

    @locations = $geo_coder->geocode(scantext => 'arbitrary text', region => 'US', ignore_words => [ 'foo', 'bar' ]);

When looking for a house number in a street, if that address isn't found but that
street is found, a place in the street is given.
So "106 Wells Street, Fort Wayne, Allen, Indiana, USA" isn't found, a match for
"Wells Street, Fort Wayne, Allen, Indiana, USA" will be given instead.
Arguably that's incorrect, but it is the behaviour I want.
If "exact" is not given,
it will go on to look just for the town if the street isn't found.

The word "county" is removed from US county searches,
that either C<Leesburg, Loudoun County, Virginia, US> or C<Leesburg, Loudoun, Virginia, US> will work.

=head3 API SPECIFICATION

=head4 input

    # Input schema (Params::Validate::Strict) — exactly one of location or scantext is required
    location     => { type => 'scalar',   optional => 1 }  # address string (exclusive with scantext)
    scantext     => { type => 'scalar',   optional => 1 }  # free text to scan for place names
    region       => { type => 'scalar',   optional => 1 }  # ISO 3166-1 alpha-2 country code hint
    ignore_words => { type => 'arrayref', optional => 1 }  # words to suppress during scantext scan
    exact        => { type => 'scalar',   optional => 1 }  # if set, do not fall back to town-only match

=head4 output

    # Output schema (Return::Set)
    # scalar context: { type => 'object',   isa => 'Geo::Location::Point', optional => 1 }
    # list context:   { type => 'arrayref', of  => { isa => 'Geo::Location::Point' } }

=cut
179
180sub geocode
181{
182
13
1
783
        my $self = shift;
183
13
43
        my $param = (@_ == 1 && !ref($_[0]))
184                ? { location => $_[0] }
185                : Params::Get::get_params(undef, @_);
186
187
13
168
        my %ignore_words;
188
13
21
        if($param->{'ignore_words'}) {
189
0
0
0
0
0
0
                %ignore_words = map { lc($_) => 1 } @{$param->{'ignore_words'}};
190        }
191
192
13
20
        if(my $scantext = $param->{'scantext'}) {
193
4
13
                return if(length($scantext) < 6);
194                # FIXME:  wow this is inefficient
195
0
0
                $scantext =~ s/[^\w']+/ /g;
196
0
0
                my @words = split(/\s/, $scantext);
197
0
0
                my $count = scalar(@words);
198
0
0
                my $offset = 0;
199
0
0
                my @rc;
200
0
0
                my $region = $param->{'region'};
201
0
0
                if($region) {
202
0
0
                        $region = uc($region);
203                }
204
0
0
                while($offset < $count) {
205
0
0
                        if(length($words[$offset]) < 2) {
206
0
0
                                $offset++;
207
0
0
                                next;
208                        }
209
0
0
                        if(exists($ignore_words{lc($words[$offset])})) {
210
0
0
                                $offset++;
211
0
0
                                next;
212                        }
213
0
0
                        my $l;
214
0
0
                        if(($l = $self->geocode(location => $words[$offset])) && ref($l)) {
215
0
0
                                push @rc, $l;
216                        }
217
0
0
                        if($offset < $count - 1) {
218
0
0
                                my $addr = join(', ', $words[$offset], $words[$offset + 1]);
219
0
0
                                if(length($addr) == 0) {
220
0
0
                                        $offset++;
221                                }
222                                # https://stackoverflow.com/questions/11160192/how-to-parse-freeform-street-postal-address-out-of-text-and-into-components
223                                # TODO: Support longer addresses
224
0
0
                                if($addr =~ /\s+(?:\d{2,5}\s+)(?![ap]m\b)(?:(?:[a-zA-Z\s]{1,5}){1,2})?(?:[\s,.]+)?(?:(?:[a-zA-Z\s]{1,30}){1,4})(?:court|ct|street|st|drive|dr|lane|ln|road|rd|blvd)(?:[\s,.;]+)?(?:(?:[a-zA-Z\s]{1,30}){1,2})(?:[\s,.]+)?\b(?:AK|AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|GU|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VI|VT|WA|WI|WV|WY)(?:[\s,.]+)?(?:\s+\d{5})?(?:[\s,.]+)/i) {
225
0
0
                                        unless($region && ($region ne 'US')) {
226
0
0
                                                if(($l = $self->geocode(location => "$addr, US")) && ref($l)) {
227
0
0
                                                        $l->confidence(0.8);
228
0
0
                                                        $l->country('US');
229
0
0
                                                        $l->location("$addr, USA");
230
0
0
                                                        push @rc, $l;
231                                                }
232                                        }
233                                } elsif($addr =~ /\s+(?:\d{2,5}\s+)(?![ap]m\b)(?:(?:[a-zA-Z\s]{1,5}){1,2})?(?:[\s,.]+)?(?:(?:[a-zA-Z\s]{1,30}){1,4})(?:court|ct|street|st|drive|dr|lane|ln|road|rd|blvd)(?:[\s,.;]+)?(?:(?:[a-zA-Z\s]{1,30}){1,2})(?:[\s,.]+)?\b(?:AB|BC|MB|NB|NL|NT|NS|ON|PE|QC|SK|YT)(?:[\s,.]+)?(?:\s+\d{5})?(?:[\s,.]+)/i) {
234
0
0
                                        unless($region && ($region ne 'CA')) {
235
0
0
                                                if(($l = $self->geocode(location => "$addr, Canada")) && ref($l)) {
236
0
0
                                                        $l->confidence(0.8);
237
0
0
                                                        $l->country('CA');
238
0
0
                                                        $l->location("$addr, Canada");
239
0
0
                                                        push @rc, $l;
240                                                }
241                                        }
242                                } elsif($addr =~ /([A-Za-z]+(?:\s+[A-Za-z]+)*)([\s,.]+)?\b(AK|AL|AR|AZ|CA|CO|CT|DC|DE|FL|GA|GU|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VI|VT|WA|WI|WV|WY)/i) {
243
0
0
                                        unless($region && ($region ne 'US')) {
244
0
0
                                                if(($l = $self->geocode(location => "$addr, US")) && ref($l)) {
245
0
0
                                                        $l->confidence(0.6);
246
0
0
                                                        $l->city(uc($1));
247
0
0
                                                        $l->state(uc($3));
248
0
0
                                                        $l->country('US');
249
0
0
                                                        $l->location(uc("$addr, USA"));
250
0
0
                                                        push @rc, $l;
251                                                }
252                                        }
253                                } elsif($addr =~ /([A-Za-z]+(?:\s+[A-Za-z]+)*)([\s,.]+)?\b(AB|BC|MB|NB|NL|NT|NS|ON|PE|QC|SK|YT)/i) {
254
0
0
                                        unless($region && ($region ne 'CA')) {
255
0
0
                                                if(($l = $self->geocode(location => "$addr, Canada")) && ref($l)) {
256
0
0
                                                        $l->confidence(0.6);
257
0
0
                                                        $l->city(uc($1));
258
0
0
                                                        $l->state(uc($3));
259
0
0
                                                        $l->country('Canada');
260
0
0
                                                        $l->location(uc("$addr, Canada"));
261
0
0
                                                        push @rc, $l;
262                                                }
263                                        }
264                                }
265
0
0
                                if($region && (($l = $self->geocode(location => "$addr, $region")) && ref($l))) {
266
0
0
                                        $l->confidence(0.2);
267
0
0
                                        $l->location("$addr, $region");
268
0
0
                                        push @rc, $l;
269                                } elsif((!$region) && (($l = $self->geocode(location => $addr)) && ref($l))) {
270
0
0
                                        $l->confidence(0.1);
271
0
0
                                        $l->location($addr);
272
0
0
                                        push @rc, $l;
273                                }
274
0
0
                                if($offset < $count - 2) {
275
0
0
                                        $addr = join(', ', $words[$offset], $words[$offset + 1], $words[$offset + 2]);
276
0
0
                                        if(($l = $self->geocode(location => $addr)) && ref($l)) {
277
0
0
                                                $l->confidence(1.0);
278
0
0
                                                $l->location($addr);
279
0
0
                                                push @rc, $l;
280                                        }
281                                }
282                        }
283
0
0
                        $offset++;
284                }
285
0
0
                return @rc;
286                # my @locations;
287
288                # foreach my $l(@rc) {
289                        # push @locations, Location::GeoTool->create_coord($l->{'latitude'}, $l->{'longitude'}, $l->{'location'}, 'Degree');
290                # }
291
292                # return @locations;
293        }
294
295        my $location = $param->{location}
296
9
20
                or Carp::croak('Usage: geocode(location => $location|scantext => $text)');
297
298
299
8
20
        $location =~ s/,\s+,\s+/, /g;
300
301
8
13
        if($location =~ /^,\s*(.+)/) {
302
0
0
                $location = $1;
303        }
304
305        # Fail when the input is just a set of numbers
306
8
18
        if($location !~ /\D/) {
307                # Carp::croak('Usage: ', __PACKAGE__, ": invalid input to geocode(), $location");
308
2
3
                return;
309        }
310
6
10
        return if(length($location) <= 1);
311
312
6
156
        if($location =~ /^(.+),?\s*Washington\s*DC$/i) {
313
0
0
                $location = "$1, Washington, DC, USA";
314        } elsif($location =~ /^(.*),?\s*Saint Louis, (Missouri|MO)(.*)$/) {
315                # createdatabase.PL also maps this
316
0
0
                $location = "$1, St. Louis, MO$3";
317        }
318
319
6
13
        if(my $rc = $known_locations{$location}) {
320                # return $known_locations{$location};
321                return Geo::Location::Point->new({
322                        'lat' => $rc->{'latitude'},
323                        'long' => $rc->{'longitude'},
324
5
36
                        'lng' => $rc->{'longitude'},
325                        'location' => $location,
326                        'database' => 'OpenAddresses'
327                });
328        }
329
330
1
4
        $self->{'location'} = $location;
331
332
1
41
        my $county;
333        my $state;
334
1
0
        my $country;
335
1
0
        my $street;
336
337
1
2
        $location =~ s/\.//g;
338
339
1
3
        if($location !~ /,/) {
340
0
0
                if($location =~ /^(.+?)\s+(United States|USA|US)$/i) {
341
0
0
                        my $l = $1;
342
0
0
                        if(my $rc = $self->_get($l, 'US')) {
343
0
0
                                $rc->{'country'} = 'US';
344
0
0
                                return $rc;
345                        }
346
0
0
                        $l =~ s/\s+//g;
347
0
0
                        if(my $rc = $self->_get($l, 'US')) {
348
0
0
                                $rc->{'country'} = 'US';
349
0
0
                                return $rc;
350                        }
351                } elsif($location =~ /^(.+?)\s+(England|Scotland|Wales|Northern Ireland|UK|GB)$/i) {
352
0
0
                        my $l = $1;
353
0
0
                        $l =~ s/\s+//g;
354
0
0
                        if(my $rc = $self->_get($l, 'GB')) {
355
0
0
                                $rc->{'country'} = 'GB';
356
0
0
                                return $rc;
357                        }
358                } elsif($location =~ /^(.+?)\s+Canada$/i) {
359
0
0
                        my $l = $1;
360
0
0
                        $l =~ s/\s+//g;
361
0
0
                        if(my $rc = $self->_get($l, 'CA')) {
362
0
0
                                $rc->{'country'} = 'CA';
363
0
0
                                return $rc;
364                        }
365                }
366        }
367
1
2
        my $ap;
368
1
11
        if(($location =~ /USA$/) || ($location =~ /United States$/)) {
369
0
0
                $ap = $self->{'ap'}->{'us'} // Lingua::EN::AddressParse->new(country => 'US', auto_clean => 1, force_case => 1, force_post_code => 0);
370
0
0
                $self->{'ap'}->{'us'} = $ap;
371        } elsif($location =~ /(England|Scotland|Wales|Northern Ireland|UK|GB)$/i) {
372
0
0
                $ap = $self->{'ap'}->{'gb'} // Lingua::EN::AddressParse->new(country => 'GB', auto_clean => 1, force_case => 1, force_post_code => 0);
373
0
0
                $self->{'ap'}->{'gb'} = $ap;
374        } elsif($location =~ /Canada$/) {
375
0
0
                $ap = $self->{'ap'}->{'ca'} // Lingua::EN::AddressParse->new(country => 'CA', auto_clean => 1, force_case => 1, force_post_code => 0);
376
0
0
                $self->{'ap'}->{'ca'} = $ap;
377        } elsif($location =~ /Australia$/) {
378
0
0
                $ap = $self->{'ap'}->{'au'} // Lingua::EN::AddressParse->new(country => 'AU', auto_clean => 1, force_case => 1, force_post_code => 0);
379
0
0
                $self->{'ap'}->{'au'} = $ap;
380        }
381
1
2
        if($ap) {
382
0
0
                my $l = $location;
383
0
0
                if($l =~ /(.+), (England|UK)$/i) {
384
0
0
                        $l = "$1, GB";
385                }
386
0
0
                if($ap->parse($l)) {
387                        # Carp::croak($ap->report());
388                } else {
389
0
0
                        my %c = $ap->components();
390
0
0
                        my %addr = ( 'location' => $l );
391
0
0
                        $street = $c{'street_name'};
392
0
0
                        if(my $type = $c{'street_type'}) {
393
0
0
                                if(my $a = _abbreviate($type)) {
394
0
0
                                        $street .= " $a";
395                                } else {
396
0
0
                                        $street .= " $type";
397                                }
398
0
0
                                if(my $suffix = $c{'street_direction_suffix'}) {
399
0
0
                                        $street .= " $suffix";
400                                }
401
0
0
                                $street =~ s/^0+//;     # Turn 04th St into 4th St
402
0
0
                                $addr{'road'} = $street;
403                        }
404
0
0
                        if(length($c{'subcountry'}) == 2) {
405
0
0
                                $addr{'state'} = $c{'subcountry'};
406                        } else {
407
0
0
                                if($c{'country'} =~ /Canada/i) {
408
0
0
                                        $addr{'country'} = 'CA';
409
0
0
                                        if(my $twoletterstate = ($_locale_ca //= Locale::CA->new())->{province2code}{uc($c{'subcountry'})}) {
410
0
0
                                                $addr{'state'} = $twoletterstate;
411                                        }
412                                } elsif($c{'country'} =~ /^(United States|USA|US)$/i) {
413
0
0
                                        $addr{'country'} = 'US';
414
0
0
                                        if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($c{'subcountry'})}) {
415
0
0
                                                $addr{'state'} = $twoletterstate;
416                                        }
417                                } elsif($c{'country'}) {
418
0
0
                                        $addr{'country'} = $c{'country'};
419
0
0
                                        if($c{'subcountry'}) {
420
0
0
                                                $addr{'state'} = $c{'subcountry'};
421                                        }
422                                }
423                        }
424
0
0
                        $addr{'house_number'} = $c{'property_identifier'};
425
0
0
                        $addr{'city'} = $c{'suburb'};
426
0
0
                        if($addr{'house_number'}) {
427
0
0
                                if(my $rc = $self->_search(\%addr, ('house_number', 'road', 'city', 'state', 'country'))) {
428
0
0
                                        return $rc;
429                                }
430                        }
431
0
0
                        if((!$addr{'house_number'}) || !$param->{'exact'}) {
432
0
0
                                if(my $rc = $self->_search(\%addr, ('road', 'city', 'state', 'country'))) {
433
0
0
                                        return $rc;
434                                }
435                        }
436                }
437        }
438
439
1
51
        if($location =~ /^(.+?)[,\s]+(United States|USA|US)$/i) {
440                # Try Geo::StreetAddress::US, which is rather buggy
441
442
0
0
                my $l = $1;
443
0
0
                $l =~ s/,/ /g;
444
0
0
                $l =~ s/\s\s+/ /g;
445
446                # Work around for RT#122617
447
0
0
                if(($location !~ /\sCounty,/i) && (my $href = (Geo::StreetAddress::US->parse_location($l) || Geo::StreetAddress::US->parse_address($l)))) {
448
0
0
                        if($state = $href->{'state'}) {
449
0
0
                                if(length($state) > 2) {
450
0
0
                                        if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
451
0
0
                                                $state = $twoletterstate;
452                                        }
453                                }
454
0
0
                                my $city;
455
0
0
                                if($href->{city}) {
456
0
0
                                        $city = uc($href->{city});
457                                }
458
0
0
                                if($street = $href->{street}) {
459
0
0
                                        if($href->{'type'} && (my $type = _abbreviate($href->{'type'}))) {
460
0
0
                                                $street .= " $type";
461                                        }
462
0
0
                                        if($href->{suffix}) {
463
0
0
                                                $street .= ' ' . $href->{suffix};
464                                        }
465
0
0
                                        if(my $prefix = $href->{prefix}) {
466
0
0
                                                $street = "$prefix $street";
467                                        }
468
0
0
                                        if($href->{'number'}) {
469
0
0
                                                if(my $rc = $self->_get($href->{'number'}, "$street$city$state", 'US')) {
470
0
0
                                                        $rc->{'country'} = 'US';
471
0
0
                                                        return $rc;
472                                                }
473                                        }
474
0
0
                                        if(my $rc = $self->_get("$street$city$state", 'US')) {
475
0
0
                                                $rc->{'country'} = 'US';
476
0
0
                                                return $rc;
477                                        }
478                                }
479                        }
480                }
481
482                # Hack to find "name, street, town, state, US"
483
0
0
                my @addr = split(/,\s*/, $location);
484
0
0
                if(scalar(@addr) == 5) {
485
0
0
                        $state = $addr[3];
486
0
0
                        if(length($state) > 2) {
487
0
0
                                if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
488
0
0
                                        $state = $twoletterstate;
489                                }
490                        }
491
0
0
                        if(length($state) == 2) {
492
0
0
                                $addr[1] = _normalize($addr[1]);
493
0
0
                                if(my $rc = $self->_get($addr[0], $addr[1], $addr[2], $state, 'US')) {
494
0
0
                                        $rc->{'country'} = 'US';
495
0
0
                                        return $rc;
496                                }
497                        }
498                        # Hack to find "street, town, county, state, US"
499
0
0
                        if(length($state) == 2) {
500
0
0
                                $addr[0] = _normalize($addr[0]);
501
0
0
                                $addr[2] =~ s/\s+COUNTY$//i;
502
0
0
                                if(my $rc = $self->_get($addr[0], $addr[1], $addr[2], $state, 'US')) {
503
0
0
                                        $rc->{'country'} = 'US';
504
0
0
                                        return $rc;
505                                }
506
0
0
                                if(my $rc = $self->_get($addr[0], $addr[1], $state, 'US')) {
507
0
0
                                        $rc->{'country'} = 'US';
508
0
0
                                        return $rc;
509                                }
510                        }
511                }
512        }
513
514
1
5
        if($location =~ /(.+),\s*([\s\w]+),\s*([\w\s]+)$/) {
515
1
2
                my $city = $1;
516
1
2
                $state = $2;
517
1
2
                $country = $3;
518
1
2
                $state =~ s/\s$//g;
519
1
1
                $country =~ s/\s$//g;
520
521
1
1
                my $c;
522
523
1
6
                if((uc($country) eq 'ENGLAND') ||
524                   (uc($country) eq 'SCOTLAND') ||
525                   (uc($country) eq 'WALES')) {
526
0
0
                        $country = 'Great Britain';
527
0
0
                        $c = 'gb';
528                } else {
529
1
4
                        $c = country2code($country);
530                }
531
1
33
                if($c) {
532
0
0
                        if($c eq 'us') {
533
0
0
                                if(length($state) > 2) {
534
0
0
                                        if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
535
0
0
                                                $state = $twoletterstate;
536                                        }
537                                }
538
0
0
                                my $rc;
539
540
0
0
                                if($city !~ /,/) {
541
0
0
                                        $city = uc($city);
542
0
0
                                        if($city =~ /^(.+)\sCOUNTY$/) {
543                                                # Simple case looking up a county in a state in the US
544
0
0
                                                if($rc = $self->_get("$1$state", 'US')) {
545
0
0
                                                        $rc->{'country'} = 'US';
546
0
0
                                                        return $rc;
547                                                }
548                                        } else {
549                                                # Simple case looking up a city in a state in the US
550
0
0
                                                if($rc = $self->_get("$city$state", 'US')) {
551
0
0
                                                        $rc->{'country'} = 'US';
552
0
0
                                                        return $rc;
553                                                }
554                                        }
555                                } elsif(my $href = Geo::StreetAddress::US->parse_address("$city, $state")) {
556                                # warn __LINE__;
557                                        # Well formed, simple street address in the US
558
0
0
                                        $state = $href->{'state'};
559
0
0
                                        if(length($state) > 2) {
560
0
0
                                                if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
561
0
0
                                                        $state = $twoletterstate;
562                                                }
563                                        }
564
0
0
                                        if($href->{city}) {
565
0
0
                                                $city = uc($href->{city});
566                                        }
567                                        # Unabbreviated - look up both, helps with fallback to Maxmind
568
0
0
                                        my $fullstreet = $href->{'street'};
569                                # warn __LINE__;
570
0
0
                                        if($street = $fullstreet) {
571                                # warn __LINE__;
572
0
0
                                                $fullstreet .= ' ' . $href->{'type'};
573
0
0
                                                if(my $type = _abbreviate($href->{'type'})) {
574
0
0
                                                        $street .= " $type";
575                                                }
576
0
0
                                                if($href->{suffix}) {
577
0
0
                                                        $street .= ' ' . $href->{suffix};
578
0
0
                                                        $fullstreet .= ' ' . $href->{suffix};
579                                                }
580                                        }
581
0
0
                                        if($street) {
582                                # warn __LINE__;
583
0
0
                                                if(my $prefix = $href->{prefix}) {
584
0
0
                                                        $street = "$prefix $street";
585
0
0
                                                        $fullstreet = "$prefix $fullstreet";
586                                                }
587
0
0
                                                if($href->{'number'}) {
588
0
0
                                                        if($rc = $self->_get($href->{'number'}, "$street$city$state", 'US')) {
589
0
0
                                                                $rc->{'country'} = 'US';
590
0
0
                                                                return $rc;
591                                                        }
592
0
0
                                                        if($rc = $self->_get($href->{'number'}, "$fullstreet$city$state", 'US')) {
593
0
0
                                                                $rc->{'country'} = 'US';
594
0
0
                                                                return $rc;
595                                                        }
596                                                }
597                                                # warn("$street$city$state", 'US');
598
0
0
                                                if($rc = $self->_get("$street$city$state", 'US')) {
599
0
0
                                                        $rc->{'country'} = 'US';
600
0
0
                                                        return $rc;
601                                                }
602
0
0
                                                $street =~ s/\s+//g;
603
0
0
                                                if($rc = $self->_get("$street$city$state", 'US')) {
604
0
0
                                                        $rc->{'country'} = 'US';
605
0
0
                                                        return $rc;
606                                                }
607                                                # warn("$fullstreet$city$state", 'US');
608
0
0
                                                if($rc = $self->_get("$fullstreet$city$state", 'US')) {
609
0
0
                                                        $rc->{'country'} = 'US';
610
0
0
                                                        return $rc;
611                                                }
612
0
0
                                                $fullstreet =~ s/\s+//g;
613                                        }
614
0
0
                                        Carp::carp("Fast lookup of US location '$location' failed");
615                                } else {
616
0
0
                                        if($city =~ /^(\d.+),\s*([\w\s]+),\s*([\w\s]+)/) {
617
0
0
                                                my $lookup = "$1, $2, $state";
618
0
0
                                                if(my $href = (Geo::StreetAddress::US->parse_address($lookup) || Geo::StreetAddress::US->parse_location($lookup))) {
619                                                        # Street, City, County
620                                                        # 105 S. West Street, Spencer, Owen, Indiana, USA
621
0
0
                                                        $county = $3;
622
0
0
                                                        $county =~ s/\s*county$//i;
623
0
0
                                                        if($href->{'state'}) {
624
0
0
                                                                $state = $href->{'state'};
625                                                        } else {
626
0
0
                                                                Carp::croak(__PACKAGE__, ": Geo::StreetAddress::US couldn't find the state in '$lookup'");
627                                                        }
628
0
0
                                                        if(length($state) > 2) {
629
0
0
                                                                if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
630
0
0
                                                                        $state = $twoletterstate;
631                                                                }
632                                                        }
633
0
0
                                                        my %args = (county => uc($county), state => $state, country => 'US');
634
0
0
                                                        if($href->{city}) {
635
0
0
                                                                $city = $args{city} = uc($href->{city});
636                                                        }
637
0
0
                                                        if($href->{number}) {
638
0
0
                                                                $args{number} = $href->{number};
639                                                        }
640
0
0
                                                        if($street = $href->{street}) {
641
0
0
                                                                if(my $type = _abbreviate($href->{'type'})) {
642
0
0
                                                                        $street .= " $type";
643                                                                }
644
0
0
                                                                if($href->{suffix}) {
645
0
0
                                                                        $street .= ' ' . $href->{suffix};
646                                                                }
647
0
0
                                                                if(my $prefix = $href->{prefix}) {
648
0
0
                                                                        $street = "$prefix $street";
649                                                                }
650
0
0
                                                                $args{street} = uc($street);
651
0
0
                                                                if($href->{'number'}) {
652
0
0
                                                                        if($county) {
653
0
0
                                                                                if($rc = $self->_get($href->{'number'}, "$street$city$county$state", 'US')) {
654
0
0
                                                                                        $rc->{'country'} = 'US';
655
0
0
                                                                                        return $rc;
656                                                                                }
657                                                                        }
658
0
0
                                                                        if($rc = $self->_get($href->{'number'}, "$street$city$state", 'US')) {
659
0
0
                                                                                $rc->{'country'} = 'US';
660
0
0
                                                                                return $rc;
661                                                                        }
662
0
0
                                                                        if($county) {
663
0
0
                                                                                if($rc = $self->_get("$street$city$county$state", 'US')) {
664
0
0
                                                                                        $rc->{'country'} = 'US';
665
0
0
                                                                                        return $rc;
666                                                                                }
667                                                                        }
668
0
0
                                                                        if($rc = $self->_get("$street$city$state", 'US')) {
669
0
0
                                                                                $rc->{'country'} = 'US';
670
0
0
                                                                                return $rc;
671                                                                        }
672                                                                }
673                                                        }
674
0
0
                                                        return; # Not found
675                                                }
676
0
0
                                                Carp::croak($city);     # TODO: do something here
677                                        } elsif($city =~ /^(\w[\w\s]+),\s*([\w\s]+)/) {
678                                                # Perhaps it just has the street's name?
679                                                # Rockville Pike, Rockville, MD, USA
680
0
0
                                                my $first = uc($1);
681
0
0
                                                my $second = uc($2);
682
0
0
                                                if($second =~ /(\d+)\s+(.+)/) {
683
0
0
                                                        $second = "$1$2";
684                                                }
685
0
0
                                                if($rc = $self->_get("$first$second$state", 'US')) {
686
0
0
                                                        $rc->{'country'} = 'US';
687
0
0
                                                        return $rc;
688                                                }
689                                                # Perhaps it's a city in a county?
690                                                # Silver Spring, Montgomery County, MD, USA
691
0
0
                                                $second =~ s/\s+COUNTY$//;
692
0
0
                                                if($rc = $self->_get("$first$second$state", 'US')) {
693
0
0
                                                        $rc->{'country'} = 'US';
694
0
0
                                                        return $rc;
695                                                }
696                                                # Not all the database has the county
697
0
0
                                                if($rc = $self->_get("$first$state", 'US')) {
698
0
0
                                                        $rc->{'country'} = 'US';
699
0
0
                                                        return $rc;
700                                                }
701                                                # Brute force last ditch approach
702
0
0
                                                my $copy = uc($location);
703
0
0
                                                $copy =~ s/,\s+//g;
704
0
0
                                                $copy =~ s/\s*USA$//;
705
0
0
                                                if($rc = $self->_get($copy, 'US')) {
706
0
0
                                                        $rc->{'country'} = 'US';
707
0
0
                                                        return $rc;
708                                                }
709
0
0
                                                if($copy =~ s/(\d+)\s+/$1/) {
710
0
0
                                                        if($rc = $self->_get($copy, 'US')) {
711
0
0
                                                                $rc->{'country'} = 'US';
712
0
0
                                                                return $rc;
713                                                        }
714                                                }
715                                        }
716                                        # warn "Can't yet parse US location '$location'";
717                                }
718                        } elsif($c eq 'ca') {
719
0
0
                                if(length($state) > 2) {
720
0
0
                                        if(my $twoletterstate = ($_locale_ca //= Locale::CA->new())->{province2code}{uc($state)}) {
721
0
0
                                                $state = $twoletterstate;
722                                        }
723                                }
724
0
0
                                my $rc;
725
0
0
                                if($city !~ /,/) {
726                                        # Simple case looking up a city in a state in Canada
727
0
0
                                        $city = uc($city);
728
0
0
                                        if($rc = $self->_get("$city$state", 'CA')) {
729
0
0
                                                $rc->{'country'} = 'CA';
730
0
0
                                                return $rc;
731                                        }
732                                # } elsif(my $href = Geo::StreetAddress::Canada->parse_address("$city, $state")) {
733                                } elsif(my $href = 0) {
734                                        # Well formed, simple street address in Canada
735
0
0
                                        $state = $href->{'province'};
736
0
0
                                        if(length($state) > 2) {
737
0
0
                                                if(my $twoletterstate = ($_locale_ca //= Locale::CA->new())->{province2code}{uc($state)}) {
738
0
0
                                                        $state = $twoletterstate;
739                                                }
740                                        }
741
0
0
                                        my %args = (state => $state, country => 'CA');
742
0
0
                                        if($href->{city}) {
743
0
0
                                                $args{city} = uc($href->{city});
744                                        }
745
0
0
                                        if($href->{number}) {
746
0
0
                                                $args{number} = $href->{number};
747                                        }
748
0
0
                                        if($street = $href->{street}) {
749
0
0
                                                if(my $type = _abbreviate($href->{'type'})) {
750
0
0
                                                        $street .= " $type";
751                                                }
752
0
0
                                                if($href->{suffix}) {
753
0
0
                                                        $street .= ' ' . $href->{suffix};
754                                                }
755                                        }
756
0
0
                                        if($street) {
757
0
0
                                                if(my $prefix = $href->{prefix}) {
758
0
0
                                                        $street = "$prefix $street";
759                                                }
760
0
0
                                                $args{street} = uc($street);
761                                        }
762
0
0
                                        Carp::carp("Fast lookup of Canadian location '$location' failed");
763                                } else {
764
0
0
                                        if($city =~ /^(\w[\w\s]+),\s*([\w\s]+)/) {
765                                                # Perhaps it just has the street's name?
766                                                # Rockville Pike, Rockville, MD, USA
767
0
0
                                                my $first = uc($1);
768
0
0
                                                my $second = uc($2);
769
0
0
                                                if($rc = $self->_get("$first$second$state", 'CA')) {
770
0
0
                                                        $rc->{'country'} = 'CA';
771
0
0
                                                        return $rc;
772                                                }
773                                                # Perhaps it's a city in a county?
774                                                # Silver Spring, Montgomery County, MD, USA
775
0
0
                                                $second =~ s/\s+COUNTY$//;
776
0
0
                                                if($rc = $self->_get("$first$second$state", 'CA')) {
777
0
0
                                                        $rc->{'country'} = 'CA';
778
0
0
                                                        return $rc;
779                                                }
780                                                # Not all the database has the county
781
0
0
                                                if($rc = $self->_get("$first$state", 'CA')) {
782
0
0
                                                        $rc->{'country'} = 'CA';
783
0
0
                                                        return $rc;
784                                                }
785                                                # Brute force last ditch approach
786
0
0
                                                my $copy = uc($location);
787
0
0
                                                $copy =~ s/,\s+//g;
788
0
0
                                                $copy =~ s/\s*Canada$//i;
789
0
0
                                                if($rc = $self->_get($copy, 'CA')) {
790
0
0
                                                        $rc->{'country'} = 'CA';
791
0
0
                                                        return $rc;
792                                                }
793
0
0
                                                if($copy =~ s/(\d+)\s+/$1/) {
794
0
0
                                                        if($rc = $self->_get($copy, 'CA')) {
795
0
0
                                                                $rc->{'country'} = 'CA';
796
0
0
                                                                return $rc;
797                                                        }
798                                                }
799                                        }
800                                        # warn "Can't yet parse Canadian location '$location'";
801                                }
802                        } else {
803                                # Currently only handles Town, Region, Country
804                                # TODO: add addresses support
805
0
0
                                if(($c eq 'au') && (length($state) > 3)) {
806
0
0
                                        if(my $abbrev = Locale::SubCountry->new('AU')->code(ucfirst(lc($state)))) {
807
0
0
                                                if($abbrev ne 'unknown') {
808
0
0
                                                        $state = $abbrev;
809                                                }
810                                        }
811                                }
812
0
0
                                if($city =~ /^(\w[\w\s]+),\s*([,\w\s]+)/) {
813                                        # City includes a street name
814
0
0
                                        $street = uc($1);
815
0
0
                                        $city = uc($2);
816
0
0
                                        my $number;
817
0
0
                                        if($street =~ /^(\d+)\s+(.+)/) {
818
0
0
                                                $number = $1;
819
0
0
                                                $street = $2;
820                                        }
821
822                                        # TODO: Configurable - or better still remove the need
823
0
0
                                        if($city eq 'MINSTER, THANET') {
824
0
0
                                                $city = 'RAMSGATE';
825                                        }
826
0
0
                                        $street = _normalize($street);
827
0
0
                                        if($number) {
828
0
0
                                                if(my $rc = $self->_get("$number$street$city$state$c")) {
829
0
0
                                                        return $rc;
830                                                }
831                                                # If we can't find the number, at least find the road
832                                        }
833
0
0
                                        if(my $rc = $self->_get("$street$city$state$c")) {
834
0
0
                                                return $rc;
835                                        }
836                                }
837
0
0
                                if((!$street) || !$param->{'exact'}) {
838
0
0
                                        if(my $rc = $self->_get("$city$state$c")) {
839                                                # return {
840                                                        # 'number' => undef,
841                                                        # 'street' => undef,
842                                                        # 'city' => $city,
843                                                        # 'state' => $state,
844                                                        # 'country' => $country,
845                                                        # %{$rc}
846                                                # };
847
0
0
                                                return $rc;
848                                        }
849                                }
850                        }
851                }
852        } elsif($location =~ /([a-z\s]+),?\s*(United States|USA|US|Canada)$/i) {
853                # Looking for a state/province in Canada or the US
854
0
0
                $state = $1;
855
0
0
                $country = $2;
856
0
0
                if($country =~ /Canada/i) {
857
0
0
                        $country = 'CA';
858
0
0
                        if(length($state) > 2) {
859
0
0
                                if(my $twoletterstate = ($_locale_ca //= Locale::CA->new())->{province2code}{uc($state)}) {
860
0
0
                                        $state = $twoletterstate;
861                                }
862                        }
863                } else {
864
0
0
                        $country = 'US';
865
0
0
                        if(length($state) > 2) {
866
0
0
                                if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($state)}) {
867
0
0
                                        $state = $twoletterstate;
868                                }
869                        }
870                }
871
0
0
                if(my $rc = $self->_get("$state$country")) {
872
0
0
                        $rc->{'country'} = $country;
873
0
0
                        return $rc;
874                }
875        }
876
877
1
2
        if($country) {
878
1
100
                require Geo::Address::Parser && Geo::Address::Parser->import() unless Geo::Address::Parser->can('parse');
879
880
0
0
                if($country eq 'US') {
881
0
0
                        my $addr_parser = Geo::Address::Parser->new(country => 'US');
882
0
0
                        if(my $fields = $addr_parser->parse($location)) {
883
0
0
0
0
                                for my $key (keys %{$fields}) {
884
0
0
                                        delete $fields->{$key} unless defined $fields->{$key};
885                                }
886
0
0
0
0
                                if(my $rc = $self->_search($fields, keys %{$fields})) {
887
0
0
                                        return $rc;
888                                }
889                        }
890                }
891        }
892
893        # Finally try libpostal,
894        # which is good but uses a lot of memory
895
0
0
        if($libpostal_is_installed == LIBPOSTAL_UNKNOWN) {
896
0
0
0
0
                if(eval { require Geo::libpostal; } ) {
897
0
0
                        Geo::libpostal->import();
898
0
0
                        $libpostal_is_installed = LIBPOSTAL_INSTALLED;
899                } else {
900
0
0
                        $libpostal_is_installed = LIBPOSTAL_NOT_INSTALLED;
901                }
902        }
903
904        # print(__PACKAGE__, ': ', __LINE__, ": libpostal_is_installed = $libpostal_is_installed ($location)\n");
905
906
0
0
        if(($libpostal_is_installed == LIBPOSTAL_INSTALLED) && (my %addr = Geo::libpostal::parse_address($location))) {
907
0
0
                if($addr{'country'} && $addr{'state'} && ($addr{'country'} =~ /^(Canada|United States|USA|US)$/i)) {
908
0
0
                        if($street = $addr{'road'}) {
909
0
0
                                $street = _normalize($street);
910
0
0
                                $addr{'road'} = $street;
911                        }
912
0
0
                        if($addr{'country'} =~ /Canada/i) {
913
0
0
                                $addr{'country'} = 'Canada';
914
0
0
                                if(length($addr{'state'}) > 2) {
915
0
0
                                        if(my $twoletterstate = ($_locale_ca //= Locale::CA->new())->{province2code}{uc($addr{'state'})}) {
916
0
0
                                                $addr{'state'} = $twoletterstate;
917                                        }
918                                }
919                        } else {
920
0
0
                                $addr{'country'} = 'US';
921
0
0
                                if(length($addr{'state'}) > 2) {
922
0
0
                                        if(my $twoletterstate = ($_locale_us //= Locale::US->new())->{state2code}{uc($addr{'state'})}) {
923
0
0
                                                $addr{'state'} = $twoletterstate;
924                                        }
925                                }
926                        }
927
0
0
                        if($addr{'state_district'}) {
928
0
0
                                $addr{'state_district'} =~ s/^(.+)\s+COUNTY/$1/i;
929
0
0
                                if(my $rc = $self->_search(\%addr, ('house_number', 'road', 'city', 'state_district', 'state', 'country'))) {
930
0
0
                                        return $rc;
931                                }
932                        }
933
0
0
                        if(my $rc = $self->_search(\%addr, ('house_number', 'road', 'city', 'state', 'country'))) {
934
0
0
                                return $rc;
935                        }
936
0
0
                        if($addr{'house_number'}) {
937
0
0
                                if(my $rc = $self->_search(\%addr, ('road', 'city', 'state', 'country'))) {
938
0
0
                                        return $rc;
939                                }
940                        }
941                }
942        }
943
0
0
        if($location =~ s/,//g) {
944
0
0
                return $self->geocode($location);
945        }
946
0
0
        undef;
947}
948
949# $data is a hashref to data such as returned by Geo::libpostal::parse_address
950# @columns is the key names to use in $data
951sub _search {
952
0
0
        my ($self, $data, @columns) = @_;
953
954
0
0
        my $location;
955
0
0
        foreach my $column(@columns) {
956
0
0
                if($data->{$column}) {
957
0
0
                        $location .= $data->{$column};
958                }
959        }
960
0
0
        if($location) {
961
0
0
                return $self->_get($location);
962        }
963}
964
965# State must be the abbreviated form
966sub _get {
967
0
0
        my ($self, @location) = @_;
968
969
0
0
        my $location = join('', @location);
970
0
0
        $location =~ s/^\s+//;
971
0
0
        $location =~ s/,\s*//g;
972
0
0
        $location =~ tr/ž/z/;     # Remove wide characters
973
0
0
        $location =~ s/\xc5\xbe/z/g;
974
0
0
        $location =~ s/\N{U+017E}/z/g;
975
0
0
        $location =~ s/\s+//g;
976
977
0
0
        my $digest;
978
0
0
        if(length($location) <= 16) {
979
0
0
                $digest = uc($location);
980        } else {
981
0
0
                $digest = substr Digest::MD5::md5_base64(uc($location)), 0, 16;
982        }
983
984        # print __PACKAGE__, ': ', __LINE__, ': ', uc($location), " = $digest\n";
985        # my @call_details = caller(0);
986        # print "\t", ' called from line ', $call_details[2], "\n";
987        # @call_details = caller(1);
988        # print "\t", ' called from line ', $call_details[2], "\n";
989
990
0
0
        if(defined($unknown_locations{$digest})) {
991
0
0
                return;
992        }
993        # my @call_details = caller(0);
994        # print "line ", $call_details[2], "\n";
995        # print "$location: $digest\n";
996
0
0
        if(my $cache = $self->{'cache'}) {
997
0
0
                if(my $rc = $cache->get_object($digest)) {
998
0
0
                        return Storable::thaw($rc->value());
999                }
1000        }
1001        my $openaddr_db = $self->{openaddr_db} ||
1002                Geo::Coder::Free::DB::openaddresses->new(
1003                        cache => $self->{cache} || CHI->new(driver => 'Memory', datastore => {}),
1004                        directory => $self->{openaddr},
1005
0
0
                        id => 'md5',
1006                        no_entry => 1,
1007                );
1008
0
0
        $self->{openaddr_db} = $openaddr_db;
1009
0
0
        if(my $geohash = $openaddr_db->geohash(md5 => $digest)) {
1010
0
0
                $self->{'geo_hash'} ||= Geo::Hash->new();
1011
0
0
                my ($latitude, $longitude) = $self->{'geo_hash'}->decode($geohash);
1012
1013
0
0
                my $rc = Geo::Location::Point->new({
1014                        'lat' => $latitude,
1015                        'long' => $longitude,
1016                        'lng' => $longitude,
1017                        'location' => $location,
1018                        'database' => 'OpenAddresses'
1019                });
1020
1021
0
0
                if(my $cache = $self->{'cache'}) {
1022
0
0
                        $cache->set($digest, Storable::freeze($rc), '1 month');
1023                }
1024
1025
0
0
                return $rc;
1026        }
1027
0
0
        $unknown_locations{$digest} = 1;
1028
0
0
        return;
1029}
1030
1031 - 1037
=head2  reverse_geocode

    $location = $geocoder->reverse_geocode(latlng => '37.778907,-122.39732');

To be done.

=cut
1038
1039# At the moment this can't be supported as the DB only has a hash in it
1040sub reverse_geocode {
1041
3
1
601
        Carp::croak(__PACKAGE__, ': Reverse lookup is not yet supported');
1042}
1043
1044 - 1048
=head2  ua

Does nothing, here for compatibility with other geocoders

=cut
1049
1050
1
sub ua {
1051}
1052
1053 - 1111
=head1 AUTHOR

Nigel Horne <njh@bandsman.co.uk>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

The contents of lib/Geo/Coder/Free/OpenAddresses/databases comes from
the places listed in the synopsis.

=head1 BUGS

Lots of lookups fail at the moment.

There are die()s where the code path has yet to be written.

The openaddresses data doesn't cover the globe.

Can't parse and handle "London, England".

Currently only searches US and Canadian data.

If you do search in the UK, only look up towns, full addresses aren't
included.  So these will print the same.

    use Geo::Coder::Free::OpenAddresses;

    $location = $geo_coder->geocode(location => '22 Central Road, Ramsgate, Kent, England');
    print $location->{latitude}, "\n";
    print $location->{longitude}, "\n";
    $location = $geo_coder->geocode(location => '7 Hillbrow Road, St Lawrence, Thanet, Kent, England');
    print $location->{latitude}, "\n";
    print $location->{longitude}, "\n";

When I added the WhosOnFirst data I should have renamed this as it contains
data from both sources.

The database shouldn't be called $OPENADDR_HOME/openaddresses.sql,
since the database now also includes data from WhosOnFirst.

The name openaddresses.sql shouldn't be hardcoded,
add support to "new" for the parameter "dbname".

The argument "openaddr",
would be less confusing if it were called "directory",

=head1 SEE ALSO

VWF, openaddresses.

=head1 LICENSE AND COPYRIGHT

Copyright 2017-2025 Nigel Horne.

The program code is released under the following licence: GPL for personal use on a single computer.
All other users (including Commercial, Charity, Educational, Government)
must apply in writing for a licence for use from Nigel Horne at `<njh at nigelhorne.com>`.

=cut
1112
11131;