Permalink
Browse files

Encoding and decoding

  • Loading branch information...
1 parent 64ce6e8 commit 2c92e192e42cc1644dfd56a82a28e593b8c63948 @joshuaclayton committed Jan 31, 2011
View
@@ -0,0 +1,24 @@
+PATH
+ remote: .
+ specs:
+ polylines (0.0.1)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ diff-lcs (1.1.2)
+ rspec (2.4.0)
+ rspec-core (~> 2.4.0)
+ rspec-expectations (~> 2.4.0)
+ rspec-mocks (~> 2.4.0)
+ rspec-core (2.4.0)
+ rspec-expectations (2.4.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.4.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ polylines!
+ rspec (= 2.4.0)
View
@@ -1,2 +1,12 @@
+require "rake"
+require "rake/tasklib"
require 'bundler'
Bundler::GemHelper.install_tasks
+
+Bundler.require
+
+require "rspec/core/rake_task"
+
+RSpec::Core::RakeTask.new(:rspec)
+
+task :default => [:rspec]
View
@@ -1,3 +1,4 @@
module Polylines
- # Your code goes here...
+ autoload :Encoder, "polylines/encoder"
+ autoload :Decoder, "polylines/decoder"
end
View
@@ -0,0 +1,84 @@
+module Polylines
+ class Decoder
+ def self.decode_polyline(polyline)
+ set = []
+
+ points_with_deltas = polyline.split(//).inject([]) do |charset, char|
+ set << char
+
+ if ((char[0] - 63) & 0x20) == 0
+ charset << set.join
+ set = []
+ end
+
+ charset
+ end.map {|set| decode(set) }
+
+ points = [[points_with_deltas.shift, points_with_deltas.shift]]
+
+ while points_with_deltas.any?
+ new_latitude = points.last[0] + points_with_deltas.shift
+ new_longitude = points.last[1] + points_with_deltas.shift
+ points << [new_latitude, new_longitude]
+ end
+
+ points
+ end
+
+ def self.decode(string)
+ decimal_values = step_11(string)
+ decimal_values = step_10(decimal_values)
+ xord_values = step_8(decimal_values)
+ five_bit_chunks = step_7(xord_values)
+ binary_string = step_6(five_bit_chunks)
+
+ negative = binary_string[-1, 1] == "1"
+
+ number = binary_string.to_i(2)
+ number = step_5(number) if negative
+ number = step_4(number)
+ number = step_3(number) if negative
+ step_2(number)
+ end
+
+ def self.step_2(number)
+ number.to_f/1e5
+ end
+
+ def self.step_3(number)
+ number -= 1
+ number = ~number
+ number *= -1
+ end
+
+ def self.step_4(number)
+ number >> 1
+ end
+
+ def self.step_5(number)
+ ~number
+ end
+
+ def self.step_6(five_bit_chunks)
+ five_bit_chunks.map do |chunk|
+ "%05b" % chunk
+ end.join
+ end
+
+ def self.step_7(five_bit_chunks)
+ five_bit_chunks.reverse
+ end
+
+ def self.step_8(decimal_values)
+ decimal_values[0..-2].map {|value| 0x20 ^ value } << decimal_values.last
+ end
+
+ def self.step_10(values)
+ values.map {|value| value - 63 }
+ end
+
+ def self.step_11(string)
+ string.split(//).map {|char| char[0] }
+ end
+ end
+end
View
@@ -0,0 +1,72 @@
+module Polylines
+ class Encoder
+ def self.encode_points(points)
+ delta_latitude, delta_longitude = 0, 0
+
+ points.inject([]) do |polyline, (latitude, longitude)|
+ polyline << encode(latitude - delta_latitude)
+ polyline << encode(longitude - delta_longitude)
+ delta_latitude, delta_longitude = latitude, longitude
+ polyline
+ end.join
+ end
+
+ def self.encode(number)
+ negative = number < 0
+ number = step_2(number)
+ number = step_3(number) if negative
+ number = step_4(number)
+ number = step_5(number) if negative
+
+ five_bit_chunks = step_6(number)
+ five_bit_chunks = step_7(five_bit_chunks)
+ ord_values = step_8(five_bit_chunks)
+ decimal_values = step_10(ord_values)
+
+ step_11(decimal_values)
+ end
+
+ def self.step_2(number)
+ (number * 1e5).round
+ end
+
+ def self.step_3(number)
+ number *= -1
+ number = ~number
+ number += 1
+ end
+
+ def self.step_4(number)
+ number << 1
+ end
+
+ def self.step_5(number)
+ ~number
+ end
+
+ def self.step_6(number)
+ [].tap do |numbers|
+ while number > 0 do
+ numbers.unshift(number & 0x1f)
+ number = number >> 5
+ end
+ end
+ end
+
+ def self.step_7(five_bit_chunks)
+ five_bit_chunks.reverse
+ end
+
+ def self.step_8(five_bit_chunks)
+ five_bit_chunks[0..-2].map {|chunk| 0x20 | chunk } << five_bit_chunks.last
+ end
+
+ def self.step_10(values)
+ values.map {|value| value + 63 }
+ end
+
+ def self.step_11(values)
+ values.map(&:chr).join
+ end
+ end
+end
View
@@ -15,4 +15,6 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
+
+ s.add_development_dependency("rspec", "2.4.0")
end
@@ -0,0 +1,16 @@
+require "spec_helper"
+
+describe Polylines::Decoder, ".decode" do
+ it "decodes a single point" do
+ Polylines::Decoder.decode("`~oia@").should be_within(0.00001).of(-179.9832104)
+ end
+end
+
+describe Polylines::Decoder, ".decode_polyline" do
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
+ let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
+
+ it "decodes a polyline correctly" do
+ Polylines::Decoder.decode_polyline(polyline).should == points
+ end
+end
@@ -0,0 +1,16 @@
+require "spec_helper"
+
+describe Polylines::Encoder, ".encode" do
+ it "encodes a single point" do
+ Polylines::Encoder.encode(-179.9832104).should == "`~oia@"
+ end
+end
+
+describe Polylines::Encoder, ".encode_points" do
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
+ let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
+
+ it "encodes points correctly" do
+ Polylines::Encoder.encode_points(points).should == polyline
+ end
+end
View
@@ -0,0 +1,7 @@
+require "rubygems"
+require "bundler/setup"
+
+require "polylines"
+#
+# RSpec.configure do |config|
+# end

0 comments on commit 2c92e19

Please sign in to comment.