encoded_polyline 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +33 -0
- data/Rakefile +8 -0
- data/encoded_polyline.gemspec +22 -0
- data/lib/encoded_polyline.rb +2 -0
- data/lib/encoded_polyline/core.rb +65 -0
- data/lib/encoded_polyline/version.rb +3 -0
- data/script/benchmark.rb +41 -0
- data/spec/encoded_polyline/encoded_polyline_spec.rb +51 -0
- data/spec/spec_helper.rb +1 -0
- metadata +105 -0
data/README.markdown
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# EncodedPolyline
|
2
|
+
|
3
|
+
EncodedPolyline implements [Google's algorithm](http://code.google.com/apis/maps/documentation/utilities/polylinealgorithm.html) for compressing arrays of geographic points. The compressed string can then be passed to the Google Maps API.
|
4
|
+
The Maps API limits URL requests to 2048 characters. This library does not enforce this limit on the encoding length.
|
5
|
+
|
6
|
+
## Examples
|
7
|
+
|
8
|
+
An [example map](http://maps.googleapis.com/maps/api/staticmap?size=400x400&sensor=false&path=weight:3|color:blue|enc:~fzz\]_uvsOdX`a@jR~Boe@_XfPZoe@N{N}p@jTqIxCpYvi@?yApz@{r@}HkQ|l@xi@pa@vB{IiHg`@ao@jWxA_Bi_@gN_S|\[zN}s@vy@mQ|c@bd@TyHti@x\]ca@vZ~Di}@mb@`\hPxe@hRky@~Rk`@nq@_p@t_@mUdw@oHaWc@dNus@kVz\[gu@oi@ne@}Yxg@mu@iCo}@mm@gQhMgWtc@}Kxv@x_@gSlWq|@vT{v@q{@_m@jL_Td\]
|
9
|
+
) from a 50-point random walk, encoded using the library.
|
10
|
+
|
11
|
+
Using the library:
|
12
|
+
|
13
|
+
EncodedPolyline.encode(-179.9832104) # "`~oia@"
|
14
|
+
EncodedPolyline.decode("`~oia@") # -179.98321
|
15
|
+
EncodedPolyline.encode_points([[37.782,-122.406]]) # "ohreFnlbjV"
|
16
|
+
EncodedPolyline.decode_points("ohreFnlbjV") # [[37.782,-122.406]]
|
17
|
+
|
18
|
+
## Performance
|
19
|
+
|
20
|
+
Some of the steps outlined in the algorithm spec are composed together, for speediness. Included is a benchmark that creates a 100-point random walk and encodes/decodes it 10,000 times (1 million points total).
|
21
|
+
|
22
|
+
Raw length(JSON): 3682 characters
|
23
|
+
Encoded length: 495 characters
|
24
|
+
Compression ratio: 0.134437805540467
|
25
|
+
|
26
|
+
user system total real
|
27
|
+
encoding 9.090000 0.000000 9.090000 ( 9.092519)
|
28
|
+
decoding 16.470000 0.010000 16.480000 ( 16.473547)
|
29
|
+
|
30
|
+
## Known Issues
|
31
|
+
|
32
|
+
Haven't tested with Ruby 1.9; probably doesn't work.
|
33
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require 'encoded_polyline/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "encoded_polyline"
|
8
|
+
s.version = EncodedPolyline::VERSION
|
9
|
+
s.authors = ["Brandon Liu"]
|
10
|
+
s.email = "bdon@bdon.org"
|
11
|
+
s.date = "2011-09-02"
|
12
|
+
s.description = "Encode an array of coordinates for the Google Static Maps API."
|
13
|
+
s.summary = "Encode an array of coordinates"
|
14
|
+
s.homepage = "http://github.com/bdon/encoded_polyline"
|
15
|
+
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
19
|
+
|
20
|
+
s.add_development_dependency("rake")
|
21
|
+
s.add_development_dependency("rspec", "~> 1.3.1")
|
22
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class EncodedPolyline
|
2
|
+
def self.encode(number)
|
3
|
+
number = (number * 1e5).round
|
4
|
+
n = number << 1
|
5
|
+
n = ~n if number < 0
|
6
|
+
str = ""
|
7
|
+
while n >= 0x20 do
|
8
|
+
str << ((0x20 | (n & 0x1f)) + 63).chr
|
9
|
+
n = n >> 5
|
10
|
+
end
|
11
|
+
return str << (n + 63).chr
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.decode_arr(arr)
|
15
|
+
num = 0
|
16
|
+
arr[0..-2].each_with_index do |elem, i|
|
17
|
+
num = (num | (((elem[0] - 63) ^ 0x20) << (i * 5)))
|
18
|
+
end
|
19
|
+
num = (num | ((arr.last[0] - 63) << ((arr.size-1) * 5)))
|
20
|
+
num = ~num if ((num & 0x1) == 1)
|
21
|
+
num = num >> 1
|
22
|
+
num = num.to_f / 1e5
|
23
|
+
return num
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.decode(str)
|
27
|
+
self.decode_arr(str.split(//))
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.encode_points(coordinates)
|
31
|
+
output = ""
|
32
|
+
output << encode(coordinates.first[0])
|
33
|
+
output << encode(coordinates.first[1])
|
34
|
+
|
35
|
+
(1..coordinates.size-1).each do |i|
|
36
|
+
previous = coordinates[i-1]
|
37
|
+
current = coordinates[i]
|
38
|
+
output << encode(current[0] - previous[0])
|
39
|
+
output << encode(current[1] - previous[1])
|
40
|
+
end
|
41
|
+
return output
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.decode_points(str)
|
45
|
+
decoded_points = []
|
46
|
+
point_chars = []
|
47
|
+
|
48
|
+
str.each_char do |char|
|
49
|
+
point_chars << char
|
50
|
+
if ((char[0] - 63) & 0x20).zero?
|
51
|
+
decoded_points << decode_arr(point_chars)
|
52
|
+
point_chars = []
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
coordinates = [[decoded_points.shift, decoded_points.shift]]
|
57
|
+
while decoded_points.any?
|
58
|
+
coordinates << [
|
59
|
+
coordinates.last[0] + decoded_points.shift,
|
60
|
+
coordinates.last[1] + decoded_points.shift
|
61
|
+
]
|
62
|
+
end
|
63
|
+
return coordinates
|
64
|
+
end
|
65
|
+
end
|
data/script/benchmark.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
require "lib/encoded_polyline/core"
|
3
|
+
require 'benchmark'
|
4
|
+
|
5
|
+
def rsign
|
6
|
+
(rand(2) == 1) ? -1 : 1
|
7
|
+
end
|
8
|
+
|
9
|
+
random_walk = [[rand * 180 * rsign, rand * 180 * rsign]]
|
10
|
+
99.times do
|
11
|
+
lat = random_walk.last[0] + rand * 0.01 * rsign
|
12
|
+
long =random_walk.last[1] + rand * 0.01 * rsign
|
13
|
+
random_walk << [lat, long]
|
14
|
+
end
|
15
|
+
|
16
|
+
random_walk_text = EncodedPolyline.encode_points(random_walk)
|
17
|
+
|
18
|
+
points_length = random_walk.inspect.gsub(' ','').to_s.length
|
19
|
+
compressed_length = random_walk_text.length
|
20
|
+
|
21
|
+
puts "Raw length(JSON):\t#{points_length} characters"
|
22
|
+
puts "Encoded length:\t\t#{compressed_length} characters"
|
23
|
+
|
24
|
+
puts "Compression ratio:\t#{compressed_length.to_f / points_length}"
|
25
|
+
|
26
|
+
puts "Static map:\t\thttp://maps.googleapis.com/maps/api/staticmap?size=400x400&sensor=false&path=weight:3%7Ccolor:blue%7Cenc:#{random_walk_text}"
|
27
|
+
puts "\n"
|
28
|
+
|
29
|
+
Benchmark.bm(20) do |x|
|
30
|
+
x.report("encoding") do
|
31
|
+
10000.times do |i|
|
32
|
+
EncodedPolyline.encode_points(random_walk)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
x.report("decoding") do
|
37
|
+
10000.times do |i|
|
38
|
+
EncodedPolyline.decode_points(random_walk_text)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe EncodedPolyline, ".encode" do
|
4
|
+
it "encodes a single point" do
|
5
|
+
EncodedPolyline.encode(-179.9832104).should == "`~oia@"
|
6
|
+
end
|
7
|
+
|
8
|
+
it "encodes a very slightly negative point" do
|
9
|
+
# Due to precision errors, verify that a negative value that rounds to 0 works.
|
10
|
+
EncodedPolyline.encode(-3e-6).should == "?"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe EncodedPolyline, ".decode" do
|
15
|
+
it "decodes a single point" do
|
16
|
+
EncodedPolyline.decode('`~oia@').should be_close(-179.9832104, 0.0001)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_be_similar_points(arr1, arr2)
|
21
|
+
arr1.each_with_index do |(x,y),i|
|
22
|
+
x.should be_close(arr2[i][0], 0.0001)
|
23
|
+
y.should be_close(arr2[i][1], 0.0001)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe EncodedPolyline, ".decode_points" do
|
28
|
+
let(:ring_points) { [[37.79297, -122.39676], [37.78987, -122.40075], [37.7894, -122.401], [37.78573, -122.40512], [37.78553, -122.40598], [37.78417, -122.40744], [37.78153, -122.41122], [37.77895, -122.41474], [37.77501, -122.41894], [37.77447, -122.41851], [37.77332, -122.41688], [37.77081, -122.41345], [37.77013, -122.40959], [37.77291, -122.40332], [37.77379, -122.40186], [37.77515, -122.40075], [37.77691, -122.39766], [37.77997, -122.39525], [37.78417, -122.39242], [37.78729, -122.38959], [37.78906, -122.38959], [37.79068, -122.39131], [37.79258, -122.39431], [37.79258, -122.39431], [37.79297, -122.39676], [37.78987, -122.40075], [37.7894, -122.401], [37.78573, -122.40512], [37.78553, -122.40598], [37.78417, -122.40744], [37.78153, -122.41122], [37.77895, -122.41474], [37.77501, -122.41894], [37.77447, -122.41851], [37.77332, -122.41688], [37.77081, -122.41345], [37.77013, -122.40959], [37.77291, -122.40332], [37.77379, -122.40186], [37.77515, -122.40075], [37.77691, -122.39766], [37.77997, -122.39525], [37.78417, -122.39242], [37.78729, -122.38959], [37.78906, -122.38959], [37.79068, -122.39131], [37.79258, -122.39431], [37.79258, -122.39431]] }
|
29
|
+
|
30
|
+
let(:ring_text) { "amteFvr`jVjR|W|Ap@|UvXf@jDnGbHnOrVbO~TrWfYjBuAdFeItNmTfCcWkPef@oDcHoG}E_JiRcRaNgYuPoRuPaJ?cIvI{JvQ??mAhNjR|W|Ap@|UvXf@jDnGbHnOrVbO~TrWfYjBuAdFeItNmTfCcWkPef@oDcHoG}E_JiRcRaNgYuPoRuPaJ?cIvI{JvQ??" }
|
31
|
+
|
32
|
+
it "encodes a pair of points" do
|
33
|
+
EncodedPolyline.encode_points([[37.782,-122.406]]).should == "ohreFnlbjV"
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should decode" do
|
37
|
+
should_be_similar_points(EncodedPolyline.decode_points(ring_text), ring_points)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "encodes points with little variation" do
|
41
|
+
points = [[37.79297, -122.39676],[37.79297, -122.39676],[37.79297, -122.39676]]
|
42
|
+
should_be_similar_points(EncodedPolyline.decode_points(EncodedPolyline.encode_points(points)), points)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "truncates to 5 digits of accuracy" do
|
46
|
+
points = [[37.79297111, -122.39676222],[37.79297333, -122.39676444],[37.79297555, -122.39676666]]
|
47
|
+
points_2 = [[37.79297, -122.39676],[37.79297, -122.39676],[37.79297, -122.39676]]
|
48
|
+
should_be_similar_points(EncodedPolyline.decode_points(EncodedPolyline.encode_points(points)), points_2)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'encoded_polyline'
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: encoded_polyline
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Brandon Liu
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-02 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rake
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 25
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 3
|
47
|
+
- 1
|
48
|
+
version: 1.3.1
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
description: Encode an array of coordinates for the Google Static Maps API.
|
52
|
+
email: bdon@bdon.org
|
53
|
+
executables: []
|
54
|
+
|
55
|
+
extensions: []
|
56
|
+
|
57
|
+
extra_rdoc_files: []
|
58
|
+
|
59
|
+
files:
|
60
|
+
- README.markdown
|
61
|
+
- Rakefile
|
62
|
+
- encoded_polyline.gemspec
|
63
|
+
- lib/encoded_polyline.rb
|
64
|
+
- lib/encoded_polyline/core.rb
|
65
|
+
- lib/encoded_polyline/version.rb
|
66
|
+
- script/benchmark.rb
|
67
|
+
- spec/encoded_polyline/encoded_polyline_spec.rb
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://github.com/bdon/encoded_polyline
|
71
|
+
licenses: []
|
72
|
+
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
hash: 3
|
84
|
+
segments:
|
85
|
+
- 0
|
86
|
+
version: "0"
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirements: []
|
97
|
+
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.6.2
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: Encode an array of coordinates
|
103
|
+
test_files:
|
104
|
+
- spec/encoded_polyline/encoded_polyline_spec.rb
|
105
|
+
- spec/spec_helper.rb
|