fast-polylines 1.0.0 → 2.0.0

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.
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