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; |