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