line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# -*- perl -*- |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# |
4
|
|
|
|
|
|
|
# Author: Slaven Rezic |
5
|
|
|
|
|
|
|
# |
6
|
|
|
|
|
|
|
# Copyright (C) 2009,2010,2012,2017 Slaven Rezic. All rights reserved. |
7
|
|
|
|
|
|
|
# This package is free software; you can redistribute it and/or |
8
|
|
|
|
|
|
|
# modify it under the same terms as Perl itself. |
9
|
|
|
|
|
|
|
# |
10
|
|
|
|
|
|
|
# Mail: slaven@rezic.de |
11
|
|
|
|
|
|
|
# WWW: http://www.rezic.de/eserte/ |
12
|
|
|
|
|
|
|
# |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
package Algorithm::GooglePolylineEncoding; |
15
|
|
|
|
|
|
|
|
16
|
1
|
|
|
1
|
|
804
|
use 5.006; # sprintf("%b") |
|
1
|
|
|
|
|
4
|
|
17
|
|
|
|
|
|
|
|
18
|
1
|
|
|
1
|
|
5
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
20
|
|
19
|
1
|
|
|
1
|
|
4
|
use vars qw($VERSION); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
524
|
|
20
|
|
|
|
|
|
|
$VERSION = '0.05'; |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
sub encode_number { |
23
|
|
|
|
|
|
|
# 1. Take the initial signed value: |
24
|
|
|
|
|
|
|
# -179.9832104 |
25
|
48
|
|
|
48
|
1
|
81
|
my $number = shift; |
26
|
|
|
|
|
|
|
# 2. Take the decimal value and multiply it by 1e5, rounding the result: |
27
|
|
|
|
|
|
|
# -17998321 |
28
|
48
|
100
|
|
|
|
113
|
$number = int($number * 1e5 + ($number < 0 ? -0.5 : 0.5)); |
29
|
|
|
|
|
|
|
# Don't do this before rounding. Negativeness may change if for example |
30
|
|
|
|
|
|
|
# using very small negative numbers. |
31
|
48
|
|
|
|
|
81
|
my $is_negative = $number < 0; |
32
|
|
|
|
|
|
|
# 3. Convert the decimal value to binary. Note that a negative value must be calculated using its two's complement by inverting the binary value and adding one to the result: |
33
|
|
|
|
|
|
|
# 00000001 00010010 10100001 11110001 |
34
|
|
|
|
|
|
|
# 11111110 11101101 01011110 00001110 |
35
|
|
|
|
|
|
|
# 11111110 11101101 01011110 00001111 |
36
|
|
|
|
|
|
|
# nothing to do here, we don't calculate with binary strings... |
37
|
|
|
|
|
|
|
# 4. Left-shift the binary value one bit: |
38
|
|
|
|
|
|
|
# 11111101 11011010 10111100 00011110 |
39
|
48
|
|
|
|
|
71
|
$number <<= 1; |
40
|
48
|
|
|
|
|
84
|
$number &= 0xffffffff; # to assure 32 bit |
41
|
|
|
|
|
|
|
# 5. If the original decimal value is negative, invert this encoding: |
42
|
|
|
|
|
|
|
# 00000010 00100101 01000011 11100001 |
43
|
48
|
100
|
|
|
|
104
|
if ($is_negative) { |
44
|
24
|
|
|
|
|
35
|
$number = (~$number); |
45
|
24
|
|
|
|
|
39
|
$number &= 0xffffffff; |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
# 6. Break the binary value out into 5-bit chunks (starting from the right hand side): |
48
|
|
|
|
|
|
|
# 00001 00010 01010 10000 11111 00001 |
49
|
48
|
|
|
|
|
109
|
my $bin = sprintf '%b', $number; |
50
|
48
|
100
|
|
|
|
139
|
$bin = '0'x(5-length($bin)%5) . $bin if length($bin)%5 != 0; # pad |
51
|
48
|
|
|
|
|
73
|
my @chunks; |
52
|
48
|
|
|
|
|
90
|
my $revbin = reverse $bin; |
53
|
48
|
|
|
|
|
432
|
push @chunks, scalar reverse($1) while $revbin =~ m{(.....)}g; |
54
|
|
|
|
|
|
|
# 7. Place the 5-bit chunks into reverse order: |
55
|
|
|
|
|
|
|
# 00001 11111 10000 01010 00010 00001 |
56
|
|
|
|
|
|
|
# It's already reversed |
57
|
|
|
|
|
|
|
# 8. OR each value with 0x20 if another bit chunk follows: |
58
|
|
|
|
|
|
|
# 100001 111111 110000 101010 100010 000001 |
59
|
48
|
|
|
|
|
113
|
@chunks = ((map { oct("0b$_") | 0x20 } @chunks[0 .. $#chunks-1]), oct("0b".$chunks[-1])); # and also decode to decimal on the fly |
|
179
|
|
|
|
|
360
|
|
60
|
|
|
|
|
|
|
# 9. Convert each value to decimal: |
61
|
|
|
|
|
|
|
# 33 63 48 42 34 1 |
62
|
|
|
|
|
|
|
# Done above |
63
|
|
|
|
|
|
|
# 10. Add 63 to each value: |
64
|
|
|
|
|
|
|
# 96 126 111 105 97 64 |
65
|
48
|
|
|
|
|
89
|
@chunks = map { $_+63 } @chunks; |
|
227
|
|
|
|
|
394
|
|
66
|
|
|
|
|
|
|
# 11. Convert each value to its ASCII equivalent: |
67
|
|
|
|
|
|
|
# `~oia@ |
68
|
48
|
|
|
|
|
82
|
@chunks = map { chr } @chunks; |
|
227
|
|
|
|
|
447
|
|
69
|
48
|
|
|
|
|
162
|
join '', @chunks; |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
sub encode_polyline { |
73
|
2
|
|
|
2
|
1
|
412
|
my(@path) = @_; |
74
|
2
|
|
|
|
|
4
|
my @res; |
75
|
2
|
|
|
|
|
4
|
my($curr_lat_e5,$curr_lon_e5) = (0,0); |
76
|
2
|
|
|
|
|
5
|
for my $lat_lon (@path) { |
77
|
23
|
|
|
|
|
46
|
my($lat_e5,$lon_e5) = map { sprintf("%.0f", $_*1e5) } ($lat_lon->{lat}, $lat_lon->{lon}); |
|
46
|
|
|
|
|
115
|
|
78
|
23
|
|
|
|
|
55
|
my $deltay = ($lat_e5 - $curr_lat_e5) / 1e5; |
79
|
23
|
|
|
|
|
38
|
my $deltax = ($lon_e5 - $curr_lon_e5) / 1e5; |
80
|
23
|
|
|
|
|
44
|
push @res, encode_number($deltay), encode_number($deltax); |
81
|
23
|
|
|
|
|
51
|
($curr_lat_e5,$curr_lon_e5) = ($lat_e5,$lon_e5); |
82
|
|
|
|
|
|
|
} |
83
|
2
|
|
|
|
|
13
|
join '', @res; |
84
|
|
|
|
|
|
|
} |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
sub encode_level { |
87
|
|
|
|
|
|
|
# 1. Take the initial unsigned value: |
88
|
|
|
|
|
|
|
# 174 |
89
|
2
|
|
|
2
|
1
|
5
|
my $number = shift; |
90
|
|
|
|
|
|
|
# 2. Convert the decimal value to a binary value: |
91
|
|
|
|
|
|
|
# 10101110 |
92
|
2
|
|
|
|
|
9
|
my $bin = sprintf '%b', $number; |
93
|
|
|
|
|
|
|
# 3. Break the binary value out into 5-bit chunks (starting from the right hand side): |
94
|
|
|
|
|
|
|
# 101 01110 |
95
|
2
|
100
|
|
|
|
8
|
$bin = '0'x(5-length($bin)%5) . $bin if length($bin)%5 != 0; # pad |
96
|
2
|
|
|
|
|
4
|
my @chunks; |
97
|
2
|
|
|
|
|
5
|
my $revbin = reverse $bin; |
98
|
2
|
|
|
|
|
44
|
push @chunks, scalar reverse($1) while $revbin =~ m{(.....)}g; |
99
|
|
|
|
|
|
|
# 4. Place the 5-bit chunks into reverse order: |
100
|
|
|
|
|
|
|
# 01110 101 |
101
|
|
|
|
|
|
|
# It's already reversed |
102
|
|
|
|
|
|
|
# 5. OR each value with 0x20 if another bit chunk follows: |
103
|
|
|
|
|
|
|
# 101110 00101 |
104
|
2
|
|
|
|
|
7
|
@chunks = ((map { oct("0b$_") | 0x20 } @chunks[0 .. $#chunks-1]), oct("0b".$chunks[-1])); # and also decode to decimal on the fly |
|
8
|
|
|
|
|
24
|
|
105
|
|
|
|
|
|
|
# 6. Convert each value to decimal: |
106
|
|
|
|
|
|
|
# 46 5 |
107
|
|
|
|
|
|
|
# Done above |
108
|
|
|
|
|
|
|
# 7. Add 63 to each value: |
109
|
|
|
|
|
|
|
# 109 68 |
110
|
2
|
|
|
|
|
5
|
@chunks = map { $_+63 } @chunks; |
|
10
|
|
|
|
|
18
|
|
111
|
|
|
|
|
|
|
# 8. Convert each value to its ASCII equivalent: |
112
|
|
|
|
|
|
|
# mD |
113
|
2
|
|
|
|
|
4
|
@chunks = map { chr } @chunks; |
|
10
|
|
|
|
|
22
|
|
114
|
2
|
|
|
|
|
13
|
join '', @chunks; |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Translated this php script |
118
|
|
|
|
|
|
|
# |
119
|
|
|
|
|
|
|
# to perl |
120
|
|
|
|
|
|
|
sub decode_polyline { |
121
|
2
|
|
|
2
|
1
|
8
|
my($encoded) = @_; |
122
|
|
|
|
|
|
|
|
123
|
2
|
|
|
|
|
4
|
my $length = length $encoded; |
124
|
2
|
|
|
|
|
3
|
my $index = 0; |
125
|
2
|
|
|
|
|
4
|
my @points; |
126
|
2
|
|
|
|
|
3
|
my $lat = 0; |
127
|
2
|
|
|
|
|
4
|
my $lng = 0; |
128
|
|
|
|
|
|
|
|
129
|
2
|
|
|
|
|
6
|
while ($index < $length) { |
130
|
|
|
|
|
|
|
# The encoded polyline consists of a latitude value followed |
131
|
|
|
|
|
|
|
# by a longitude value. They should always come in pairs. Read |
132
|
|
|
|
|
|
|
# the latitude value first. |
133
|
23
|
|
|
|
|
44
|
for my $val (\$lat, \$lng) { |
134
|
46
|
|
|
|
|
70
|
my $shift = 0; |
135
|
46
|
|
|
|
|
61
|
my $result = 0; |
136
|
|
|
|
|
|
|
# Temporary variable to hold each ASCII byte. |
137
|
46
|
|
|
|
|
69
|
my $b; |
138
|
46
|
|
|
|
|
64
|
do { |
139
|
|
|
|
|
|
|
# The `ord(substr($encoded, $index++))` statement returns |
140
|
|
|
|
|
|
|
# the ASCII code for the character at $index. Subtract 63 |
141
|
|
|
|
|
|
|
# to get the original value. (63 was added to ensure |
142
|
|
|
|
|
|
|
# proper ASCII characters are displayed in the encoded |
143
|
|
|
|
|
|
|
# polyline string, which is `human` readable) |
144
|
225
|
|
|
|
|
359
|
$b = ord(substr($encoded, $index++, 1)) - 63; |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# AND the bits of the byte with 0x1f to get the original |
147
|
|
|
|
|
|
|
# 5-bit `chunk. Then left shift the bits by the required |
148
|
|
|
|
|
|
|
# amount, which increases by 5 bits each time. OR the |
149
|
|
|
|
|
|
|
# value into $results, which sums up the individual 5-bit |
150
|
|
|
|
|
|
|
# chunks into the original value. Since the 5-bit chunks |
151
|
|
|
|
|
|
|
# were reversed in order during encoding, reading them in |
152
|
|
|
|
|
|
|
# this way ensures proper summation. |
153
|
225
|
|
|
|
|
324
|
$result |= ($b & 0x1f) << $shift; |
154
|
225
|
|
|
|
|
462
|
$shift += 5; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
# Continue while the read byte is >= 0x20 since the last |
157
|
|
|
|
|
|
|
# `chunk` was not OR'd with 0x20 during the conversion |
158
|
|
|
|
|
|
|
# process. (Signals the end) |
159
|
|
|
|
|
|
|
while ($b >= 0x20); |
160
|
|
|
|
|
|
|
|
161
|
1
|
|
|
1
|
|
425
|
use integer; # see last paragraph of "Integer Arithmetic" in perlop.pod |
|
1
|
|
|
|
|
16
|
|
|
1
|
|
|
|
|
5
|
|
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# Check if negative, and convert. (All negative values have the last bit |
164
|
|
|
|
|
|
|
# set) |
165
|
46
|
100
|
|
|
|
89
|
my $dtmp = (($result & 1) ? ~($result >> 1) : ($result >> 1)); |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# Compute actual latitude (resp. longitude) since value is |
168
|
|
|
|
|
|
|
# offset from previous value. |
169
|
46
|
|
|
|
|
85
|
$$val += $dtmp; |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
# The actual latitude and longitude values were multiplied by |
173
|
|
|
|
|
|
|
# 1e5 before encoding so that they could be converted to a 32-bit |
174
|
|
|
|
|
|
|
# integer representation. (With a decimal accuracy of 5 places) |
175
|
|
|
|
|
|
|
# Convert back to original values. |
176
|
23
|
|
|
|
|
76
|
push @points, {lat => $lat * 1e-5, lon => $lng * 1e-5}; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
|
179
|
2
|
|
|
|
|
10
|
@points; |
180
|
|
|
|
|
|
|
} |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
1; |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
__END__ |