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 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,8 @@
1
+ require "spec/rake/spectask"
2
+
3
+ task :default => :spec
4
+
5
+ # RSpec 1.3
6
+ Spec::Rake::SpecTask.new(:spec) do |spec|
7
+ spec.spec_files = FileList['spec/encoded_polyline/*_spec.rb']
8
+ end
@@ -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,2 @@
1
+ require 'encoded_polyline/core'
2
+ require 'encoded_polyline/version'
@@ -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
@@ -0,0 +1,3 @@
1
+ class EncodedPolyline
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -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
@@ -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