encoded_polyline 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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