| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Geo::Coder::US; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Geo::Coder::US - Geocode (estimate latitude and longitude for) any US address |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Geo::Coder::US; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
Geo::Coder::US->set_db( "geocoder.db" ); |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
my @matches = Geo::Coder::US->geocode( |
|
14
|
|
|
|
|
|
|
"1600 Pennsylvania Ave., Washington, DC" ); |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my @matches = Geo::Coder::US->geocode( |
|
17
|
|
|
|
|
|
|
"42nd & Broadway New York NY" ) |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
my ($ora) = Geo::Coder::US->geocode( |
|
20
|
|
|
|
|
|
|
"1005 Gravenstein Hwy N, 95472" ); |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
print "O'Reilly is located at $ora->{lat} degrees north, " |
|
23
|
|
|
|
|
|
|
"$ora->{long} degrees east.\n"; |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
Geo::Coder::US provides a complete facility for geocoding US addresses, that |
|
29
|
|
|
|
|
|
|
is, estimating the latitude and longitude of any street address or intersection |
|
30
|
|
|
|
|
|
|
in the United States, using the TIGER/Line data set from the US Census Bureau. |
|
31
|
|
|
|
|
|
|
Geo::Coder::US uses Geo::TigerLine to parse this data, and DB_File to store a |
|
32
|
|
|
|
|
|
|
highly compressed distillation of it, and Geo::StreetAddress::US to parse |
|
33
|
|
|
|
|
|
|
addresses into normalized components suitable for looking up in its |
|
34
|
|
|
|
|
|
|
database. |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
You can find a live demo of this code at L. The |
|
37
|
|
|
|
|
|
|
demo.cgi script is included in eg/ directory distributed with this module, |
|
38
|
|
|
|
|
|
|
along with a whole bunch of other goodies. See L |
|
39
|
|
|
|
|
|
|
for how to build your own Geo::Coder::US database. |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
Consider using a web service to access this geocoder over the Internet, |
|
42
|
|
|
|
|
|
|
rather than going to all the trouble of building a database yourself. |
|
43
|
|
|
|
|
|
|
See eg/soap-client.pl, eg/xmlrpc-client.pl, and eg/rest-client.pl for |
|
44
|
|
|
|
|
|
|
different examples of working clients for the rpc.geocoder.us geocoder |
|
45
|
|
|
|
|
|
|
web service. |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=head1 METHODS |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
In general, the only methods you are likely to need to call on |
|
50
|
|
|
|
|
|
|
Geo::Coder::US are set_db() and geocode(). The following documentation |
|
51
|
|
|
|
|
|
|
is included for completeness's sake, and for the benefit of developers |
|
52
|
|
|
|
|
|
|
interested in using bits of the module's internals. |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Note: Calling conventions for address and intersection specifiers are |
|
55
|
|
|
|
|
|
|
discussed in the following section on CALLING CONVENTIONS. |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
=over 4 |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=cut |
|
60
|
|
|
|
|
|
|
|
|
61
|
3
|
|
|
3
|
|
2211
|
use 5.6.1; |
|
|
3
|
|
|
|
|
13
|
|
|
|
3
|
|
|
|
|
148
|
|
|
62
|
3
|
|
|
3
|
|
12118
|
use Geo::StreetAddress::US; |
|
|
3
|
|
|
|
|
276588
|
|
|
|
3
|
|
|
|
|
284
|
|
|
63
|
3
|
|
|
3
|
|
2051
|
use DB_File; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
use strict; |
|
65
|
|
|
|
|
|
|
use warnings; |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
our $VERSION = '1.00'; |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
use constant SNAP_DISTANCE => 0.00015; |
|
70
|
|
|
|
|
|
|
# distance to snap intersection points, in degrees |
|
71
|
|
|
|
|
|
|
# 0.00005 = ~7 meters |
|
72
|
|
|
|
|
|
|
# 0.0001 = ~14 meters |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
our $Parser = 'Geo::StreetAddress::US'; |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
our ( %DB, $DBO ); |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub db { \%DB } |
|
79
|
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub db_file { $DBO } |
|
81
|
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
sub set_db { |
|
83
|
|
|
|
|
|
|
my ($class, $file, $writable) = @_; |
|
84
|
|
|
|
|
|
|
return $DBO if $DBO and not $writable; |
|
85
|
|
|
|
|
|
|
my $mode = $writable ? O_CREAT|O_RDWR : O_RDONLY; |
|
86
|
|
|
|
|
|
|
$DB_BTREE->{compare} = sub { lc $_[0] cmp lc $_[1] }; |
|
87
|
|
|
|
|
|
|
$DBO = tie %DB, "DB_File", $file, $mode, 0666, $DB_BTREE; |
|
88
|
|
|
|
|
|
|
return \%DB; |
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
=item Geo::Coder::US->geocode( $string ) |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
Given a string containing a street address or intersection, return a |
|
94
|
|
|
|
|
|
|
list of specifiers including latitude and longitude for all matching |
|
95
|
|
|
|
|
|
|
entities in the database. To keep from churning over the entire database, |
|
96
|
|
|
|
|
|
|
the given address string must contain either a city and state, or a ZIP |
|
97
|
|
|
|
|
|
|
code (or both), or geocode() will return undef. |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
geocode() will attempt to normalize directional prefixes and suffixes, |
|
100
|
|
|
|
|
|
|
street types, and state abbreviations, as well as substitute TIGER/Line's |
|
101
|
|
|
|
|
|
|
idea of the "primary street name", if an alternate street name was |
|
102
|
|
|
|
|
|
|
provided instead. |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
If geocode() can parse the address, but not find a match in the database, |
|
105
|
|
|
|
|
|
|
it will return a hashref containing the parsed and normalized address |
|
106
|
|
|
|
|
|
|
or intersection, but without the "lat" and "long" keys specifying the |
|
107
|
|
|
|
|
|
|
location. If geocode() cannot even parse the address, it will return |
|
108
|
|
|
|
|
|
|
undef. B for the existence of "lat" and "long" keys |
|
109
|
|
|
|
|
|
|
in the hashes returned from geocode() B attempting to use the |
|
110
|
|
|
|
|
|
|
values! This serves to distinguish between addresses that cannot be |
|
111
|
|
|
|
|
|
|
found versus addresses that are completely unparseable. |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
geocode() attempts to be as forgiving as possible when geocoding an |
|
114
|
|
|
|
|
|
|
address. If you say "Mission Ave" and all it knows about is "Mission St", |
|
115
|
|
|
|
|
|
|
then "Mission St" is what you'll get back. If you leave off directional |
|
116
|
|
|
|
|
|
|
identifiers, geocode() will return address geocoded in all the variants |
|
117
|
|
|
|
|
|
|
it can find, i.e. both "N Main St" I "S Main St". |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
Don't be surprised if geocoding an intersection returns more than one |
|
120
|
|
|
|
|
|
|
lat/long pair for a single intersection. If one of the streets curves |
|
121
|
|
|
|
|
|
|
greatly or doglegs even slightly, this will be the likely outcome. |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
geocode() is probably the method you want to use. See more in the |
|
124
|
|
|
|
|
|
|
following section on the structure of the returned address and |
|
125
|
|
|
|
|
|
|
intersection specifiers. |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
=cut |
|
128
|
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
sub geocode { |
|
130
|
|
|
|
|
|
|
my ($class, $addr) = @_; |
|
131
|
|
|
|
|
|
|
my @results; |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
my $part = $Parser->parse_location($addr); |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
return unless $part |
|
136
|
|
|
|
|
|
|
and ($part->{zip} or ($part->{city} and $part->{state})); |
|
137
|
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
if ( exists $part->{street1} ) { |
|
139
|
|
|
|
|
|
|
@results = $class->lookup_intersection($part); |
|
140
|
|
|
|
|
|
|
} else { |
|
141
|
|
|
|
|
|
|
@results = $class->lookup_ranges($part); |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
return @results ? @results : $part; |
|
145
|
|
|
|
|
|
|
} |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=item Geo::Coder::US->geocode_address( $string ) |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Works exactly like geocode(), but only parses addresses. |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub geocode_address { |
|
154
|
|
|
|
|
|
|
my ($class, $addr) = @_; |
|
155
|
|
|
|
|
|
|
my @results; |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
my $part = $Parser->parse_address($addr); |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
return unless $part |
|
160
|
|
|
|
|
|
|
and ($part->{zip} or ($part->{city} and $part->{state})); |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
@results = $class->lookup_ranges($part); |
|
163
|
|
|
|
|
|
|
return @results ? @results : $part; |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=item Geo::Coder::US->geocode_intersection( $string ) |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
Works exactly like geocode(), but only parses intersections. |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=cut |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
sub geocode_intersection { |
|
173
|
|
|
|
|
|
|
my ($class, $addr) = @_; |
|
174
|
|
|
|
|
|
|
my @results; |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
my $part = $Parser->parse_intersection($addr); |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
return unless $part and $part->{street1} and $part->{street2} |
|
179
|
|
|
|
|
|
|
and ($part->{zip} or ($part->{city} and $part->{state})); |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
@results = $class->lookup_intersection($part); |
|
182
|
|
|
|
|
|
|
return @results ? @results : $part; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
=item Geo::Coder::US->filter_ranges( $spec, @candidates ) |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
Filters a list of address specifiers (presumably from the database) |
|
188
|
|
|
|
|
|
|
against a query specifier, filtering by prefix, type, suffix, or primary |
|
189
|
|
|
|
|
|
|
name if possible. Returns a list of matching specifiers. filter_ranges() |
|
190
|
|
|
|
|
|
|
will ignore a filtering step if it would result in no specifiers being |
|
191
|
|
|
|
|
|
|
returned. You probably won't need to use this. |
|
192
|
|
|
|
|
|
|
|
|
193
|
|
|
|
|
|
|
=cut |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub filter_ranges { |
|
196
|
|
|
|
|
|
|
my ($class, $args, @addrs) = @_; |
|
197
|
|
|
|
|
|
|
my @filter; |
|
198
|
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
for my $field (qw( prefix type suffix city zip )) { |
|
200
|
|
|
|
|
|
|
next unless $args->{$field}; |
|
201
|
|
|
|
|
|
|
@filter = grep { lc $_->{$field} eq lc $args->{$field} } @addrs; |
|
202
|
|
|
|
|
|
|
@addrs = @filter if @filter; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
return @addrs; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
=item Geo::Coder::US->find_ranges( $address_spec ) |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Given a normalized address specifier, return all the address ranges |
|
211
|
|
|
|
|
|
|
in the database that appear to cover that address. find_ranges() |
|
212
|
|
|
|
|
|
|
ignores prefix, suffix, and type fields in the specifier for search |
|
213
|
|
|
|
|
|
|
purposes, and then filters against them ex post facto. The intention |
|
214
|
|
|
|
|
|
|
for find_ranges() to find the closest match possible in preference to |
|
215
|
|
|
|
|
|
|
returning nothing. You probably want to use lookup_ranges() instead, |
|
216
|
|
|
|
|
|
|
which will call find_ranges() for you. |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
=cut |
|
219
|
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub find_zips_by_city { |
|
221
|
|
|
|
|
|
|
my ($class, $args) = @_; |
|
222
|
|
|
|
|
|
|
my $city = "$args->{city}, $args->{state}"; |
|
223
|
|
|
|
|
|
|
return unless exists $DB{$city}; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
my @zips = unpack "w*", $DB{$city}; |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
# city, state might point to the FIPS code of the |
|
228
|
|
|
|
|
|
|
# place that encompasses it. in which case, get the place |
|
229
|
|
|
|
|
|
|
# name for *that* FIPS code and try again. |
|
230
|
|
|
|
|
|
|
if (@zips == 1 and $zips[0] > 99999) { |
|
231
|
|
|
|
|
|
|
my $fips = sprintf "%07d", $zips[0]; |
|
232
|
|
|
|
|
|
|
$city = "$DB{$fips}, $args->{state}"; |
|
233
|
|
|
|
|
|
|
return unless exists $DB{$city}; |
|
234
|
|
|
|
|
|
|
@zips = unpack "w*", $DB{$city}; |
|
235
|
|
|
|
|
|
|
} |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# finally, format the ZIP codes |
|
238
|
|
|
|
|
|
|
return map { sprintf "%05d", $_ } @zips; |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
sub find_streets_by_zip { |
|
242
|
|
|
|
|
|
|
my ($class, $args, @zips) = @_; |
|
243
|
|
|
|
|
|
|
my @streets; |
|
244
|
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
for my $zip ( @zips ) { |
|
246
|
|
|
|
|
|
|
my $path = "/$zip/$args->{street}/"; |
|
247
|
|
|
|
|
|
|
my ($key, $value); |
|
248
|
|
|
|
|
|
|
$DBO->seq( $key = $path, $value, R_CURSOR ); |
|
249
|
|
|
|
|
|
|
while ( $key and $value and $key =~ /^$path/i ) { |
|
250
|
|
|
|
|
|
|
if ($value =~ /^\//o) { |
|
251
|
|
|
|
|
|
|
push @streets, map { "/$zip$_" } split( ",", $value ); |
|
252
|
|
|
|
|
|
|
} else { |
|
253
|
|
|
|
|
|
|
push @streets, $key; |
|
254
|
|
|
|
|
|
|
} |
|
255
|
|
|
|
|
|
|
$DBO->seq( $key, $value, R_NEXT ); |
|
256
|
|
|
|
|
|
|
} |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
return @streets; |
|
260
|
|
|
|
|
|
|
} |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
sub find_streets { |
|
263
|
|
|
|
|
|
|
my ($class, $args) = @_; |
|
264
|
|
|
|
|
|
|
my (@streets); |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# try first with the zip code if we have one |
|
267
|
|
|
|
|
|
|
if ( $args->{zip} ) { |
|
268
|
|
|
|
|
|
|
@streets = $class->find_streets_by_zip( $args, $args->{zip} ); |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
# no luck with the zip code? try again |
|
272
|
|
|
|
|
|
|
if ( not @streets and $args->{city} and $args->{state} ) { |
|
273
|
|
|
|
|
|
|
my @zips = $class->find_zips_by_city( $args ); |
|
274
|
|
|
|
|
|
|
@streets = $class->find_streets_by_zip( $args, @zips ); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
return @streets; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
sub add_city_and_state { |
|
281
|
|
|
|
|
|
|
my ($class, @results) = @_; |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
for my $item (@results) { |
|
284
|
|
|
|
|
|
|
my $fips = sprintf "%07d", $item->{fips}; |
|
285
|
|
|
|
|
|
|
my $state = substr($fips, 0, 2); |
|
286
|
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
# if the FIPS code points to a county subdivision (i.e. not |
|
288
|
|
|
|
|
|
|
# in the database) find the nearest inhabited place by ZIP |
|
289
|
|
|
|
|
|
|
# code instead. |
|
290
|
|
|
|
|
|
|
# |
|
291
|
|
|
|
|
|
|
$fips = sprintf "%07d", unpack( "w", $DB{$item->{zip}} ) |
|
292
|
|
|
|
|
|
|
unless $DB{$fips}; |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
$item->{city} = $DB{$fips}; |
|
295
|
|
|
|
|
|
|
$item->{state} = $Geo::StreetAddress::US::State_FIPS{$state}; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
sub find_ranges { |
|
300
|
|
|
|
|
|
|
my ($class, $args) = @_; |
|
301
|
|
|
|
|
|
|
my @streets = $class->find_streets($args); |
|
302
|
|
|
|
|
|
|
my $number = $args->{number}; |
|
303
|
|
|
|
|
|
|
my @results; |
|
304
|
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
$number =~ s/\D//gos; # remove non-numerics, e.g. dashes |
|
306
|
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
for my $street (@streets) { |
|
308
|
|
|
|
|
|
|
my ($fips, @data) = unpack "w*", $DB{$street}; |
|
309
|
|
|
|
|
|
|
my (@from, @to, @range, @best, $matched); |
|
310
|
|
|
|
|
|
|
while (@data) { |
|
311
|
|
|
|
|
|
|
@from = splice( @data, 0, 2 ) if $data[0] > 1_000_000; |
|
312
|
|
|
|
|
|
|
while (@data and $data[0] < 1_000_000) { |
|
313
|
|
|
|
|
|
|
shift @data if not $data[0]; # skip street-side zero marker |
|
314
|
|
|
|
|
|
|
@range = splice( @data, 0, 2 ); |
|
315
|
|
|
|
|
|
|
if ($number % 2 == $range[0] % 2 and |
|
316
|
|
|
|
|
|
|
(($number >= $range[0] and $number <= $range[1]) or |
|
317
|
|
|
|
|
|
|
($number <= $range[0] and $number >= $range[1]))) { |
|
318
|
|
|
|
|
|
|
$matched++; |
|
319
|
|
|
|
|
|
|
shift @data while @data and $data[0] < 1_000_000; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
else { |
|
322
|
|
|
|
|
|
|
next if $best[0] and |
|
323
|
|
|
|
|
|
|
abs($best[0] - $number) < abs($range[0] - $number); |
|
324
|
|
|
|
|
|
|
@best = ($range[0], @from); |
|
325
|
|
|
|
|
|
|
} |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
last unless @data; |
|
328
|
|
|
|
|
|
|
@to = splice( @data, 0, 2 ); |
|
329
|
|
|
|
|
|
|
last if $matched; |
|
330
|
|
|
|
|
|
|
@best = ($range[1], @to) |
|
331
|
|
|
|
|
|
|
if $best[0] and |
|
332
|
|
|
|
|
|
|
abs($best[0] - $number) > abs($range[0] - $number); |
|
333
|
|
|
|
|
|
|
@from = @to; |
|
334
|
|
|
|
|
|
|
} |
|
335
|
|
|
|
|
|
|
if (@best and not $matched) { |
|
336
|
|
|
|
|
|
|
@range = @best[0,0]; |
|
337
|
|
|
|
|
|
|
@from = @to = @best[1,2]; |
|
338
|
|
|
|
|
|
|
} |
|
339
|
|
|
|
|
|
|
if ($matched or @best) { |
|
340
|
|
|
|
|
|
|
my %found = ( fips => $fips ); |
|
341
|
|
|
|
|
|
|
@found{qw{ zip street type prefix suffix }} |
|
342
|
|
|
|
|
|
|
= split "/", substr($street, 1), 5; |
|
343
|
|
|
|
|
|
|
@found{qw{ toadd fradd }} = @range; |
|
344
|
|
|
|
|
|
|
@found{qw{ frlat frlong tolat tolong }} |
|
345
|
|
|
|
|
|
|
= map( $_ / 1_000_000, @from, @to ); |
|
346
|
|
|
|
|
|
|
$found{$_} *= -1 for qw/frlong tolong/; |
|
347
|
|
|
|
|
|
|
push @results, \%found; |
|
348
|
|
|
|
|
|
|
} |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
$class->add_city_and_state( @results ); |
|
352
|
|
|
|
|
|
|
return $class->filter_ranges( $args, @results ); |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=item Geo::Coder::US->lookup_ranges( $address_spec, @ranges ) |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
Given an address specifier and (optionally) some address ranges from the |
|
358
|
|
|
|
|
|
|
database, interpolate the street address into the street segment referred |
|
359
|
|
|
|
|
|
|
to by the address range, and return a latitude and longitude for the |
|
360
|
|
|
|
|
|
|
given address within each of the given ranges. If @ranges is not given, |
|
361
|
|
|
|
|
|
|
lookup_ranges() calls find_ranges() with the given address specifier, |
|
362
|
|
|
|
|
|
|
and uses those returned. You probably want to just use geocode() instead, |
|
363
|
|
|
|
|
|
|
which also parses an address string and determines whether it's a proper |
|
364
|
|
|
|
|
|
|
address or an intersection automatically. |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=cut |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
sub lookup_ranges { |
|
369
|
|
|
|
|
|
|
my ($class, $args, @addrs) = @_; |
|
370
|
|
|
|
|
|
|
my %results; |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
@addrs = $class->find_ranges($args) unless @addrs; |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
for my $range (@addrs) { |
|
375
|
|
|
|
|
|
|
my %target = %$args; |
|
376
|
|
|
|
|
|
|
if ($range->{fradd} == $range->{toadd}) { |
|
377
|
|
|
|
|
|
|
@target{qw{ lat long number }} = @$range{qw{ frlat frlong fradd }}; |
|
378
|
|
|
|
|
|
|
} else { |
|
379
|
|
|
|
|
|
|
my $pct = ($args->{number} - $range->{toadd}) / |
|
380
|
|
|
|
|
|
|
($range->{fradd} - $range->{toadd}); |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
$target{lat} = sprintf "%.6f", |
|
383
|
|
|
|
|
|
|
$range->{frlat} + ($range->{tolat} - $range->{frlat} ) * $pct; |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
$target{long} = sprintf "%.6f", |
|
386
|
|
|
|
|
|
|
$range->{frlong} + ($range->{tolong} - $range->{frlong}) * $pct; |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
$target{number} = $args->{number}; |
|
389
|
|
|
|
|
|
|
} |
|
390
|
|
|
|
|
|
|
$target{$_} = $range->{$_} |
|
391
|
|
|
|
|
|
|
for (qw( prefix street type suffix city state zip )); |
|
392
|
|
|
|
|
|
|
|
|
393
|
|
|
|
|
|
|
$results{"$target{lat}:$target{long}"} = \%target; |
|
394
|
|
|
|
|
|
|
} |
|
395
|
|
|
|
|
|
|
|
|
396
|
|
|
|
|
|
|
my @filter = grep { $_->{number} eq $args->{number} } values %results; |
|
397
|
|
|
|
|
|
|
return @filter ? @filter : values %results; |
|
398
|
|
|
|
|
|
|
} |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
=item Geo::Coder::US->find_segments( $intersection_spec ) |
|
401
|
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
Given a normalized intersection specifier, find all of the street segments |
|
403
|
|
|
|
|
|
|
in the database matching the two given streets in the given locale or |
|
404
|
|
|
|
|
|
|
ZIP code. find_segments() ignores prefix, suffix, and type fields in |
|
405
|
|
|
|
|
|
|
the specifier for search purposes, and then filters against them ex |
|
406
|
|
|
|
|
|
|
post facto. The intention for find_segments() to find the closest match |
|
407
|
|
|
|
|
|
|
possible in preference to returning nothing. You probably want to use |
|
408
|
|
|
|
|
|
|
lookup_intersection() instead, which will call find_segments() for you. |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
=cut |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
sub find_segments { |
|
413
|
|
|
|
|
|
|
my ($class, $args) = @_; |
|
414
|
|
|
|
|
|
|
my @streets = $class->find_streets($args); |
|
415
|
|
|
|
|
|
|
my @segments; |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
for my $street (@streets) { |
|
418
|
|
|
|
|
|
|
my ($fips, @data) = unpack "w*", $DB{$street}; |
|
419
|
|
|
|
|
|
|
my (@from, @to); |
|
420
|
|
|
|
|
|
|
while (@data) { |
|
421
|
|
|
|
|
|
|
@from = splice( @data, 0, 2 ) if $data[0] > 1_000_000; |
|
422
|
|
|
|
|
|
|
shift @data while @data and $data[0] < 1_000_000; |
|
423
|
|
|
|
|
|
|
last unless @data; |
|
424
|
|
|
|
|
|
|
my @to = splice( @data, 0, 2 ); |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
my %found = (fips => $fips); |
|
427
|
|
|
|
|
|
|
@found{qw{ zip street type prefix suffix }} |
|
428
|
|
|
|
|
|
|
= split "/", substr($street, 1), 5; |
|
429
|
|
|
|
|
|
|
@found{qw{ city state }} = @$args{qw{ city state }}; |
|
430
|
|
|
|
|
|
|
@found{qw{ frlat frlong tolat tolong }} |
|
431
|
|
|
|
|
|
|
= map( $_ / 1_000_000, @from, @to ); |
|
432
|
|
|
|
|
|
|
$found{$_} *= -1 for qw/frlong tolong/; |
|
433
|
|
|
|
|
|
|
push @segments, \%found; |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
@from = @to; |
|
436
|
|
|
|
|
|
|
} |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
$class->add_city_and_state( @segments ); |
|
440
|
|
|
|
|
|
|
return $class->filter_ranges( $args, @segments ); |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
=item Geo::Coder::US->lookup_intersection( $intersection_spec ) |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
Given an intersection specifier, return all of the intersections in the |
|
446
|
|
|
|
|
|
|
database between the two streets specified, plus a latitude and longitude |
|
447
|
|
|
|
|
|
|
for each intersection. You probably want to just use geocode() instead, |
|
448
|
|
|
|
|
|
|
which also parses an address string and determines whether it's a proper |
|
449
|
|
|
|
|
|
|
address or an intersection automatically. |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
=cut |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
sub lookup_intersection { |
|
454
|
|
|
|
|
|
|
my ($class, $args) = @_; |
|
455
|
|
|
|
|
|
|
my (@points1, @points2, %results); |
|
456
|
|
|
|
|
|
|
my %subargs = %$args; |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
$subargs{$_} = $args->{$_ . 1} for (qw( prefix street suffix type )); |
|
459
|
|
|
|
|
|
|
push @points1, |
|
460
|
|
|
|
|
|
|
[$_->{frlat}, $_->{frlong}, $_], |
|
461
|
|
|
|
|
|
|
[$_->{tolat}, $_->{tolong}, $_] |
|
462
|
|
|
|
|
|
|
for $class->find_segments(\%subargs); |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
$subargs{$_} = $args->{$_ . 2} for (qw( prefix street suffix type )); |
|
465
|
|
|
|
|
|
|
push @points2, |
|
466
|
|
|
|
|
|
|
[$_->{frlat}, $_->{frlong}, $_], |
|
467
|
|
|
|
|
|
|
[$_->{tolat}, $_->{tolong}, $_] |
|
468
|
|
|
|
|
|
|
for $class->find_segments(\%subargs); |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
return unless @points1 and @points2; |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
%subargs = %$args; |
|
473
|
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
for my $x (@points1) { |
|
475
|
|
|
|
|
|
|
for my $y (@points2) { |
|
476
|
|
|
|
|
|
|
if (abs($x->[0] - $y->[0]) < SNAP_DISTANCE and |
|
477
|
|
|
|
|
|
|
abs($x->[1] - $y->[1]) < SNAP_DISTANCE) { |
|
478
|
|
|
|
|
|
|
my ($st1, $st2, %target) = ($x->[2], $y->[2]); |
|
479
|
|
|
|
|
|
|
$target{lat} = $x->[0]; |
|
480
|
|
|
|
|
|
|
$target{long} = $x->[1]; |
|
481
|
|
|
|
|
|
|
$target{$_ . 1} = $st1->{$_} for (qw( prefix type suffix )); |
|
482
|
|
|
|
|
|
|
$target{street1} = $st1->{street}; |
|
483
|
|
|
|
|
|
|
$target{$_ . 2} = $st2->{$_} for (qw( prefix type suffix )); |
|
484
|
|
|
|
|
|
|
$target{street2} = $st2->{street}; |
|
485
|
|
|
|
|
|
|
$target{$_} = $st1->{$_} || $st2->{$_} for qw/zip city state/; |
|
486
|
|
|
|
|
|
|
$results{"$target{lat}:$target{long}"} = \%target; |
|
487
|
|
|
|
|
|
|
} |
|
488
|
|
|
|
|
|
|
} |
|
489
|
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
return values %results; |
|
492
|
|
|
|
|
|
|
} |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
1; |
|
495
|
|
|
|
|
|
|
__END__ |