line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
=head1 NAME |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
Geo::Google::PolylineEncoder - encode lat/lons to Google Maps Polylines |
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 SYNOPSIS |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
use Geo::Google::PolylineEncoder; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
my $points = [ |
10
|
|
|
|
|
|
|
# can also take points as [lat, lon] |
11
|
|
|
|
|
|
|
{ lat => 38.5, lon => -120.2 }, |
12
|
|
|
|
|
|
|
{ lat => 40.7, lon => -120.95 }, |
13
|
|
|
|
|
|
|
{ lat => 43.252, lon => -126.453 }, |
14
|
|
|
|
|
|
|
]; |
15
|
|
|
|
|
|
|
my $encoder = Geo::Google::PolylineEncoder->new; |
16
|
|
|
|
|
|
|
my $eline = $encoder->encode( $points ); |
17
|
|
|
|
|
|
|
print $eline->{num_levels}; # 18 |
18
|
|
|
|
|
|
|
print $eline->{zoom_factor}; # 2 |
19
|
|
|
|
|
|
|
print $eline->{points}; # _p~iF~ps|U_ulLnnqC_mqNvxq`@ |
20
|
|
|
|
|
|
|
print $eline->{levels}; # POP |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
# in Javascript, assuming eline was encoded as JSON: |
23
|
|
|
|
|
|
|
# ... load GMap2 ... |
24
|
|
|
|
|
|
|
var opts = { |
25
|
|
|
|
|
|
|
points: eline.points, |
26
|
|
|
|
|
|
|
levels: eline.levels, |
27
|
|
|
|
|
|
|
numLevels: eline.num_levels, |
28
|
|
|
|
|
|
|
zoomFactor: eline.zoom_factor, |
29
|
|
|
|
|
|
|
}; |
30
|
|
|
|
|
|
|
var line = GPolyline.fromEncoded( opts ); |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=cut |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
package Geo::Google::PolylineEncoder; |
35
|
|
|
|
|
|
|
|
36
|
3
|
|
|
3
|
|
2804
|
use strict; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
111
|
|
37
|
3
|
|
|
3
|
|
21
|
use warnings; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
147
|
|
38
|
|
|
|
|
|
|
|
39
|
3
|
|
|
|
|
20
|
use accessors qw(num_levels zoom_factor visible_threshold force_endpoints |
40
|
|
|
|
|
|
|
zoom_level_breaks escape_encoded_points lons_first |
41
|
3
|
|
|
3
|
|
2270
|
points dists max_dist encoded_points encoded_levels ); |
|
3
|
|
|
|
|
3425
|
|
42
|
3
|
|
|
|
|
5401
|
use constant defaults => { |
43
|
|
|
|
|
|
|
num_levels => 18, |
44
|
|
|
|
|
|
|
zoom_factor => 2, |
45
|
|
|
|
|
|
|
force_endpoints => 1, |
46
|
|
|
|
|
|
|
escape_encoded_points => 0, |
47
|
|
|
|
|
|
|
visible_threshold => 0.00001, |
48
|
|
|
|
|
|
|
lons_first => 0, |
49
|
3
|
|
|
3
|
|
1066
|
}; |
|
3
|
|
|
|
|
5
|
|
50
|
|
|
|
|
|
|
our $VERSION = 0.06; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# The constructor |
53
|
|
|
|
|
|
|
sub new { |
54
|
10
|
|
|
10
|
1
|
191620
|
my $class = shift; |
55
|
10
|
|
|
|
|
48
|
my $self = bless {}, $class; |
56
|
10
|
|
|
|
|
49
|
$self->init(@_); |
57
|
10
|
|
|
|
|
66
|
return $self; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub init { |
61
|
10
|
|
|
10
|
0
|
37
|
my ($self, %args) = @_; |
62
|
|
|
|
|
|
|
|
63
|
10
|
|
|
|
|
17
|
foreach my $attr (keys %{ $self->defaults }) { |
|
10
|
|
|
|
|
82
|
|
64
|
60
|
|
|
|
|
441
|
$self->$attr($self->defaults->{$attr}); |
65
|
|
|
|
|
|
|
} |
66
|
|
|
|
|
|
|
|
67
|
10
|
|
|
|
|
75
|
foreach my $attr (keys %args) { |
68
|
21
|
|
|
|
|
99
|
$self->$attr($args{$attr}); |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub init_zoom_level_breaks { |
73
|
10
|
|
|
10
|
0
|
30
|
my $self = shift; |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# Cache for performance: |
76
|
10
|
|
|
|
|
28
|
my $num_levels = $self->num_levels; |
77
|
|
|
|
|
|
|
|
78
|
10
|
|
|
|
|
44
|
my @zoom_level_breaks; |
79
|
10
|
|
|
|
|
25
|
for my $i (1 .. $num_levels) { |
80
|
171
|
|
|
|
|
1162
|
push @zoom_level_breaks, |
81
|
|
|
|
|
|
|
$self->visible_threshold * $self->zoom_factor ** ($num_levels - $i); |
82
|
|
|
|
|
|
|
} |
83
|
|
|
|
|
|
|
|
84
|
10
|
|
|
|
|
106
|
$self->zoom_level_breaks(\@zoom_level_breaks); |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub reset_encoder { |
88
|
10
|
|
|
10
|
0
|
18
|
my $self = shift; |
89
|
10
|
|
|
|
|
43
|
$self->points([])->dists([])->max_dist(0)->encoded_points('')->encoded_levels(''); |
90
|
|
|
|
|
|
|
# Note: calculate zoom level breaks here in case num_levels, etc. have |
91
|
|
|
|
|
|
|
# changed between encodes: |
92
|
10
|
|
|
|
|
193
|
$self->init_zoom_level_breaks; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
sub set_points { |
96
|
10
|
|
|
10
|
0
|
91
|
my ($self, $points) = @_; |
97
|
|
|
|
|
|
|
|
98
|
10
|
50
|
|
|
|
58
|
die "points must be an arrayref!" unless UNIVERSAL::isa( $points, 'ARRAY' ); |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# Internally, points are stored as [lat, lon]. Although this is less |
101
|
|
|
|
|
|
|
# readable, it is more efficient than using a hash. |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# Make a copy of the points we were given |
104
|
10
|
|
|
|
|
12
|
my @points; |
105
|
10
|
100
|
|
|
|
510
|
if (UNIVERSAL::isa($points->[0], 'HASH')) { |
|
|
50
|
|
|
|
|
|
106
|
8
|
|
|
|
|
14
|
my @keys = keys %{ $points->[0] }; |
|
8
|
|
|
|
|
32
|
|
107
|
8
|
|
|
|
|
51
|
my ($lat_key) = grep( /^lat$/i, @keys ); |
108
|
8
|
|
|
|
|
47
|
my ($lon_key) = grep( /^(?:lon)|(?:lng)$/i, @keys ); |
109
|
8
|
|
|
|
|
28
|
@points = map { [$_->{$lat_key}, $_->{$lon_key}] } @$points; |
|
1577
|
|
|
|
|
4915
|
|
110
|
|
|
|
|
|
|
} elsif (UNIVERSAL::isa($points->[0], 'ARRAY')) { |
111
|
2
|
100
|
|
|
|
7
|
if ($self->lons_first) { |
112
|
1
|
|
|
|
|
6
|
@points = map {[ $_->[1], $_->[0] ]} @$points; |
|
2
|
|
|
|
|
7
|
|
113
|
|
|
|
|
|
|
} else { |
114
|
1
|
|
|
|
|
7
|
@points = map {[ $_->[0], $_->[1] ]} @$points; |
|
2
|
|
|
|
|
7
|
|
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
} else { |
117
|
0
|
|
|
|
|
0
|
die "don't know how to handle points = $points"; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
10
|
|
|
|
|
87
|
return $self->points( \@points ); |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
0
|
return $self; |
123
|
|
|
|
|
|
|
} |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
# The main entry point |
126
|
|
|
|
|
|
|
sub encode { |
127
|
10
|
|
|
10
|
1
|
10446
|
my ($self, $points) = @_; |
128
|
|
|
|
|
|
|
|
129
|
10
|
|
|
|
|
34
|
$self->reset_encoder |
130
|
|
|
|
|
|
|
->set_points( $points ) |
131
|
|
|
|
|
|
|
->calculate_distances |
132
|
|
|
|
|
|
|
->encode_points |
133
|
|
|
|
|
|
|
->encode_levels; |
134
|
|
|
|
|
|
|
|
135
|
10
|
|
|
|
|
87
|
my $eline = { |
136
|
|
|
|
|
|
|
points => $self->encoded_points, |
137
|
|
|
|
|
|
|
levels => $self->encoded_levels, |
138
|
|
|
|
|
|
|
num_levels => $self->num_levels, |
139
|
|
|
|
|
|
|
zoom_factor => $self->zoom_factor, |
140
|
|
|
|
|
|
|
}; |
141
|
|
|
|
|
|
|
|
142
|
10
|
50
|
|
|
|
155
|
if ($self->escape_encoded_points) { |
143
|
|
|
|
|
|
|
# create string literals: |
144
|
0
|
|
|
|
|
0
|
$eline->{points} =~ s/\\/\\\\/g; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
10
|
|
|
|
|
69
|
return $eline; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
# The main function. Essentially the Douglas-Peucker algorithm, adapted for |
151
|
|
|
|
|
|
|
# encoding. Rather than simply eliminating points, we record their distance |
152
|
|
|
|
|
|
|
# from the segment which occurs at that recursive step. These distances are |
153
|
|
|
|
|
|
|
# then easily converted to zoom levels. |
154
|
|
|
|
|
|
|
# |
155
|
|
|
|
|
|
|
# Note: this function has been optimized and is quite long, sorry. |
156
|
|
|
|
|
|
|
# Any further optimizations should probably be done in XS. |
157
|
|
|
|
|
|
|
sub calculate_distances { |
158
|
10
|
|
|
10
|
0
|
108
|
my $self = shift; |
159
|
10
|
|
|
|
|
645
|
my $points = $self->points; |
160
|
|
|
|
|
|
|
|
161
|
10
|
|
|
|
|
41
|
my @dists; |
162
|
10
|
|
|
|
|
15
|
my $max_dist = 0; |
163
|
|
|
|
|
|
|
|
164
|
10
|
100
|
|
|
|
32
|
if (@$points <= 2) { |
165
|
|
|
|
|
|
|
# no point doing distance calcs: |
166
|
3
|
|
|
|
|
9
|
return $self->dists( \@dists )->max_dist( $max_dist ); |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# cache commonly used vars: |
170
|
7
|
|
|
|
|
19
|
my $visible_threshold = $self->visible_threshold; |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# Iterate through all the points, and calculate their dists |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# Each stack element contains the index of two points representing a line |
175
|
|
|
|
|
|
|
# seg that we calculate distances from. Start off with the first & last pt: |
176
|
7
|
|
|
|
|
43
|
my @stack = ([0, @$points - 1]); |
177
|
|
|
|
|
|
|
|
178
|
7
|
|
|
|
|
30
|
while (@stack > 0) { |
179
|
2375
|
|
|
|
|
3005
|
my $current = pop @stack; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# cache to save array lookups: |
182
|
2375
|
|
|
|
|
3098
|
my $current_0 = $current->[0]; |
183
|
2375
|
|
|
|
|
2501
|
my $current_1 = $current->[1]; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
# Get the two points, $A & $B: |
186
|
2375
|
|
|
|
|
3302
|
my ($A, $B) = ($points->[$current_0], $points->[$current_1]); |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
# Cache their lon/lats to avoid unneccessary array lookups. |
189
|
|
|
|
|
|
|
# Note: we use X/Y because it's shorter, and more math-y |
190
|
2375
|
|
|
|
|
3935
|
my ($Ax, $Ay, $Bx, $By) = ($A->[1], $A->[0], $B->[1], $B->[0]); |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# Create a line segment between $A & $B and calculate its length |
193
|
|
|
|
|
|
|
# Note: cache the square of the seg length for use in calcs later... |
194
|
2375
|
|
|
|
|
6858
|
my $seg_length_squared = (($Bx - $Ax) ** 2 + ($By - $Ay) ** 2); |
195
|
2375
|
|
|
|
|
2862
|
my $seg_length = sqrt($seg_length_squared); |
196
|
2375
|
|
|
|
|
3228
|
my $seg_length_is_0 = $seg_length == 0; # cache |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Cache the deltas in x/y for calcs later: |
199
|
2375
|
|
|
|
|
3298
|
my ($Bx_minus_Ax, $By_minus_Ay) = ($Bx - $Ax, $By - $Ay); |
200
|
|
|
|
|
|
|
|
201
|
2375
|
|
|
|
|
2366
|
my $current_max_dist = 0; |
202
|
2375
|
|
|
|
|
2236
|
my $current_max_dist_idx; |
203
|
2375
|
|
|
|
|
5423
|
for (my $i = $current_0 + 1; $i < $current_1; $i++) { |
204
|
|
|
|
|
|
|
# Get the current point: |
205
|
16721
|
|
|
|
|
22251
|
my $P = $points->[$i]; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# Cache its lon/lat to avoid unneccessary hash lookups. |
208
|
|
|
|
|
|
|
# Note: we use X/Y because it's shorter, and more math-y |
209
|
16721
|
|
|
|
|
32782
|
my ($Py, $Px) = ($P->[0], $P->[1]); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Compute the distance between point $P and line segment [$A, $B]. |
212
|
|
|
|
|
|
|
# Maths borrowed from Philip Nicoletti (see below). |
213
|
|
|
|
|
|
|
# |
214
|
|
|
|
|
|
|
# Note: we approximate distance using flat (Euclidian) geometry, |
215
|
|
|
|
|
|
|
# rather than trying to bring the curvature of the earth into it. |
216
|
|
|
|
|
|
|
# This greatly simplifies things, and makes the calcs faster... |
217
|
|
|
|
|
|
|
# |
218
|
|
|
|
|
|
|
# Note: distance calculations have been brought in-line as the |
219
|
|
|
|
|
|
|
# majority of encoding time was spent calling the 'distance' |
220
|
|
|
|
|
|
|
# method. This way we can avoid passing lots of data by value, |
221
|
|
|
|
|
|
|
# setting up the sub stack, and we can also cache some values. |
222
|
|
|
|
|
|
|
#my $dist = $self->distance($points->[$i], $A, $B, $seg_length, $seg_length_squared); |
223
|
|
|
|
|
|
|
|
224
|
16721
|
|
|
|
|
16406
|
my $dist; |
225
|
16721
|
50
|
|
|
|
22919
|
if ($seg_length_is_0) { |
226
|
|
|
|
|
|
|
# The line is really just a point, so calc dist between it and $P: |
227
|
0
|
|
|
|
|
0
|
$dist = sqrt(($By - $Py) ** 2 + ($Bx - $Px) ** 2); |
228
|
|
|
|
|
|
|
} else { |
229
|
|
|
|
|
|
|
# Thanks to Philip Nicoletti's explanation: |
230
|
|
|
|
|
|
|
# http://www.codeguru.com/forum/printthread.php?t=194400 |
231
|
|
|
|
|
|
|
# |
232
|
|
|
|
|
|
|
# So, to find out how far the line segment (AB) is from the point (P), |
233
|
|
|
|
|
|
|
# let 'I' be the point of perpendicular projection of P on AB. The |
234
|
|
|
|
|
|
|
# parameter 'r' indicates I's position along AB, and is computed by |
235
|
|
|
|
|
|
|
# the dot product of AP and AB divided by the square of the length |
236
|
|
|
|
|
|
|
# of AB: |
237
|
|
|
|
|
|
|
# |
238
|
|
|
|
|
|
|
# AP . AB (Px-Ax)(Bx-Ax) + (Py-Ay)(By-Ay) |
239
|
|
|
|
|
|
|
# r = -------- = ------------------------------- |
240
|
|
|
|
|
|
|
# ||AB||^2 L^2 |
241
|
|
|
|
|
|
|
# |
242
|
|
|
|
|
|
|
# r can be interpreded ala: |
243
|
|
|
|
|
|
|
# |
244
|
|
|
|
|
|
|
# r=0 I = A |
245
|
|
|
|
|
|
|
# r=1 I = B |
246
|
|
|
|
|
|
|
# r<0 I is on the backward extension of A-B |
247
|
|
|
|
|
|
|
# r>1 I is on the forward extension of A-B |
248
|
|
|
|
|
|
|
# 0
|
249
|
|
|
|
|
|
|
# |
250
|
|
|
|
|
|
|
# In cases 1-4 we can simply use the distance between P and either A or B. |
251
|
|
|
|
|
|
|
# In case 5 we can use the distance between I and P. To do that we need to |
252
|
|
|
|
|
|
|
# find I: |
253
|
|
|
|
|
|
|
# |
254
|
|
|
|
|
|
|
# Ix = Ax + r(Bx-Ax) |
255
|
|
|
|
|
|
|
# Iy = Ay + r(By-Ay) |
256
|
|
|
|
|
|
|
# |
257
|
|
|
|
|
|
|
# And the distance from A to I = r*L. |
258
|
|
|
|
|
|
|
# Use another parameter s to indicate the location along IP, with the |
259
|
|
|
|
|
|
|
# following meaning: |
260
|
|
|
|
|
|
|
# s<0 P is left of AB |
261
|
|
|
|
|
|
|
# s>0 P is right of AB |
262
|
|
|
|
|
|
|
# s=0 P is on AB |
263
|
|
|
|
|
|
|
# |
264
|
|
|
|
|
|
|
# Compute s as follows: |
265
|
|
|
|
|
|
|
# |
266
|
|
|
|
|
|
|
# (Ay-Py)(Bx-Ax) - (Ax-Px)(By-Ay) |
267
|
|
|
|
|
|
|
# s = ------------------------------- |
268
|
|
|
|
|
|
|
# L^2 |
269
|
|
|
|
|
|
|
# |
270
|
|
|
|
|
|
|
# Then the distance from P to I = |s|*L. |
271
|
|
|
|
|
|
|
|
272
|
16721
|
|
|
|
|
40764
|
my $r = (($Px - $Ax) * ($Bx - $Ax) + |
273
|
|
|
|
|
|
|
($Py - $Ay) * ($By - $Ay)) / $seg_length_squared; |
274
|
|
|
|
|
|
|
|
275
|
16721
|
100
|
|
|
|
34731
|
if ($r <= 0.0) { |
|
|
100
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# Either I = A, or I is on the backward extension of A-B, |
277
|
|
|
|
|
|
|
# so find dist between $P & $A: |
278
|
4
|
|
|
|
|
16
|
$dist = sqrt(($Ay - $Py) ** 2 + ($Ax - $Px) ** 2); |
279
|
|
|
|
|
|
|
} elsif ($r >= 1.0) { |
280
|
|
|
|
|
|
|
# Either I = B, or I is on the forward extension of A-B, |
281
|
|
|
|
|
|
|
# so find dist between $P & $B: |
282
|
116
|
|
|
|
|
220
|
$dist = sqrt(($By - $Py) ** 2 + ($Bx - $Px) ** 2); |
283
|
|
|
|
|
|
|
} else { |
284
|
|
|
|
|
|
|
# I is interior to A-B, so find $s, and use it to find the |
285
|
|
|
|
|
|
|
# dist between $P and A-B: |
286
|
16601
|
|
|
|
|
26115
|
my $s = (($Ay - $Py) * $Bx_minus_Ax - |
287
|
|
|
|
|
|
|
($Ax - $Px) * $By_minus_Ay) / $seg_length_squared; |
288
|
16601
|
|
|
|
|
24471
|
$dist = abs($s) * $seg_length; |
289
|
|
|
|
|
|
|
} |
290
|
|
|
|
|
|
|
# warn "\t$Px\t$Py\t$Ax\t$Ay\t$Bx\t$By\t$r\t$dist\n"; |
291
|
|
|
|
|
|
|
} |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
# See if this distance is the greatest for this segment so far: |
294
|
16721
|
100
|
|
|
|
43004
|
if ($dist > $current_max_dist) { |
295
|
5388
|
|
|
|
|
5659
|
$current_max_dist = $dist; |
296
|
5388
|
|
|
|
|
5291
|
$current_max_dist_idx = $i; |
297
|
5388
|
100
|
|
|
|
16019
|
if ($current_max_dist > $max_dist) { |
298
|
289
|
|
|
|
|
712
|
$max_dist = $current_max_dist; |
299
|
|
|
|
|
|
|
} |
300
|
|
|
|
|
|
|
} |
301
|
|
|
|
|
|
|
} |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
# If the point that had the greatest distance from the line seg is |
304
|
|
|
|
|
|
|
# also greater than our threshold, process again using it as a new |
305
|
|
|
|
|
|
|
# start/end point for the line. |
306
|
2375
|
100
|
|
|
|
6274
|
if ($current_max_dist > $visible_threshold) { |
307
|
|
|
|
|
|
|
# store this distance - we'll use it later when creating zoom values |
308
|
1184
|
|
|
|
|
1838
|
$dists[$current_max_dist_idx] = $current_max_dist; |
309
|
1184
|
|
|
|
|
2248
|
push @stack, [$current_0, $current_max_dist_idx]; |
310
|
1184
|
|
|
|
|
4795
|
push @stack, [$current_max_dist_idx, $current_1]; |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
|
314
|
7
|
|
|
|
|
52
|
$self->dists( \@dists )->max_dist( $max_dist ); |
315
|
|
|
|
|
|
|
} # calculate_distances |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
# The encode_points function is very similar to Google's |
319
|
|
|
|
|
|
|
# http://www.google.com/apis/maps/documentation/polyline.js |
320
|
|
|
|
|
|
|
# The key difference is that not all points are encoded, |
321
|
|
|
|
|
|
|
# since some were eliminated by Douglas-Peucker. |
322
|
|
|
|
|
|
|
sub encode_points { |
323
|
10
|
|
|
10
|
0
|
113
|
my $self = shift; |
324
|
10
|
|
|
|
|
32
|
my $points = $self->points; |
325
|
10
|
|
|
|
|
60
|
my $dists = $self->dists; |
326
|
|
|
|
|
|
|
|
327
|
10
|
|
|
|
|
36
|
my $encoded_points = ""; |
328
|
10
|
|
|
|
|
17
|
my $oldencoded_points = ""; |
329
|
10
|
|
|
|
|
17
|
my ($last_lat, $last_lon) = (0.0, 0.0); |
330
|
|
|
|
|
|
|
|
331
|
10
|
|
|
|
|
54
|
for (my $i = 0; $i < @$points; $i++) { |
332
|
1581
|
|
|
|
|
1929
|
my $point = $points->[$i]; |
333
|
1581
|
|
|
|
|
1886
|
my $lat = $point->[0]; |
334
|
1581
|
|
|
|
|
1674
|
my $lon = $point->[1]; |
335
|
|
|
|
|
|
|
|
336
|
1581
|
100
|
100
|
|
|
5561
|
if (defined($dists->[$i]) || $i == 0 || $i == @$points - 1) { |
|
|
|
100
|
|
|
|
|
337
|
|
|
|
|
|
|
# compute deltas, rounded to 5 decimal places: |
338
|
1204
|
|
|
|
|
5193
|
my $lat_e5 = sprintf('%.5f', $lat)+0; # round() |
339
|
1204
|
|
|
|
|
4321
|
my $lon_e5 = sprintf('%.5f', $lon)+0; # round() |
340
|
1204
|
|
|
|
|
3870
|
my $delta_lat = sprintf('%.5f', $lat_e5 - $last_lat)+0; |
341
|
1204
|
|
|
|
|
3652
|
my $delta_lon = sprintf('%.5f', $lon_e5 - $last_lon)+0; |
342
|
1204
|
|
|
|
|
1403
|
($last_lat, $last_lon) = ($lat_e5, $lon_e5); |
343
|
|
|
|
|
|
|
|
344
|
1204
|
|
|
|
|
2246
|
$encoded_points .= |
345
|
|
|
|
|
|
|
$self->encode_signed_number($delta_lat) . |
346
|
|
|
|
|
|
|
$self->encode_signed_number($delta_lon); |
347
|
|
|
|
|
|
|
} else { |
348
|
|
|
|
|
|
|
# warn "skipping point: $lat, $lon"; |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
} |
351
|
|
|
|
|
|
|
|
352
|
10
|
|
|
|
|
43
|
$self->encoded_points( $encoded_points ); |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# Use compute_level to march down the list of points and encode the levels. |
357
|
|
|
|
|
|
|
# Like encode_points, we ignore points whose distance (in dists) is undefined. |
358
|
|
|
|
|
|
|
# See http://code.google.com/apis/maps/documentation/polylinealgorithm.html |
359
|
|
|
|
|
|
|
sub encode_levels { |
360
|
10
|
|
|
10
|
0
|
91
|
my $self = shift; |
361
|
10
|
|
|
|
|
30
|
my $points = $self->points; |
362
|
10
|
|
|
|
|
49
|
my $dists = $self->dists; |
363
|
10
|
|
|
|
|
53
|
my $max_dist = $self->max_dist; |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
# Cache for performance: |
366
|
10
|
|
|
|
|
52
|
my $num_levels = $self->num_levels; |
367
|
10
|
|
|
|
|
42
|
my $num_levels_minus_1 = $num_levels - 1; |
368
|
10
|
|
|
|
|
25
|
my $visible_threshold = $self->visible_threshold; |
369
|
10
|
|
|
|
|
54
|
my $zoom_level_breaks = $self->zoom_level_breaks; |
370
|
|
|
|
|
|
|
|
371
|
10
|
|
|
|
|
38
|
my $encoded_levels = ""; |
372
|
|
|
|
|
|
|
|
373
|
10
|
50
|
|
|
|
27
|
if ($self->force_endpoints) { |
374
|
10
|
|
|
|
|
91
|
$encoded_levels .= $self->encode_number($num_levels_minus_1); |
375
|
|
|
|
|
|
|
} else { |
376
|
0
|
|
|
|
|
0
|
$encoded_levels .= $self->encode_number($num_levels_minus_1 - $self->compute_level($max_dist)); |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
# Note: skip the first & last point: |
381
|
10
|
|
|
|
|
32
|
for my $i (1 .. scalar(@$points) - 2) { |
382
|
1561
|
|
|
|
|
1889
|
my $dist = $dists->[$i]; |
383
|
1561
|
100
|
|
|
|
2445
|
if (defined $dist) { |
384
|
|
|
|
|
|
|
# Note: brought compute_level in-line as it was performing *really* slowly |
385
|
|
|
|
|
|
|
# |
386
|
|
|
|
|
|
|
# This computes the appropriate zoom level of a point in terms of it's |
387
|
|
|
|
|
|
|
# distance from the relevant segment in the DP algorithm. Could be done |
388
|
|
|
|
|
|
|
# in terms of a logarithm, but this approach makes it a bit easier to |
389
|
|
|
|
|
|
|
# ensure that the level is not too large. |
390
|
|
|
|
|
|
|
#my $level = $self->compute_level($dist); |
391
|
1184
|
|
|
|
|
1511
|
my $level = 0; |
392
|
1184
|
50
|
|
|
|
1916
|
if ($dist > $visible_threshold) { |
393
|
1184
|
|
|
|
|
1957
|
while ($dist < $zoom_level_breaks->[$level]) { |
394
|
12431
|
|
|
|
|
18702
|
$level++; |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
1184
|
|
|
|
|
2189
|
$encoded_levels .= $self->encode_number($num_levels_minus_1 - $level); |
399
|
|
|
|
|
|
|
} |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
|
402
|
10
|
50
|
|
|
|
41
|
if ($self->force_endpoints) { |
403
|
10
|
|
|
|
|
66
|
$encoded_levels .= $self->encode_number($num_levels_minus_1); |
404
|
|
|
|
|
|
|
} else { |
405
|
0
|
|
|
|
|
0
|
$encoded_levels .= $self->encode_number($num_levels_minus_1 - $self->compute_level($max_dist)); |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
|
408
|
10
|
|
|
|
|
34
|
$self->encoded_levels( $encoded_levels ); |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# This computes the appropriate zoom level of a point in terms of it's |
413
|
|
|
|
|
|
|
# distance from the relevant segment in the DP algorithm. Could be done |
414
|
|
|
|
|
|
|
# in terms of a logarithm, but this approach makes it a bit easier to |
415
|
|
|
|
|
|
|
# ensure that the level is not too large. |
416
|
|
|
|
|
|
|
sub compute_level { |
417
|
0
|
|
|
0
|
0
|
0
|
my ($self, $dist) = @_; |
418
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
# Cache for performance: |
420
|
0
|
|
|
|
|
0
|
my $zoom_level_breaks = $self->zoom_level_breaks; |
421
|
|
|
|
|
|
|
|
422
|
0
|
|
|
|
|
0
|
my $level; |
423
|
0
|
0
|
|
|
|
0
|
if ($dist > $self->visible_threshold) { |
424
|
0
|
|
|
|
|
0
|
$level = 0; |
425
|
0
|
|
|
|
|
0
|
while ($dist < $zoom_level_breaks->[$level]) { |
426
|
0
|
|
|
|
|
0
|
$level++; |
427
|
|
|
|
|
|
|
} |
428
|
|
|
|
|
|
|
} |
429
|
|
|
|
|
|
|
|
430
|
0
|
|
|
|
|
0
|
return $level; |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
# Based on the official google example |
434
|
|
|
|
|
|
|
# http://code.google.com/apis/maps/documentation/include/polyline.js |
435
|
|
|
|
|
|
|
sub encode_signed_number { |
436
|
2412
|
|
|
2412
|
0
|
3055
|
my ($self, $orig_num) = @_; |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
# 1. Take the initial signed value: |
439
|
|
|
|
|
|
|
# 2. Take the decimal value and multiply it by 1e5, rounding the result: |
440
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
# Note 1: we limit the number to 5 decimal places with sprintf to avoid |
442
|
|
|
|
|
|
|
# perl's rounding errors (they can throw the line off by a big margin sometimes) |
443
|
|
|
|
|
|
|
# From Geo::Google: use the correct floating point precision or else |
444
|
|
|
|
|
|
|
# 34.06694 - 34.06698 will give you -3.999999999999999057E-5 which doesn't |
445
|
|
|
|
|
|
|
# encode properly. -4E-5 encodes properly. |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
# Note 2: we use sprintf(%.0f ...) rather than int() for similar reasons |
448
|
|
|
|
|
|
|
# (see perldoc -f int), though there's not much in it and the sprintf approach |
449
|
|
|
|
|
|
|
# ends up doing more of a round() than a floor() in some cases: |
450
|
|
|
|
|
|
|
# floor = -30 num=-30 *int=-29 1e5=-30 %3.5f=-0.00030 orig=-0.000300000000009959 |
451
|
|
|
|
|
|
|
# floor = 119 *num=120 int=119 1e5=120 %3.5f=0.00120 orig=0.0011999999999972 |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# Note 3: We don't use floor() to avoid a dependency on POSIX. And it |
454
|
|
|
|
|
|
|
# doesn't round() anyway. |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
# do this in a series of steps so we can see what's going on in the debugger: |
457
|
2412
|
|
|
|
|
7911
|
my $num3_5 = sprintf('%.5f', $orig_num)+0; # round at 5 decimal places |
458
|
2412
|
|
|
|
|
2836
|
my $num_1e5 = $num3_5 * 1e5; |
459
|
2412
|
|
|
|
|
3636
|
my $num = sprintf('%.0f', $num_1e5)+0; # think int(...) |
460
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
# RT 49327: the signedness has to be determined *after* rounding |
462
|
2412
|
|
|
|
|
2663
|
my $is_negative = $num < 0; |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
{ |
465
|
|
|
|
|
|
|
# 3. Convert the decimal value to binary. Note that a negative value |
466
|
|
|
|
|
|
|
# must be calculated using its two's complement by inverting the |
467
|
|
|
|
|
|
|
# binary value and adding one to the result. |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
# Note: perl ints are already binary, but bitwise operators work on |
470
|
|
|
|
|
|
|
# the assumption they are unsigned, ie ~$num => one's complement. |
471
|
|
|
|
|
|
|
# if we 'use integer' bitwise operands are treated as signed: |
472
|
3
|
|
|
3
|
|
2553
|
use integer; # force 2's complement |
|
3
|
|
|
|
|
28
|
|
|
3
|
|
|
|
|
16
|
|
|
2412
|
|
|
|
|
2075
|
|
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
# 4. Left-shift the binary value one bit: |
475
|
2412
|
|
|
|
|
2141
|
$num = $num << 1; |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
# 5. If the original decimal value is negative, invert this encoding: |
478
|
|
|
|
|
|
|
# (see note on RT 49327 above) |
479
|
2412
|
100
|
|
|
|
4282
|
if ($is_negative) { |
480
|
626
|
|
|
|
|
765
|
$num = ~$num; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
} |
483
|
|
|
|
|
|
|
|
484
|
2412
|
|
|
|
|
4055
|
return $self->encode_number($num); |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
# Based on the official google example |
488
|
|
|
|
|
|
|
# http://code.google.com/apis/maps/documentation/include/polyline.js |
489
|
|
|
|
|
|
|
sub encode_number { |
490
|
3618
|
|
|
3618
|
0
|
3940
|
my ($self, $num) = @_; |
491
|
3
|
|
|
3
|
|
234
|
no integer; # treat bitwise operands as unsigned |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
11
|
|
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
# 6. Break the binary value out into 5-bit chunks (starting from the right hand side): |
494
|
|
|
|
|
|
|
# 7. Place the 5-bit chunks into reverse order: |
495
|
|
|
|
|
|
|
# 8. OR each value with 0x20 if another bit chunk follows: |
496
|
|
|
|
|
|
|
# 9. Convert each value to decimal: |
497
|
|
|
|
|
|
|
# 10. Add 63 to each value: |
498
|
|
|
|
|
|
|
|
499
|
3618
|
|
|
|
|
3684
|
my $encodeString = ""; |
500
|
3618
|
|
|
|
|
6371
|
while ($num >= 0x20) { |
501
|
1974
|
|
|
|
|
2296
|
my $nextValue = (0x20 | ($num & 0x1f)) + 63; |
502
|
1974
|
|
|
|
|
2246
|
$encodeString .= chr( $nextValue ); |
503
|
1974
|
|
|
|
|
3580
|
$num >>= 5; |
504
|
|
|
|
|
|
|
} |
505
|
|
|
|
|
|
|
|
506
|
3618
|
|
|
|
|
3572
|
my $finalValue = $num + 63; |
507
|
3618
|
|
|
|
|
3760
|
$encodeString .= chr( $finalValue ); |
508
|
|
|
|
|
|
|
|
509
|
3618
|
|
|
|
|
11125
|
return $encodeString; |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
# Superficial validation of encoded points. Note that decode_points |
513
|
|
|
|
|
|
|
# does not check that points are validated before decoding. |
514
|
|
|
|
|
|
|
sub validate_encoded_points { |
515
|
6
|
|
|
6
|
0
|
6053
|
my ($class, $encoded) = @_; |
516
|
|
|
|
|
|
|
|
517
|
6
|
50
|
33
|
|
|
36
|
return unless (defined $encoded && $encoded ne ""); |
518
|
|
|
|
|
|
|
|
519
|
6
|
|
|
|
|
51
|
my @ords = unpack "c*", $encoded; |
520
|
|
|
|
|
|
|
|
521
|
6
|
50
|
|
|
|
15
|
my @out = grep { $_ < 63 || $_ > 127 } @ords; |
|
180
|
|
|
|
|
626
|
|
522
|
6
|
50
|
|
|
|
16
|
return if @out; |
523
|
|
|
|
|
|
|
|
524
|
6
|
|
|
|
|
35
|
return 1; |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
|
|
|
|
|
|
# Decode an encoded polyline into a list of lat/lng tuples. |
528
|
|
|
|
|
|
|
# adapted from http://code.google.com/apis/maps/documentation/include/polyline.js |
529
|
|
|
|
|
|
|
sub decode_points { |
530
|
12
|
|
|
12
|
1
|
16921
|
my ($class, $encoded) = @_; |
531
|
|
|
|
|
|
|
|
532
|
12
|
|
|
|
|
23
|
my $len = length( $encoded ); |
533
|
12
|
|
|
|
|
21
|
my @array; |
534
|
|
|
|
|
|
|
|
535
|
12
|
|
|
|
|
14
|
my $index = 0; |
536
|
12
|
|
|
|
|
17
|
my $lat = 0; |
537
|
12
|
|
|
|
|
16
|
my $lon = 0; |
538
|
|
|
|
|
|
|
|
539
|
12
|
|
|
|
|
35
|
while ($index < $len) { |
540
|
|
|
|
|
|
|
{ |
541
|
2339
|
|
|
|
|
2330
|
my $b; |
|
2339
|
|
|
|
|
2184
|
|
542
|
2339
|
|
|
|
|
2442
|
my $shift = 0; |
543
|
2339
|
|
|
|
|
2450
|
my $result = 0; |
544
|
2339
|
|
|
|
|
2124
|
do { |
545
|
4194
|
|
|
|
|
4909
|
$b = ord( substr( $encoded, $index++, 1 ) ) - 63; |
546
|
4194
|
|
|
|
|
4456
|
$result |= ($b & 0x1f) << $shift; |
547
|
4194
|
|
|
|
|
7741
|
$shift += 5; |
548
|
|
|
|
|
|
|
} while ($b >= 0x20); |
549
|
2339
|
|
|
|
|
2470
|
my $dlat = $result >> 1; |
550
|
2339
|
100
|
|
|
|
3833
|
if ($result & 1) { |
551
|
3
|
|
|
3
|
|
847
|
use integer; # force 2's complement |
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
11
|
|
552
|
706
|
|
|
|
|
788
|
$dlat = ~$dlat; |
553
|
|
|
|
|
|
|
} |
554
|
2339
|
|
|
|
|
2281
|
$lat += $dlat; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
# cut-n-paste to improve performance? |
557
|
2339
|
|
|
|
|
2229
|
$shift = 0; |
558
|
2339
|
|
|
|
|
2276
|
$result = 0; |
559
|
2339
|
|
|
|
|
2081
|
do { |
560
|
4248
|
|
|
|
|
4687
|
$b = ord( substr( $encoded, $index++, 1 ) ) - 63; |
561
|
4248
|
|
|
|
|
4485
|
$result |= ($b & 0x1f) << $shift; |
562
|
4248
|
|
|
|
|
7621
|
$shift += 5; |
563
|
|
|
|
|
|
|
} while ($b >= 0x20); |
564
|
2339
|
|
|
|
|
2400
|
my $dlon = $result >> 1; |
565
|
2339
|
100
|
|
|
|
4370
|
if ($result & 1) { |
566
|
3
|
|
|
3
|
|
248
|
use integer; # force 2's complement |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
17
|
|
567
|
498
|
|
|
|
|
591
|
$dlon = ~$dlon; |
568
|
|
|
|
|
|
|
} |
569
|
2339
|
|
|
|
|
2838
|
$lon += $dlon; |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
2339
|
|
|
|
|
9468
|
push @array, { lat => $lat * 1e-5, lon => $lon * 1e-5 }; |
573
|
|
|
|
|
|
|
} |
574
|
|
|
|
|
|
|
|
575
|
12
|
|
|
|
|
266
|
return \@array; |
576
|
|
|
|
|
|
|
} |
577
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
# Decode an encoded levels string into a list of levels. |
579
|
|
|
|
|
|
|
# adapted from http://code.google.com/apis/maps/documentation/include/polyline.js |
580
|
|
|
|
|
|
|
sub decode_levels { |
581
|
10
|
|
|
10
|
1
|
83
|
my ($class, $encoded) = @_; |
582
|
|
|
|
|
|
|
|
583
|
10
|
|
|
|
|
18
|
my $len = length( $encoded ); |
584
|
10
|
|
|
|
|
19
|
my @levels; |
585
|
|
|
|
|
|
|
|
586
|
10
|
|
|
|
|
31
|
for (my $index = 0; $index < $len; $index++) { |
587
|
2317
|
|
|
|
|
2459
|
my $level = ord( substr( $encoded, $index, 1 ) ) - 63; |
588
|
2317
|
|
|
|
|
4478
|
push @levels, $level; |
589
|
|
|
|
|
|
|
} |
590
|
|
|
|
|
|
|
|
591
|
10
|
|
|
|
|
36
|
return \@levels; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
1; |
596
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
__END__ |