fast-polylines 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8481e1a1c0eeda19f205c7900fa58f3608e7092e88a1d90ffd4fabc4520706f1
4
- data.tar.gz: d85050fd3d1796b76d40e8fbb2ae6d4ba18871359312c2648cf6831feba92bf8
3
+ metadata.gz: a3a21f83abca7c1b9ef3bb824145ceea3189fbdb9d428a2faff1daa94e2a50fb
4
+ data.tar.gz: 0e6578cb705d0121daadb0a019bedd2d0d1b2589b2905134f933f4ae0859d3e2
5
5
  SHA512:
6
- metadata.gz: 5ed680bb3cb3097c7b060d18d27b4a18a5def03ec6b0a79e918f61e763cb4f32477023e9db76bb660ea33a6fa0f5c8352781c11d63e2b2d1cc2d0a79c45bf134
7
- data.tar.gz: e929f7f781e273068542d9b60c8344e6f2fa548dcaf0652787beaf3fd46bd6e162c588cb9c1616bd77342b8e69ce5954ad2799344485b212ba907f07f3f65ee9
6
+ metadata.gz: 16c293fc12361086fc39002b10fd81faaeb68632e4c6e670de1c2ec242b8277a7c453da2e5f1a9176104ccdafab99447807871f8afdb09188d97f07ed6a307d0
7
+ data.tar.gz: e48e91654bc2746c4c36d3d6ce744914941ec4966f1aa6b56bbe61cacdcce841dc936b997dd477794ad8a0546ebb7c9317afc007dc1b9f74dd630d9de5d2f417
data/README.md CHANGED
@@ -1,83 +1,139 @@
1
1
  # Fast Polylines
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/fast-polylines.svg)](https://badge.fury.io/rb/fast-polylines)
4
- [![CircleCI](https://circleci.com/gh/klaxit/fast-polylines.svg?style=shield&circle-token=:circle-token)](https://circleci.com/gh/klaxit/fast-polylines)
4
+ [![Build Status](https://travis-ci.org/klaxit/fast-polylines.svg?branch=master)](https://travis-ci.org/klaxit/fast-polylines)
5
5
 
6
- Implementation of the Google polyline algorithm :
7
- http://code.google.com/apis/maps/documentation/utilities/polylinealgorithm.html
6
+ Implementation of the [Google polyline algorithm][algorithm].
8
7
 
9
- Greatly inspired by Joshua Clayton gem : https://github.com/joshuaclayton/polylines
8
+ **BREAKING CHANGES:** The version 2.0.0 of FastPolylines includes breaking changes, see [Migrate from 1.0.0](#migrate-from-1.0.0)
10
9
 
11
- But an about **8x faster encoding** and **10x faster decoding** implementation (`make benchmark` on a MacBook pro 13 - 2,3 GHz Intel Core i5).
10
+
11
+ About **300x faster encoding and decoding** than [Joshua Clayton's gem][polylines].
12
+
13
+ `make benchmark` on a MacBook pro 13 - 2,3 GHz Intel Core i5:
12
14
 
13
15
  ```
14
- ** ENCODING **
16
+ ——————————————————————————————— ENCODING ————————————————————————————————
15
17
 
16
18
  Warming up --------------------------------------
17
- Polylines encode 282.000 i/100ms
18
- FastPolylines encode 2.339k i/100ms
19
+ Polylines 277.000 i/100ms
20
+ FastPolylinesV1 2.050k i/100ms
21
+ FastPolylinesV2 73.822k i/100ms
19
22
  Calculating -------------------------------------
20
- Polylines encode 3.024k7.4%) i/s - 15.228k in 5.068950s
21
- FastPolylines encode 24.562k6.7%) i/s - 123.967k in 5.074793s
23
+ Polylines 3.254k1.8%) i/s - 16.343k in 5.023767s
24
+ FastPolylinesV1 25.715k3.7%) i/s - 129.150k in 5.029675s
25
+ FastPolylinesV2 933.751k (± 4.3%) i/s - 4.725M in 5.072446s
22
26
 
23
27
  Comparison:
24
- FastPolylines encode: 24561.8 i/s
25
- Polylines encode: 3023.6 i/s - 8.12x slower
28
+ FastPolylinesV2: 933750.7 i/s
29
+ FastPolylinesV1: 25715.1 i/s - 36.31x slower
30
+ Polylines: 3254.3 i/s - 286.93x slower
26
31
 
27
32
 
28
- ** DECODING **
33
+ ——————————————————————————————— DECODING ————————————————————————————————
29
34
 
30
35
  Warming up --------------------------------------
31
- Polylines decode 125.000 i/100ms
32
- FastPolylines decode 1.341k i/100ms
36
+ Polylines 140.000 i/100ms
37
+ FastPolylinesV1 1.602k i/100ms
38
+ FastPolylinesV2 36.432k i/100ms
33
39
  Calculating -------------------------------------
34
- Polylines decode 1.284k9.7%) i/s - 6.375k in 5.023536s
35
- FastPolylines decode 13.278k20.5%) i/s - 61.686k in 5.003858s
40
+ Polylines 1.401k2.2%) i/s - 7.000k in 5.000321s
41
+ FastPolylinesV1 16.465k 3.7%) i/s - 83.304k in 5.067786s
42
+ FastPolylinesV2 396.100k (± 5.2%) i/s - 2.004M in 5.074500s
36
43
 
37
44
  Comparison:
38
- FastPolylines decode: 13278.0 i/s
39
- Polylines decode: 1284.3 i/s - 10.34x slower
45
+ FastPolylinesV2: 396100.0 i/s
46
+ FastPolylinesV1: 16464.6 i/s - 24.06x slower
47
+ Polylines: 1400.6 i/s - 282.81x slower
40
48
  ```
41
49
 
42
50
  ## Install
43
51
 
44
- ```
52
+ ```bash
45
53
  gem install fast-polylines
46
54
  ```
47
55
 
48
- or in Bundler:
49
- ```
50
- gem "fast-polylines"
56
+ or in your `Gemfile`:
57
+ ```ruby
58
+ gem "fast-polylines", "~> 2.0.0"
51
59
  ```
52
60
 
53
61
  ## Usage
54
62
 
55
- ```
56
- require 'fast-polylines'
63
+ ```ruby
64
+ require "fast-polylines"
57
65
 
58
- FastPolylines::Encoder.encode([[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]])
66
+ FastPolylines.encode([[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]])
59
67
  # "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
60
68
 
61
- FastPolylines::Decoder.decode("_p~iF~ps|U_ulLnnqC_mqNvxq`@")
69
+ FastPolylines.decode("_p~iF~ps|U_ulLnnqC_mqNvxq`@")
62
70
  # [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
63
71
  ```
64
72
 
65
73
  ## Advanced usage
66
74
 
67
- * With a different precision (default precision of `1e5`) :
75
+ **Use a different precision**
68
76
 
69
- ```
70
- FastPolylines::Encoder.encode([[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]], 1e6)
77
+ Default precision is `5` decimals, to use a precision of `6` decimals:
78
+ ```ruby
79
+ FastPolylines.encode([[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]], 6)
71
80
  # "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI"
72
81
 
73
- FastPolylines::Decoder.decode("_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI", 1e6)
82
+ FastPolylines.decode("_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI", 6)
74
83
  # [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
75
84
  ```
85
+ The precision max is `13`.
86
+
87
+ ## Migrate from 1.0.0
88
+
89
+ **TL;DR:**
90
+
91
+ ```ruby
92
+ # before
93
+ FastPolylines::Encoder.encode([[1.2, 1.2], [2.4, 2.4]], 1e6)
94
+ # after
95
+ FastPolylines.encode([[1.2, 1.2], [2.4, 2.4]], 6)
96
+ ```
97
+
98
+ The new version of `FastPolylines` doesn't support precision more than `1e13`,
99
+ you should not consider using it anyway since [it is way too precise][xkcd].
100
+
101
+ `Encoder` and `Decoder` modules are deprecated in favor of the single parent
102
+ module. Even though you can still use those, a deprecation warning will be
103
+ printed.
104
+
105
+ The precision is now an integer representing the number of decimals. It is
106
+ slightly smaller, and mostly this will avoid having any float value as
107
+ precision.
76
108
 
77
109
  ## Run the Benchmark
78
110
 
79
111
  You can run the benchmark with `make benchmark`.
80
112
 
113
+ ## Contributing
114
+
115
+ ```bash
116
+ git clone git@github.com:klaxit/fast-polylines
117
+ cd fast-polylines
118
+ bundle install
119
+ # Implement a feature, resolve a bug...
120
+ make rubocop
121
+ make test
122
+ git commit "My new feature!"
123
+ # Make a PR
124
+ ```
125
+
126
+ There is a `make console` command as well to open a ruby console with the
127
+ current version loaded.
128
+
129
+ [And here's a good starting point for Ruby C extensions knowledge.][ruby-c]
130
+
81
131
  ## License
82
132
 
83
133
  Please see LICENSE
134
+
135
+
136
+ [algorithm]: https://code.google.com/apis/maps/documentation/utilities/polylinealgorithm.html
137
+ [polylines]: https://github.com/joshuaclayton/polylines
138
+ [xkcd]: https://xkcd.com/2170/
139
+ [ruby-c]: https://github.com/ruby/ruby/blob/master/doc/extension.rdoc
@@ -0,0 +1,3 @@
1
+ *.o
2
+ *.bundle
3
+ Makefile
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ create_makefile "fast_polylines/fast_polylines"
@@ -0,0 +1,151 @@
1
+ #include <ruby/ruby.h>
2
+
3
+ // An encoded number can have at most _precision_ characters. However,
4
+ // it seems like we have a fuzzy behavior on low precisions. Hence a guard
5
+ // for those lower precisions.
6
+ #define MAX_ENCODED_CHUNKS(precision) (precision < 5 ? 5 : precision)
7
+
8
+ #define DEFAULT_PRECISION 5
9
+
10
+ // Already smaller than a sand grain. https://xkcd.com/2170/
11
+ // In fact, at 15 precision, we can be sure there will be precision loss
12
+ // in the C/Ruby value conversion. And 14 precision may be an edge case
13
+ // since we work with signed values.
14
+ #define MAX_PRECISION 13
15
+
16
+ // Uncomment this line to show debug logs.
17
+ // #define DEBUG 1
18
+
19
+ #ifdef DEBUG
20
+ #define dbg(...) printf(__VA_ARGS__)
21
+ #define rdbg(value) rb_funcall(Qnil, rb_intern("p"), 1, value)
22
+ #else
23
+ #define dbg(...)
24
+ #define rdbg(...)
25
+ #endif
26
+
27
+ static inline uint _get_precision(VALUE value) {
28
+ int precision = NIL_P(value) ? DEFAULT_PRECISION : NUM2INT(value);
29
+ if (precision > MAX_PRECISION) rb_raise(rb_eArgError, "precision too high (https://xkcd.com/2170/)");
30
+ if (precision < 0) rb_raise(rb_eArgError, "negative precision doesn't make sense");
31
+ return (uint)precision;
32
+ }
33
+
34
+ static inline VALUE
35
+ rb_FastPolylines__decode(int argc, VALUE *argv, VALUE self) {
36
+ rb_check_arity(argc, 1, 2);
37
+ Check_Type(argv[0], T_STRING);
38
+ char* polyline = StringValueCStr(argv[0]);
39
+ uint precision = _get_precision(argc > 1 ? argv[1] : Qnil);
40
+ double precision_value = pow(10.0, precision);
41
+ VALUE ary = rb_ary_new();
42
+ // Helps keeping track of whether we are computing lat (0) or lng (1).
43
+ uint8_t index = 0;
44
+ size_t shift = 0;
45
+ int64_t delta = 0;
46
+ VALUE sub_ary[2];
47
+ double latlng[2] = {0.0, 0.0};
48
+ // Loops until end of string nul character is encountered.
49
+ while (*polyline) {
50
+ int64_t chunk = *polyline++;
51
+
52
+ if (chunk < 63 || chunk > 126) {
53
+ rb_raise(rb_eArgError, "invalid character '%c'", (char)chunk);
54
+ }
55
+
56
+ chunk -= 63;
57
+ delta = delta | ((chunk & ~0x20) << shift);
58
+ shift += 5;
59
+
60
+ if (!(chunk & 0x20)) {
61
+ delta = (delta & 1) ? ~(delta >> 1) : (delta >> 1);
62
+ latlng[index] += delta;
63
+ sub_ary[index] = rb_float_new((double) latlng[index] / precision_value);
64
+ // When both coordinates are parsed, we can push those to the result ary.
65
+ if (index) rb_ary_push(ary, rb_ary_new_from_values(2, sub_ary));
66
+ // Reinitilize since we are done for current coordinate.
67
+ index = 1 - index; delta = 0; shift = 0;
68
+ }
69
+ }
70
+ return ary;
71
+ }
72
+
73
+ static inline uint8_t
74
+ _polyline_encode_number(char *chunks, int64_t number) {
75
+ number = number < 0 ? ~(number << 1) : (number << 1);
76
+ uint8_t i = 0;
77
+ while (number > 0x20) {
78
+ uint8_t chunk = number & 0x1f;
79
+ chunks[i++] = (0x20 | chunk) + 63;
80
+ number = number >> 5;
81
+ }
82
+ dbg("%u encoded chunks\n", i);
83
+ chunks[i++] = number + 63;
84
+ return i;
85
+ }
86
+
87
+ static inline VALUE
88
+ rb_FastPolylines__encode(int argc, VALUE *argv, VALUE self) {
89
+ rb_check_arity(argc, 1, 2);
90
+ Check_Type(argv[0], T_ARRAY);
91
+ size_t len = RARRAY_LEN(argv[0]);
92
+ uint64_t i;
93
+ VALUE current_pair;
94
+ int64_t prev_pair[2] = {0, 0};
95
+ uint precision = _get_precision(argc > 1 ? argv[1] : Qnil);
96
+ dbg("rb_FastPolylines__encode(..., %u)\n", precision);
97
+ rdbg(argv[0]);
98
+ double precision_value = pow(10.0, precision);
99
+ // This is the maximum possible size the polyline may have. This
100
+ // being **without** null character. To copy it later as a Ruby
101
+ // string we'll have to use `rb_str_new` with the length.
102
+ dbg("allocated size: %u * 2 * %lu = %lu\n",
103
+ MAX_ENCODED_CHUNKS(precision),
104
+ len,
105
+ MAX_ENCODED_CHUNKS(precision) * 2 * len);
106
+ char *chunks = malloc(MAX_ENCODED_CHUNKS(precision) * 2 * len * sizeof(char));
107
+ size_t chunks_index = 0;
108
+ for (i = 0; i < len; i++) {
109
+ current_pair = RARRAY_AREF(argv[0], i);
110
+ uint8_t j;
111
+ if (RARRAY_LEN(current_pair) != 2) {
112
+ free(chunks);
113
+ rb_raise(rb_eArgError, "wrong number of coordinates");
114
+ }
115
+ for (j = 0; j < 2; j++) {
116
+ VALUE current_value = RARRAY_AREF(current_pair, j);
117
+ switch (TYPE(current_value)) {
118
+ case T_BIGNUM:
119
+ case T_FLOAT:
120
+ case T_FIXNUM:
121
+ case T_RATIONAL:
122
+ break;
123
+ default:
124
+ free(chunks);
125
+ rb_raise(rb_eTypeError, "no implicit conversion to Float from %s", rb_obj_classname(current_value));
126
+ };
127
+
128
+ double parsed_value = NUM2DBL(current_value);
129
+ if (-180.0 > parsed_value || parsed_value > 180.0) {
130
+ free(chunks);
131
+ rb_raise(rb_eArgError, "coordinates must be between -180.0 and 180.0");
132
+ }
133
+ int64_t rounded_value = round(parsed_value * precision_value);
134
+ int64_t delta = rounded_value - prev_pair[j];
135
+ prev_pair[j] = rounded_value;
136
+ // We pass a pointer to the current chunk that need to be filled. Doing so
137
+ // avoid having to copy the string every single iteration.
138
+ chunks_index += _polyline_encode_number(chunks + chunks_index * sizeof(char), delta);
139
+ }
140
+ }
141
+ dbg("final chunks_index: %zu\n", chunks_index);
142
+ VALUE polyline = rb_str_new(chunks, chunks_index);
143
+ free(chunks);
144
+ return polyline;
145
+ }
146
+
147
+ void Init_fast_polylines() {
148
+ VALUE mFastPolylines = rb_define_module("FastPolylines");
149
+ rb_define_module_function(mFastPolylines, "decode", rb_FastPolylines__decode, -1);
150
+ rb_define_module_function(mFastPolylines, "encode", rb_FastPolylines__encode, -1);
151
+ }
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fast_polylines/fast_polylines"
4
+
5
+ module FastPolylines::Encoder
6
+ module_function def encode(points, precision = 1e5)
7
+ warn "Deprecated use of `FastPolylines::Encoder.encode`, " \
8
+ "use `FastPolylines.encode`."
9
+ FastPolylines.encode(points, Math.log10(precision))
10
+ end
11
+ end
12
+
13
+ module FastPolylines::Decoder
14
+ module_function def decode(polyline, precision = 1e5)
15
+ warn "Deprecated use of `FastPolylines::Decoder.decode`, " \
16
+ "use `FastPolylines.decode`."
17
+ FastPolylines.decode(polyline, Math.log10(precision))
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FastPolylines
4
+ VERSION = "2.0.0"
5
+ end
@@ -0,0 +1,94 @@
1
+ require "spec_helper"
2
+
3
+ describe FastPolylines do
4
+ describe ".decode" do
5
+ let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
6
+ context "with default precision" do
7
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
8
+ it "should decode a polyline correctly" do
9
+ expect(described_class.decode(polyline)).to eq points
10
+ end
11
+ end
12
+ context "with a 6 decimals precision" do
13
+ let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
14
+ it "should decode a polyline correctly" do
15
+ expect(described_class.decode(polyline, 6)).to eq points
16
+ end
17
+ end
18
+ context "with a 15 decimals precision" do
19
+ it "should raise" do
20
+ expect { described_class.decode("_kiF_kiF", 15) }.to raise_error(ArgumentError, /too high/)
21
+ end
22
+ end
23
+ context "with a negative precision" do
24
+ it "should raise" do
25
+ expect { described_class.decode("_kiF_kiF", -rand(1..10)) }.to raise_error(ArgumentError, /negative/)
26
+ end
27
+ end
28
+ context "with wrong characters" do
29
+ it "should raise" do
30
+ chr = "~".next
31
+ expect { described_class.decode(chr) }.to raise_error(ArgumentError, /'#{chr}'/)
32
+ end
33
+ end
34
+ context "with points that were close together" do
35
+ let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
36
+ it "should decode a polyline correctly" do
37
+ expect(described_class.decode("krk{FdxdlO?e@")).to eq points
38
+ end
39
+ end
40
+ context "with points that cause float overflow" do
41
+ let(:polyline) { "ahdiHsmeMHPDN|A`FVt@" }
42
+ let(:points) { [[48.85137, 2.32682], [48.85132, 2.32673], [48.85129, 2.32665], [48.85082, 2.32552], [48.8507, 2.32525]] }
43
+ it "should respect precision" do
44
+ expect(described_class.decode(polyline)).to eq points
45
+ end
46
+ end
47
+ end
48
+
49
+ describe ".encode" do
50
+ let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
51
+ context "with default precision" do
52
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
53
+ it "should encode points correctly" do
54
+ expect(described_class.encode(points)).to eq polyline
55
+ end
56
+ end
57
+ context "with a 6 decimals precision" do
58
+ let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
59
+ it "should encode points correctly" do
60
+ expect(described_class.encode(points, 6)).to eq polyline
61
+ end
62
+ end
63
+ context "with points that were close together" do
64
+ let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
65
+ it "should encode points correctly" do
66
+ expect(described_class.encode(points)).to eq "krk{FdxdlO?e@"
67
+ end
68
+ end
69
+ context "with possible rounding ambiguity" do
70
+ let(:points) { [[39.13594499,-94.4243478], [39.13558757,-94.4243471]] }
71
+ it "should encode points as Google API do" do
72
+ expect(described_class.encode(points)).to eq "svzmFdgi_QdA?"
73
+ end
74
+ end
75
+ context "with points out of bounds" do
76
+ it "should raise" do
77
+ expect { described_class.encode([[180.1, 0.0]]) }.to raise_error ArgumentError
78
+ expect { described_class.encode([[0, 180.1]]) }.to raise_error ArgumentError
79
+ expect { described_class.encode([[-180.1, 0.0]]) }.to raise_error ArgumentError
80
+ expect { described_class.encode([[0, -180.1]]) }.to raise_error ArgumentError
81
+ end
82
+ end
83
+ context "with a 15 decimals precision" do
84
+ it "should raise" do
85
+ expect { described_class.encode([[1.2, 1.2]], 15) }.to raise_error(ArgumentError, /too high/)
86
+ end
87
+ end
88
+ context "with a negative precision" do
89
+ it "should raise" do
90
+ expect { described_class.encode([[1.2, 1.2]], -rand(1..10)) }.to raise_error(ArgumentError, /negative/)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -1,7 +1,7 @@
1
1
  require "rubygems"
2
2
  require "bundler/setup"
3
3
 
4
- require "fast-polylines"
4
+ require "fast_polylines"
5
5
 
6
6
  RSpec.configure do |config|
7
7
  # Additional config goes here
metadata CHANGED
@@ -1,29 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fast-polylines
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrille Courtière
8
+ - Ulysse Buonomo
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2019-04-04 00:00:00.000000000 Z
12
+ date: 2020-03-26 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: benchmark-ips
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
- - - ">="
18
+ - - "~>"
18
19
  - !ruby/object:Gem::Version
19
- version: '0'
20
+ version: '2.7'
20
21
  type: :development
21
22
  prerelease: false
22
23
  version_requirements: !ruby/object:Gem::Requirement
23
24
  requirements:
24
- - - ">="
25
+ - - "~>"
25
26
  - !ruby/object:Gem::Version
26
- version: '0'
27
+ version: '2.7'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: polylines
29
30
  requirement: !ruby/object:Gem::Requirement
@@ -54,20 +55,21 @@ dependencies:
54
55
  version: '3.5'
55
56
  description:
56
57
  email:
57
- - cyrille@klaxit.com
58
+ - dev@klaxit.com
58
59
  executables: []
59
- extensions: []
60
+ extensions:
61
+ - ext/fast_polylines/extconf.rb
60
62
  extra_rdoc_files: []
61
63
  files:
62
64
  - README.md
63
- - lib/fast-polylines.rb
64
- - lib/fast-polylines/decoder.rb
65
- - lib/fast-polylines/encoder.rb
66
- - lib/fast-polylines/version.rb
67
- - spec/fast-polylines/decoder_spec.rb
68
- - spec/fast-polylines/encoder_spec.rb
65
+ - ext/fast_polylines/.gitignore
66
+ - ext/fast_polylines/extconf.rb
67
+ - ext/fast_polylines/fast_polylines.c
68
+ - lib/fast_polylines.rb
69
+ - lib/fast_polylines/version.rb
70
+ - spec/fast_polylines_spec.rb
69
71
  - spec/spec_helper.rb
70
- homepage: http://github.com/klaxit/fast-polylines
72
+ homepage: https://github.com/klaxit/fast-polylines
71
73
  licenses:
72
74
  - MIT
73
75
  metadata: {}
@@ -91,6 +93,5 @@ signing_key:
91
93
  specification_version: 4
92
94
  summary: Fast & easy Google polylines
93
95
  test_files:
94
- - spec/fast-polylines/decoder_spec.rb
95
- - spec/fast-polylines/encoder_spec.rb
96
+ - spec/fast_polylines_spec.rb
96
97
  - spec/spec_helper.rb
@@ -1,5 +0,0 @@
1
- require "fast-polylines/encoder"
2
- require "fast-polylines/decoder"
3
-
4
- module FastPolylines
5
- end
@@ -1,41 +0,0 @@
1
- module FastPolylines
2
- ##
3
- # Provide an interface to decode a Google polyline
4
- # to an array of lat / lng points
5
- class Decoder
6
- ##
7
- # Decode a polyline to a list of points
8
- #
9
- # @param [String] polyline to be decoded
10
- # @param [Float] precision of the polyline (default 1e5)
11
- # @return [Array<Array>] A list of lat / lng pairs
12
- def self.decode(polyline, precision = 1e5)
13
- coords = []
14
- acc = ""
15
- polyline.each_byte do |b|
16
- acc << b
17
- next unless b < 0x5f
18
- coords << acc
19
- acc = ""
20
- end
21
- lat = lng = 0
22
- coords.each_slice(2).map do |coords_pair|
23
- lat += decode_number(coords_pair[0])
24
- lng += decode_number(coords_pair[1])
25
- [lat / precision, lng / precision]
26
- end
27
- end
28
-
29
- def self.decode_number(string)
30
- result = 1
31
- shift = 0
32
- string.each_byte do |b|
33
- b = b - 63 - 1
34
- result += b << shift
35
- shift += 5
36
- end
37
- (result & 1).nonzero? ? (~result >> 1) : (result >> 1)
38
- end
39
- private_class_method :decode_number
40
- end
41
- end
@@ -1,47 +0,0 @@
1
- module FastPolylines
2
- ##
3
- # Provides an interface to encode an array of lat / lng points
4
- # to a Google polyline
5
- #
6
- class Encoder
7
- ##
8
- # Encode a list of points into a polyline string
9
- #
10
- # @param [Array<Array>] points as lat/lng pairs
11
- # @param [Float] precision of the encoding (default 1e5)
12
- # @return [String] the encoded polyline
13
- def self.encode(points, precision = 1e5)
14
- result = ""
15
- last_lat = last_lng = 0
16
- points.each do |point|
17
- lat = (point[0] * precision).round
18
- lng = (point[1] * precision).round
19
- d_lat = lat - last_lat
20
- d_lng = lng - last_lng
21
- chunks_lat = encode_number(d_lat)
22
- chunks_lng = encode_number(d_lng)
23
- result << chunks_lat << chunks_lng
24
- last_lat = lat
25
- last_lng = lng
26
- end
27
- result
28
- end
29
-
30
- def self.encode_number(num)
31
- sgn_num = num << 1
32
- sgn_num = ~sgn_num if num < 0
33
- encode_unsigned_number(sgn_num)
34
- end
35
-
36
- def self.encode_unsigned_number(num)
37
- encoded = ""
38
- while num >= 0x20
39
- encoded << (0x20 | (num & 0x1f)) + 63
40
- num = num >> 5
41
- end
42
- encoded << num + 63
43
- end
44
- private_class_method :encode_number
45
- private_class_method :encode_unsigned_number
46
- end
47
- end
@@ -1,3 +0,0 @@
1
- module FastPolylines
2
- VERSION = "1.0.0".freeze
3
- end
@@ -1,32 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe FastPolylines::Decoder do
4
- describe ".decode" do
5
- let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
6
- context "with default precision" do
7
- let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
8
- it "should decode a polyline correctly" do
9
- expect(described_class.decode(polyline)).to eq points
10
- end
11
- end
12
- context "with 1e6 precision" do
13
- let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
14
- it "should decode a polyline correctly" do
15
- expect(described_class.decode(polyline, 1e6)).to eq points
16
- end
17
- end
18
- context "with points that were close together" do
19
- let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
20
- it "should decode a polyline correctly" do
21
- expect(described_class.decode("krk{FdxdlO?e@")).to eq points
22
- end
23
- end
24
- context "with points that cause float overflow" do
25
- let(:polyline) { "ahdiHsmeMHPDN|A`FVt@" }
26
- let(:points) { [[48.85137, 2.32682], [48.85132, 2.32673], [48.85129, 2.32665], [48.85082, 2.32552], [48.8507, 2.32525]] }
27
- it "should respect precision" do
28
- expect(described_class.decode(polyline)).to eq points
29
- end
30
- end
31
- end
32
- end
@@ -1,31 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe FastPolylines::Encoder do
4
- describe ".encode" do
5
- let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
6
- context "with default precision" do
7
- let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
8
- it "should encode points correctly" do
9
- expect(described_class.encode(points)).to eq polyline
10
- end
11
- end
12
- context "with 1e6 precision" do
13
- let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
14
- it "should encode points correctly" do
15
- expect(described_class.encode(points, 1e6)).to eq polyline
16
- end
17
- end
18
- context "with points that were close together" do
19
- let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
20
- it "should encode points correctly" do
21
- expect(described_class.encode(points)).to eq "krk{FdxdlO?e@"
22
- end
23
- end
24
- context "with possible rounding ambiguity" do
25
- let(:points) { [[39.13594499,-94.4243478], [39.13558757,-94.4243471]] }
26
- it "should encode points as Google API do" do
27
- expect(described_class.encode(points)).to eq "svzmFdgi_QdA?"
28
- end
29
- end
30
- end
31
- end