| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# Google's Open Location Code |
|
2
|
|
|
|
|
|
|
# https://github.com/google/open-location-code |
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package Geo::OLC; |
|
5
|
|
|
|
|
|
|
|
|
6
|
5
|
|
|
5
|
|
68371
|
use strict; |
|
|
5
|
|
|
|
|
7
|
|
|
|
5
|
|
|
|
|
123
|
|
|
7
|
5
|
|
|
5
|
|
17
|
use warnings; |
|
|
5
|
|
|
|
|
5
|
|
|
|
5
|
|
|
|
|
131
|
|
|
8
|
5
|
|
|
5
|
|
20
|
use List::Util qw(min max); |
|
|
5
|
|
|
|
|
11
|
|
|
|
5
|
|
|
|
|
757
|
|
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
Geo::OLC - API for Google's Open Location Codes |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
Open Location Codes are a Google-created method of reducing a |
|
17
|
|
|
|
|
|
|
Latitude/Longitude pair to a short string. This module implements the |
|
18
|
|
|
|
|
|
|
recommended API from L |
|
19
|
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
use Geo::OLC qw(encode decode shorten recover_nearest); |
|
21
|
|
|
|
|
|
|
use Geo::OLC qw(:all); |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
$code = encode(34.6681375,135.502765625,11); |
|
24
|
|
|
|
|
|
|
# '8Q6QMG93+742' |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
$ref = decode('8Q6QMG93+742'); |
|
27
|
|
|
|
|
|
|
# @{$ref->{center}} == (34.6681375,135.502765625) |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
$short = shorten('8Q6QMG93+742',34.6937048,135.5016142); |
|
30
|
|
|
|
|
|
|
# 'MG93+742' ("...in Osaka") |
|
31
|
|
|
|
|
|
|
$short = shorten('8Q6QMG93+742',34.6788184,135.4987303); |
|
32
|
|
|
|
|
|
|
# '93+742' ("...in Chuo Ward, Osaka") |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
$full = recover_nearest('XQP5+',35.0060799,135.6909098); |
|
35
|
|
|
|
|
|
|
# '8Q6QXQP5+' (Kyoto Station, "XQP5+ in Kyoto") |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
By default, Geo::OLC does not export any functions. |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
=cut |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
BEGIN { |
|
42
|
5
|
|
|
5
|
|
20
|
require Exporter; |
|
43
|
5
|
|
|
|
|
6
|
our $VERSION = 1.00; |
|
44
|
5
|
|
|
|
|
32
|
our @ISA = qw(Exporter); |
|
45
|
5
|
|
|
|
|
9
|
our @EXPORT = qw(); |
|
46
|
5
|
|
|
|
|
11
|
our @EXPORT_OK = qw(is_valid is_short is_full encode decode |
|
47
|
|
|
|
|
|
|
shorten shorten46 recover_nearest _code_digits); |
|
48
|
5
|
|
|
|
|
6161
|
our %EXPORT_TAGS = (all => \@EXPORT_OK); |
|
49
|
|
|
|
|
|
|
} |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
# set up the radix-20 digits |
|
52
|
|
|
|
|
|
|
# |
|
53
|
|
|
|
|
|
|
my $radix = '23456789CFGHJMPQRVWX'; |
|
54
|
|
|
|
|
|
|
my @rchar = split(//,$radix); |
|
55
|
|
|
|
|
|
|
my $_i=0; |
|
56
|
|
|
|
|
|
|
my %rval = map(($_=>$_i++),@rchar); |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
# calculate size of grid for each length (lat/lon differ after 10) |
|
59
|
|
|
|
|
|
|
# |
|
60
|
|
|
|
|
|
|
my @LAT; |
|
61
|
|
|
|
|
|
|
my @LON; |
|
62
|
|
|
|
|
|
|
my $m = 20; |
|
63
|
|
|
|
|
|
|
foreach my $i (2,4,6,8,10) { |
|
64
|
|
|
|
|
|
|
$LAT[$i] = $m; |
|
65
|
|
|
|
|
|
|
$LON[$i] = $m; |
|
66
|
|
|
|
|
|
|
$m /= 20; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
foreach my $i (11..16) { |
|
69
|
|
|
|
|
|
|
$LAT[$i] = $LAT[$i-1]/5; |
|
70
|
|
|
|
|
|
|
$LON[$i] = $LON[$i-1]/4; |
|
71
|
|
|
|
|
|
|
} |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head1 FUNCTIONS |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head2 is_valid($code) |
|
76
|
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Returns 1 if $code is a valid short, full, or zero-padded OLC. |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=cut |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub is_valid { |
|
82
|
353
|
|
|
353
|
1
|
68995
|
my ($code) = @_; |
|
83
|
353
|
|
|
|
|
572
|
my $plus = index($code,'+'); |
|
84
|
353
|
100
|
|
|
|
1104
|
return 0 unless grep($_ == $plus,0,2,4,6,8); |
|
85
|
341
|
|
|
|
|
405
|
$code =~ tr/a-z/A-Z/; |
|
86
|
341
|
|
|
|
|
723
|
my ($pre,$post) = split(/\+/,$code,2); |
|
87
|
341
|
100
|
|
|
|
750
|
if (index($code,'0') > -1) { |
|
88
|
29
|
100
|
|
|
|
90
|
return 0 if $post ne ''; |
|
89
|
17
|
|
|
|
|
29
|
$pre =~ tr/0//d; |
|
90
|
|
|
|
|
|
|
} |
|
91
|
329
|
100
|
|
|
|
522
|
if ($pre) { |
|
92
|
313
|
50
|
|
|
|
596
|
return 0 if length($pre) % 2; |
|
93
|
313
|
100
|
|
|
|
1086
|
return 0 if $pre !~ /^[$radix]+$/o; |
|
94
|
|
|
|
|
|
|
} |
|
95
|
323
|
100
|
|
|
|
508
|
if ($post) { |
|
96
|
231
|
100
|
|
|
|
440
|
return 0 if length($post) == 1; |
|
97
|
219
|
100
|
|
|
|
518
|
return 0 if $post !~ /^[$radix]+$/o; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
305
|
100
|
|
|
|
602
|
return 0 if $pre.$post eq ''; |
|
100
|
299
|
|
|
|
|
661
|
return 1; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
=head2 is_short($code) |
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
Returns 1 if $code is a valid shortened code. |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
=cut |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
sub is_short { |
|
110
|
34
|
|
|
34
|
1
|
64
|
my ($code) = @_; |
|
111
|
34
|
100
|
|
|
|
40
|
return 0 unless is_valid($code); |
|
112
|
16
|
|
|
|
|
21
|
return 1 - is_full($code); |
|
113
|
|
|
|
|
|
|
} |
|
114
|
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
=head2 is_full($code) |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
Returns 1 if $code is a valid full-length code, and has lat < 90 |
|
118
|
|
|
|
|
|
|
and lon < 180. |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=cut |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
sub is_full { |
|
123
|
257
|
|
|
257
|
1
|
270
|
my ($code) = @_; |
|
124
|
257
|
100
|
|
|
|
266
|
return 0 unless is_valid($code); |
|
125
|
239
|
100
|
|
|
|
581
|
return 0 unless index($code,'+') == 8; |
|
126
|
|
|
|
|
|
|
# check for lat/lon out of range |
|
127
|
|
|
|
|
|
|
# CVXXXXXX+XXX == 89.999975,179.99996875 |
|
128
|
206
|
50
|
|
|
|
465
|
return 0 if uc(substr($code,0,1)) gt 'C'; |
|
129
|
206
|
50
|
|
|
|
367
|
return 0 if uc(substr($code,1,1)) gt 'V'; |
|
130
|
206
|
|
|
|
|
370
|
return 1; |
|
131
|
|
|
|
|
|
|
} |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
=head2 encode($lat,$lon,[$len]) |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
Encodes a location as OLC. $len can be 2, 4, 6, 8, 10, or 11-16. |
|
136
|
|
|
|
|
|
|
The default $len is 10, which is approximately 13.9x13.9 meters |
|
137
|
|
|
|
|
|
|
at the equator; 11 brings that down to 3.5x2.8 meters, and 12 |
|
138
|
|
|
|
|
|
|
is about 0.9x0.6 meters, so there's not much point in going past |
|
139
|
|
|
|
|
|
|
that. I only go to 16 because there's a test case for the Ruby |
|
140
|
|
|
|
|
|
|
API that uses 15. |
|
141
|
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=cut |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub encode { |
|
145
|
169
|
|
|
169
|
1
|
201
|
my ($lat,$lon,$len) = _norm(@_); |
|
146
|
169
|
|
|
|
|
142
|
my $code; |
|
147
|
169
|
|
|
|
|
225
|
foreach my $i (0..4) { |
|
148
|
845
|
100
|
|
|
|
1123
|
$code .= '+' if $i==4; |
|
149
|
845
|
|
|
|
|
1021
|
my $tmplat = int($lat / $LAT[($i+1)*2]); |
|
150
|
845
|
|
|
|
|
845
|
$code .= $rchar[$tmplat]; |
|
151
|
845
|
|
|
|
|
856
|
$lat -= $tmplat * $LAT[($i+1)*2]; |
|
152
|
845
|
|
|
|
|
926
|
my $tmplon = int($lon / $LON[($i+1)*2]); |
|
153
|
845
|
|
|
|
|
732
|
$code .= $rchar[$tmplon]; |
|
154
|
845
|
|
|
|
|
1196
|
$lon -= $tmplon * $LON[($i+1)*2]; |
|
155
|
|
|
|
|
|
|
} |
|
156
|
169
|
100
|
|
|
|
341
|
if ($len < 10) { |
|
|
|
100
|
|
|
|
|
|
|
157
|
9
|
|
|
|
|
27
|
$code = substr($code,0,$len) . ('0' x (8 - $len)) . '+'; |
|
158
|
|
|
|
|
|
|
}elsif ($len > 10) { |
|
159
|
3
|
|
|
|
|
5
|
foreach my $i (11..$len) { |
|
160
|
9
|
|
|
|
|
11
|
my $gridlat = int($lat / $LAT[$i]); |
|
161
|
9
|
|
|
|
|
11
|
$lat -= $gridlat * $LAT[$i]; |
|
162
|
9
|
|
|
|
|
10
|
my $gridlon = int($lon / $LON[$i]); |
|
163
|
9
|
|
|
|
|
9
|
$lon -= $gridlon * $LON[$i]; |
|
164
|
9
|
|
|
|
|
17
|
$code .= $rchar[$gridlat*4+$gridlon]; |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
} |
|
167
|
169
|
|
|
|
|
306
|
return $code; |
|
168
|
|
|
|
|
|
|
} |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=head2 decode($code) |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
Decodes a valid OLC into its location, returned as three pairs of |
|
173
|
|
|
|
|
|
|
lat/lon coordinates, plus the length of the original code. 'lower' |
|
174
|
|
|
|
|
|
|
and 'upper' are the bounding-box of the OLC grid, and 'center' is |
|
175
|
|
|
|
|
|
|
the target location. |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
$ref = decode('8Q6QMG93+742'); |
|
178
|
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
$ref = { |
|
180
|
|
|
|
|
|
|
lower => [34.668125,135.50275], |
|
181
|
|
|
|
|
|
|
center=> [34.6681375,135.502765625], |
|
182
|
|
|
|
|
|
|
upper => [34.66815,135.50278125], |
|
183
|
|
|
|
|
|
|
length=> 11, |
|
184
|
|
|
|
|
|
|
}; |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
=cut |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
sub decode { |
|
189
|
179
|
|
|
179
|
1
|
3104
|
my ($code) = @_; |
|
190
|
179
|
50
|
|
|
|
209
|
if (!is_full($code)) { |
|
191
|
0
|
|
|
|
|
0
|
warn "decode(): invalid or short code '$code'\n"; |
|
192
|
0
|
|
|
|
|
0
|
return undef; |
|
193
|
|
|
|
|
|
|
} |
|
194
|
179
|
|
|
|
|
201
|
$code =~ tr/a-z/A-Z/; |
|
195
|
179
|
|
|
|
|
182
|
$code =~ tr/+0//d; |
|
196
|
179
|
|
|
|
|
192
|
my ($lat,$lon) = (0,0); |
|
197
|
179
|
|
|
|
|
186
|
my $len = length($code); |
|
198
|
179
|
|
|
|
|
152
|
my $origlen = $len; |
|
199
|
179
|
|
|
|
|
299
|
while ($len > 10) { |
|
200
|
112
|
|
|
|
|
165
|
my $n = $rval{chop($code)}; |
|
201
|
112
|
|
|
|
|
140
|
my $latoffset = int($n/4); |
|
202
|
112
|
|
|
|
|
94
|
my $lonoffset = $n - $latoffset * 4; |
|
203
|
112
|
|
|
|
|
153
|
$lat += $latoffset * $LAT[$len]; |
|
204
|
112
|
|
|
|
|
116
|
$lon += $lonoffset * $LON[$len]; |
|
205
|
112
|
|
|
|
|
226
|
$len -= 1; |
|
206
|
|
|
|
|
|
|
} |
|
207
|
179
|
|
|
|
|
188
|
foreach my $i (2,4,6,8,10) { |
|
208
|
878
|
100
|
|
|
|
1116
|
last if $i > $len; |
|
209
|
822
|
|
|
|
|
933
|
my $latchar = substr($code,$i-2,1); |
|
210
|
822
|
|
|
|
|
820
|
$lat += $rval{$latchar} * $LAT[$i]; |
|
211
|
822
|
|
|
|
|
832
|
my $lonchar = substr($code,$i-1,1); |
|
212
|
822
|
|
|
|
|
1176
|
$lon += $rval{$lonchar} * $LON[$i]; |
|
213
|
|
|
|
|
|
|
} |
|
214
|
179
|
|
|
|
|
158
|
my $latsize = $LAT[$origlen]; |
|
215
|
179
|
|
|
|
|
117
|
my $lonsize = $LON[$origlen]; |
|
216
|
|
|
|
|
|
|
return { |
|
217
|
179
|
|
|
|
|
225
|
lower => [_denorm($lat,$lon)], |
|
218
|
|
|
|
|
|
|
center => [_denorm($lat+$latsize/2,$lon+$lonsize/2)], |
|
219
|
|
|
|
|
|
|
upper => [_denorm($lat+$latsize,$lon+$lonsize)], |
|
220
|
|
|
|
|
|
|
length => $origlen, |
|
221
|
|
|
|
|
|
|
}; |
|
222
|
|
|
|
|
|
|
} |
|
223
|
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
=head2 shorten($code,$latref,$lonref) |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
Shortens a valid full-length code based on the reference location; |
|
227
|
|
|
|
|
|
|
returns the original code if it can't be shortened. |
|
228
|
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
Note that removing 2 or 8 digits is not necessarily practical, since |
|
230
|
|
|
|
|
|
|
there may not be useful names for the area covered, but it's necessary |
|
231
|
|
|
|
|
|
|
for API testing. I recommend using shorten46() instead. |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
=cut |
|
234
|
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
sub shorten { |
|
236
|
11
|
|
|
11
|
1
|
71870
|
return _shorten(@_,8,6,4,2); |
|
237
|
|
|
|
|
|
|
} |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head2 shorten46($code,$latref,$lonref) |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
Shortens a valid full-length code by 4 or 6 digits, based on the |
|
242
|
|
|
|
|
|
|
reference location. |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=cut |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub shorten46 { |
|
247
|
0
|
|
|
0
|
1
|
0
|
return _shorten(@_,6,4); |
|
248
|
|
|
|
|
|
|
} |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# common code used by shorten and shorten46 |
|
251
|
|
|
|
|
|
|
# |
|
252
|
|
|
|
|
|
|
sub _shorten { |
|
253
|
11
|
|
|
11
|
|
23
|
my ($code,$lat,$lon,@lengths) = @_; |
|
254
|
11
|
50
|
|
|
|
16
|
return undef if !is_valid($code); |
|
255
|
11
|
50
|
|
|
|
15
|
return $code if !is_full($code); |
|
256
|
11
|
|
|
|
|
22
|
my $ref = decode($code); |
|
257
|
11
|
|
|
|
|
32
|
($lat,$lon) = _denorm(_norm($lat,$lon,_code_digits($code))); |
|
258
|
|
|
|
|
|
|
my $distance = max(abs($lat - $ref->{center}->[0]), |
|
259
|
11
|
|
|
|
|
53
|
abs($lon - $ref->{center}->[1])); |
|
260
|
11
|
|
|
|
|
20
|
foreach my $dist (@lengths) { |
|
261
|
25
|
100
|
|
|
|
109
|
return substr($code,$dist) if $distance < $LON[$dist] * 0.3; |
|
262
|
|
|
|
|
|
|
} |
|
263
|
0
|
|
|
|
|
0
|
return $code; |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head2 recover_nearest($shortcode,$latref,$lonref) |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Converts a shortened OLC back into a full-length code, using the |
|
270
|
|
|
|
|
|
|
reference location to supply the missing digits. Note that the |
|
271
|
|
|
|
|
|
|
resulting code will not necessarily have the same leading digits |
|
272
|
|
|
|
|
|
|
as the reference location, if it's not in the same grid. |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=cut |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub recover_nearest { |
|
277
|
17
|
|
|
17
|
1
|
72338
|
my ($code,$lat,$lon) = @_; |
|
278
|
17
|
50
|
|
|
|
30
|
if (!is_valid($code)) { |
|
279
|
0
|
|
|
|
|
0
|
warn "recover_nearest(): invalid code '$code'\n"; |
|
280
|
0
|
|
|
|
|
0
|
return undef; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
17
|
50
|
|
|
|
27
|
return $code if is_full($code); |
|
283
|
|
|
|
|
|
|
|
|
284
|
17
|
|
|
|
|
31
|
($lat,$lon) = _denorm(_norm($lat,$lon)); |
|
285
|
17
|
|
|
|
|
44
|
my $removed = 8 - index($code,'+'); |
|
286
|
17
|
|
|
|
|
25
|
my $size = $LAT[$removed]; |
|
287
|
17
|
|
|
|
|
11
|
my $distance = 9999; |
|
288
|
17
|
|
|
|
|
12
|
my $closest = ''; |
|
289
|
17
|
|
|
|
|
34
|
foreach my $latoff (-$size, 0, $size) { |
|
290
|
51
|
|
|
|
|
69
|
foreach my $lonoff (-$size, 0, $size) { |
|
291
|
153
|
|
|
|
|
270
|
my $refcode = encode($lat + $latoff,$lon + $lonoff); |
|
292
|
153
|
|
|
|
|
290
|
my $testcode = substr($refcode,0,$removed) . $code; |
|
293
|
153
|
|
|
|
|
172
|
my $testloc = decode($testcode); |
|
294
|
153
|
|
|
|
|
371
|
my $latdiff = $testloc->{center}->[0] - $lat; |
|
295
|
153
|
|
|
|
|
155
|
my $londiff = $testloc->{center}->[1] - $lon; |
|
296
|
153
|
|
|
|
|
279
|
my $tmpdist = sqrt($latdiff**2 + $londiff**2); |
|
297
|
153
|
100
|
|
|
|
437
|
if ($tmpdist < $distance) { |
|
298
|
61
|
|
|
|
|
52
|
$distance = $tmpdist; |
|
299
|
61
|
|
|
|
|
181
|
$closest = $testcode; |
|
300
|
|
|
|
|
|
|
} |
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
} |
|
303
|
17
|
|
|
|
|
68
|
return $closest; |
|
304
|
|
|
|
|
|
|
} |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
=head2 _code_digits($code) |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
Returns number of non-padded digits in a code; used internally by |
|
309
|
|
|
|
|
|
|
shorten() and shorten46(), and useful for testing. |
|
310
|
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=cut |
|
312
|
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub _code_digits { |
|
314
|
27
|
|
|
27
|
|
68770
|
my ($code) = @_; |
|
315
|
27
|
100
|
|
|
|
80
|
if ($code =~ /(0+)\+$/) { |
|
316
|
9
|
|
|
|
|
31
|
return 8 - length($1); |
|
317
|
|
|
|
|
|
|
}else{ |
|
318
|
18
|
|
|
|
|
53
|
return length($code) - 1; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
# normalize lat/lon for use in encoding |
|
323
|
|
|
|
|
|
|
# |
|
324
|
|
|
|
|
|
|
sub _norm { |
|
325
|
197
|
|
|
197
|
|
225
|
my ($lat,$lon,$len) = @_; |
|
326
|
197
|
|
100
|
|
|
540
|
$len ||= 10; |
|
327
|
197
|
50
|
66
|
|
|
729
|
if ($len <= 10 && $len % 2) { |
|
328
|
0
|
|
|
|
|
0
|
warn "invalid code length '$len', setting to 10\n"; |
|
329
|
0
|
|
|
|
|
0
|
$len = 10; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
197
|
|
|
|
|
456
|
$lat = min(90,max(-90,$lat)); |
|
332
|
197
|
100
|
|
|
|
352
|
$lat -= $LAT[$len] if $lat == 90; |
|
333
|
197
|
|
|
|
|
356
|
while ($lon <= -180) { |
|
334
|
0
|
|
|
|
|
0
|
$lon += 360; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
197
|
|
|
|
|
295
|
while ($lon >= 180) { |
|
337
|
2
|
|
|
|
|
5
|
$lon -= 360; |
|
338
|
|
|
|
|
|
|
} |
|
339
|
197
|
|
|
|
|
431
|
return ($lat+90,$lon+180,$len); |
|
340
|
|
|
|
|
|
|
} |
|
341
|
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
# restore lat/lon to standard range |
|
343
|
|
|
|
|
|
|
# |
|
344
|
|
|
|
|
|
|
sub _denorm { |
|
345
|
565
|
|
|
565
|
|
583
|
my ($lat,$lon) = @_; |
|
346
|
565
|
|
|
|
|
774
|
return (_defloat($lat-90),_defloat($lon-180)); |
|
347
|
|
|
|
|
|
|
} |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# hide floating-point artifacts; cheaper than using bignums |
|
350
|
|
|
|
|
|
|
# |
|
351
|
|
|
|
|
|
|
sub _defloat { |
|
352
|
1130
|
|
|
1130
|
|
945
|
my ($n) = @_; |
|
353
|
1130
|
|
|
|
|
3061
|
$n =~ s/(\.\d+?)(0{4,}[12])$/$1/; |
|
354
|
1130
|
|
|
|
|
3031
|
return $n; |
|
355
|
|
|
|
|
|
|
} |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head1 AUTHOR |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
J Greely, C<< >> |
|
360
|
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=head1 LOCATIONS |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
8Q6QMG93+742 is Tenka Gyoza in Osaka, home of the best one-bite |
|
364
|
|
|
|
|
|
|
gyoza you'll ever devour multiple plates of ("+742 Dotonbori Osaka"). |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
8FW4V75V+ is the Eiffel Tower ("V75V+ Paris"). |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
86HJW8XV+ is Wrigley Field ("W8XV+8Q Chicago"). |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
849VCRVJ+CHV is a pair of ATMs at Stanford Mall, Palo Alto, CA |
|
371
|
|
|
|
|
|
|
("CRVJ+CHV Palo Alto"). |
|
372
|
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
=head1 BUGS |
|
374
|
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
The off-by-one-cell code in recover_nearest() is largely untested, |
|
376
|
|
|
|
|
|
|
because there are no test cases for it. The "XQP5+ in Kyoto" case |
|
377
|
|
|
|
|
|
|
in the synopsis is a simple test I came up with, since most of |
|
378
|
|
|
|
|
|
|
Kyoto is in 8Q7Q0000+, but the station is just across the border |
|
379
|
|
|
|
|
|
|
in 8Q6Q0000+. |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=cut |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
1; |