fast-polylines 0.1.1 → 2.2.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
- SHA1:
3
- metadata.gz: 188b873d21a061c22675b35f10d0364dd81407c2
4
- data.tar.gz: d0a78755d81d3eecc329023a5956af078c1930b1
2
+ SHA256:
3
+ metadata.gz: 0ab14d087b6992d7abec3047ad38aea4e7474f4bfff06b921ea91699bd788172
4
+ data.tar.gz: 4225fc53ff0801e6b145058461d9cf6d5f65be902787a7496e85c83883b52ba5
5
5
  SHA512:
6
- metadata.gz: 206c7ddc05a9cd4585f3090ff3a75eec0a888a59c2413ffcfa3dfb6a60152f3295e0298eff098528bee4f1e9289a8f44ab71a5658aa2ca48aa9605ca6ff182cb
7
- data.tar.gz: 6ece894847da8d830754b09a096c250232e9f7cc4dd80758ea093a463130a24724b6c114b98f4db190684394720951bf4000a0afa5615b9e3b9300f46092036b
6
+ metadata.gz: 82f43939d565a422fdf0696539f1c668d851aa88ff36d52a9f3ad2d909ebfe095c1ccd0b7266243943c834f70dc5c954c85715a3c435768df2c6ccce871443b4
7
+ data.tar.gz: 97ab65e7683ecbb53bf5673532e5ff99399b7f0b97b140221127b302e2d54020c1e3b1b6c174a624c49d276801b0f76089e3378cf5949b7a8fdad64494d89db0
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,48 @@
1
+
2
+ # Changelog
3
+
4
+ All notable changes to this project will be documented in this file.
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/).
6
+ This project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## [unreleased] —
9
+
10
+ ### Added
11
+
12
+ ### Changed
13
+
14
+ ### Fixed
15
+
16
+ ## [2.2.0] — 2020-11-24
17
+
18
+ ### Added
19
+
20
+ - support for windows (#19 #23) by [@mohits]
21
+
22
+ ### Changed
23
+
24
+ - Speedup calculation by avoiding calling pow(10,x) (#22) by [@mohits]
25
+
26
+ ### Fixed
27
+
28
+ - move from travis to github actions
29
+
30
+ [@mohits]: https://github.com/mohits
31
+
32
+ ## [2.1.0] — 2020-04-30
33
+
34
+ ### Added
35
+
36
+ - A Require ptah that match the gem name (#18)
37
+
38
+ ## [2.0.1] — 2020-04-02
39
+
40
+ ### Fixed
41
+
42
+ - Broken behavior when approaching the chunk size limit (#16)
43
+
44
+
45
+ [unreleased]: https://github.com/klaxit/fast-polylines/compare/v2.2.0...HEAD
46
+ [2.2.0]: https://github.com/klaxit/fast-polylines/compare/v2.1.0...v2.2.0
47
+ [2.1.0]: https://github.com/klaxit/fast-polylines/compare/v2.0.1...v2.1.0
48
+ [2.0.1]: https://github.com/klaxit/fast-polylines/compare/v2.0.0...v2.0.1
data/README.md CHANGED
@@ -1,50 +1,146 @@
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 of FastPolylines includes breaking changes, see [Migrate from V1](#migrate-from-V1)
10
9
 
11
- But an **at least 5x faster** implementation.
12
10
 
13
- ## Install
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:
14
14
 
15
15
  ```
16
- gem install fast-polylines
16
+ ——————————————————————————————— ENCODING ————————————————————————————————
17
+
18
+ Warming up --------------------------------------
19
+ Polylines 310.000 i/100ms
20
+ FastPolylinesV1 2.607k i/100ms
21
+ FastPolylinesV2 59.833k i/100ms
22
+ Calculating -------------------------------------
23
+ Polylines 2.957k (± 5.9%) i/s - 14.880k in 5.049867s
24
+ FastPolylinesV1 25.644k (± 5.8%) i/s - 127.743k in 4.999954s
25
+ FastPolylinesV2 682.981k (± 7.7%) i/s - 3.410M in 5.025952s
26
+
27
+ Comparison:
28
+ FastPolylinesV2: 682980.7 i/s
29
+ FastPolylinesV1: 25643.7 i/s - 26.63x slower
30
+ Polylines: 2957.1 i/s - 230.97x slower
31
+
32
+
33
+ ——————————————————————————————— DECODING ————————————————————————————————
34
+
35
+ Warming up --------------------------------------
36
+ Polylines 127.000 i/100ms
37
+ FastPolylinesV1 1.225k i/100ms
38
+ FastPolylinesV2 40.667k i/100ms
39
+ Calculating -------------------------------------
40
+ Polylines 1.289k (± 6.1%) i/s - 6.477k in 5.046552s
41
+ FastPolylinesV1 15.445k (± 4.4%) i/s - 77.175k in 5.006896s
42
+ FastPolylinesV2 468.413k (± 7.8%) i/s - 2.359M in 5.068936s
43
+
44
+ Comparison:
45
+ FastPolylinesV2: 468412.8 i/s
46
+ FastPolylinesV1: 15445.4 i/s - 30.33x slower
47
+ Polylines: 1288.8 i/s - 363.46x slower
17
48
  ```
18
49
 
19
- or in Bundler:
50
+ ## Install
51
+
52
+ ```bash
53
+ gem install fast-polylines
20
54
  ```
21
- gem "fast-polylines"
55
+
56
+ or in your `Gemfile`:
57
+ ```ruby
58
+ gem "fast-polylines", "~> 2.0.0"
22
59
  ```
23
60
 
24
61
  ## Usage
25
62
 
26
- ```
27
- require 'fast-polylines'
63
+ ```ruby
64
+ require "fast_polylines"
28
65
 
29
- 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]])
30
67
  # "_p~iF~ps|U_ulLnnqC_mqNvxq`@"
31
68
 
32
- FastPolylines::Decoder.decode("_p~iF~ps|U_ulLnnqC_mqNvxq`@")
69
+ FastPolylines.decode("_p~iF~ps|U_ulLnnqC_mqNvxq`@")
33
70
  # [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
34
71
  ```
35
72
 
36
73
  ## Advanced usage
37
74
 
38
- * With a different precision (default precision of `1e5`) :
75
+ **Use a different precision**
39
76
 
40
- ```
41
- 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`:
78
+ ```ruby
79
+ FastPolylines.encode([[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]], 6)
42
80
  # "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI"
43
81
 
44
- FastPolylines::Decoder.decode("_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI", 1e6)
82
+ FastPolylines.decode("_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI", 6)
45
83
  # [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]]
46
84
  ```
85
+ The precision max is `13`.
86
+
87
+ ## Migrate from V1
88
+
89
+ **TL;DR:**
90
+
91
+ ```ruby
92
+ # before
93
+ require "fast-polylines"
94
+ FastPolylines::Encoder.encode([[1.2, 1.2], [2.4, 2.4]], 1e6)
95
+ # after
96
+ require "fast_polylines"
97
+ FastPolylines.encode([[1.2, 1.2], [2.4, 2.4]], 6)
98
+ ```
99
+
100
+ **Detailled:**
101
+
102
+ The new version of `FastPolylines` doesn't support precision more than `1e13`,
103
+ you should not consider using it anyway since [it is way too precise][xkcd].
104
+
105
+ `Encoder` and `Decoder` modules are deprecated in favor of the single parent
106
+ module. Even though you can still use those, a deprecation warning will be
107
+ printed.
108
+
109
+ The precision is now an integer representing the number of decimals. It is
110
+ slightly smaller, and mostly this will avoid having any float value as
111
+ precision.
112
+
113
+ The file name to require is now snake_cased, you'll have to require
114
+ `fast_polylines`. The gem name stays the same however.
115
+
116
+ ## Run the Benchmark
117
+
118
+ You can run the benchmark with `make benchmark`.
119
+
120
+ ## Contributing
121
+
122
+ ```bash
123
+ git clone git@github.com:klaxit/fast-polylines
124
+ cd fast-polylines
125
+ bundle install
126
+ # Implement a feature, resolve a bug...
127
+ make rubocop
128
+ make test
129
+ git commit "My new feature!"
130
+ # Make a PR
131
+ ```
132
+
133
+ There is a `make console` command as well to open a ruby console with the
134
+ current version loaded.
135
+
136
+ [And here's a good starting point for Ruby C extensions knowledge.][ruby-c]
47
137
 
48
138
  ## License
49
139
 
50
140
  Please see LICENSE
141
+
142
+
143
+ [algorithm]: https://code.google.com/apis/maps/documentation/utilities/polylinealgorithm.html
144
+ [polylines]: https://github.com/joshuaclayton/polylines
145
+ [xkcd]: https://xkcd.com/2170/
146
+ [ruby-c]: https://github.com/ruby/ruby/blob/master/doc/extension.rdoc
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ if ENV["DEBUG"]
6
+ warn "DEBUG MODE."
7
+ $CFLAGS << " " << %w(
8
+ -Wall
9
+ -ggdb
10
+ -DDEBUG
11
+ -pedantic
12
+ ) * " "
13
+ end
14
+ create_makefile "fast_polylines/fast_polylines"
@@ -0,0 +1,173 @@
1
+ #include <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
+ typedef unsigned int uint;
28
+
29
+ // Speed up the code a bit by not calling pow(10.0, precision) since it is
30
+ // always 10^precision. Convert that into a lookup instead of doing the
31
+ // calculation.
32
+ // The precision parameter is already checked in +_get_precision()+ to ensure
33
+ // that it is within the range.
34
+ static inline double _fast_pow10(uint precision) {
35
+ const double lookup[MAX_PRECISION + 1] = {
36
+ 1.0, 10.0, 1.0e+2, 1.0e+3, 1.0e+4, 1.0e+5, 1.0e+6, 1.0e+7,
37
+ 1.0e+8, 1.0e+9, 1.0e+10, 1.0e+11, 1.0e+12, 1.0e+13
38
+ };
39
+ return lookup[precision];
40
+ }
41
+
42
+ static inline uint _get_precision(VALUE value) {
43
+ int precision = NIL_P(value) ? DEFAULT_PRECISION : NUM2INT(value);
44
+ if (precision > MAX_PRECISION) rb_raise(rb_eArgError, "precision too high (https://xkcd.com/2170/)");
45
+ if (precision < 0) rb_raise(rb_eArgError, "negative precision doesn't make sense");
46
+ return (uint)precision;
47
+ }
48
+
49
+ static inline VALUE
50
+ rb_FastPolylines__decode(int argc, VALUE *argv, VALUE self) {
51
+ rb_check_arity(argc, 1, 2);
52
+ Check_Type(argv[0], T_STRING);
53
+ char* polyline = StringValueCStr(argv[0]);
54
+ uint precision = _get_precision(argc > 1 ? argv[1] : Qnil);
55
+ double precision_value = _fast_pow10(precision);
56
+
57
+ VALUE ary = rb_ary_new();
58
+ // Helps keeping track of whether we are computing lat (0) or lng (1).
59
+ uint8_t index = 0;
60
+ size_t shift = 0;
61
+ int64_t delta = 0;
62
+ VALUE sub_ary[2];
63
+ double latlng[2] = {0.0, 0.0};
64
+ // Loops until end of string nul character is encountered.
65
+ while (*polyline) {
66
+ int64_t chunk = *polyline++;
67
+
68
+ if (chunk < 63 || chunk > 126) {
69
+ rb_raise(rb_eArgError, "invalid character '%c'", (char)chunk);
70
+ }
71
+
72
+ chunk -= 63;
73
+ delta = delta | ((chunk & ~0x20) << shift);
74
+ shift += 5;
75
+
76
+ if (!(chunk & 0x20)) {
77
+ delta = (delta & 1) ? ~(delta >> 1) : (delta >> 1);
78
+ latlng[index] += delta;
79
+ sub_ary[index] = rb_float_new((double) latlng[index] / precision_value);
80
+ // When both coordinates are parsed, we can push those to the result ary.
81
+ if (index) rb_ary_push(ary, rb_ary_new_from_values(2, sub_ary));
82
+ // Reinitilize since we are done for current coordinate.
83
+ index = 1 - index; delta = 0; shift = 0;
84
+ }
85
+ }
86
+ return ary;
87
+ }
88
+
89
+ static inline uint8_t
90
+ _polyline_encode_number(char *chunks, int64_t number) {
91
+ dbg("_polyline_encode_number(\"%s\", %lli)\n", chunks, number);
92
+ number = number < 0 ? ~(number << 1) : (number << 1);
93
+ uint8_t i = 0;
94
+ while (number >= 0x20) {
95
+ uint8_t chunk = number & 0x1f;
96
+ chunks[i++] = (0x20 | chunk) + 63;
97
+ number = number >> 5;
98
+ }
99
+ chunks[i++] = number + 63;
100
+ dbg("%u encoded chunks\n", i);
101
+ dbg("chunks: %s\n", chunks);
102
+ dbg("/_polyline_encode_number\n");
103
+ return i;
104
+ }
105
+
106
+ static inline VALUE
107
+ rb_FastPolylines__encode(int argc, VALUE *argv, VALUE self) {
108
+ rb_check_arity(argc, 1, 2);
109
+ Check_Type(argv[0], T_ARRAY);
110
+ size_t len = RARRAY_LEN(argv[0]);
111
+ uint64_t i;
112
+ VALUE current_pair;
113
+ int64_t prev_pair[2] = {0, 0};
114
+ uint precision = _get_precision(argc > 1 ? argv[1] : Qnil);
115
+ dbg("rb_FastPolylines__encode(..., %u)\n", precision);
116
+ rdbg(argv[0]);
117
+ double precision_value = _fast_pow10(precision);
118
+
119
+ // This is the maximum possible size the polyline may have. This
120
+ // being **without** null character. To copy it later as a Ruby
121
+ // string we'll have to use `rb_str_new` with the length.
122
+ dbg("allocated size: %u * 2 * %lu = %lu\n",
123
+ MAX_ENCODED_CHUNKS(precision),
124
+ len,
125
+ MAX_ENCODED_CHUNKS(precision) * 2 * len);
126
+ char *chunks = malloc(MAX_ENCODED_CHUNKS(precision) * 2 * len * sizeof(char));
127
+ size_t chunks_index = 0;
128
+ for (i = 0; i < len; i++) {
129
+ current_pair = RARRAY_AREF(argv[0], i);
130
+ uint8_t j;
131
+ Check_Type(current_pair, T_ARRAY);
132
+ if (RARRAY_LEN(current_pair) != 2) {
133
+ free(chunks);
134
+ rb_raise(rb_eArgError, "wrong number of coordinates");
135
+ }
136
+ for (j = 0; j < 2; j++) {
137
+ VALUE current_value = RARRAY_AREF(current_pair, j);
138
+ switch (TYPE(current_value)) {
139
+ case T_BIGNUM:
140
+ case T_FLOAT:
141
+ case T_FIXNUM:
142
+ case T_RATIONAL:
143
+ break;
144
+ default:
145
+ free(chunks);
146
+ rb_raise(rb_eTypeError, "no implicit conversion to Float from %s", rb_obj_classname(current_value));
147
+ };
148
+
149
+ double parsed_value = NUM2DBL(current_value);
150
+ if (-180.0 > parsed_value || parsed_value > 180.0) {
151
+ free(chunks);
152
+ rb_raise(rb_eArgError, "coordinates must be between -180.0 and 180.0");
153
+ }
154
+ int64_t rounded_value = round(parsed_value * precision_value);
155
+ int64_t delta = rounded_value - prev_pair[j];
156
+ prev_pair[j] = rounded_value;
157
+ // We pass a pointer to the current chunk that need to be filled. Doing so
158
+ // avoid having to copy the string every single iteration.
159
+ chunks_index += _polyline_encode_number(chunks + chunks_index * sizeof(char), delta);
160
+ dbg("%s\n", chunks);
161
+ }
162
+ }
163
+ dbg("final chunks_index: %zu\n", chunks_index);
164
+ VALUE polyline = rb_str_new(chunks, chunks_index);
165
+ free(chunks);
166
+ return polyline;
167
+ }
168
+
169
+ void Init_fast_polylines() {
170
+ VALUE mFastPolylines = rb_define_module("FastPolylines");
171
+ rb_define_module_function(mFastPolylines, "decode", rb_FastPolylines__decode, -1);
172
+ rb_define_module_function(mFastPolylines, "encode", rb_FastPolylines__encode, -1);
173
+ }
@@ -1,5 +1,3 @@
1
- require "fast-polylines/encoder"
2
- require "fast-polylines/decoder"
3
-
4
- module FastPolylines
5
- end
1
+ # frozen_string_literal: true
2
+ # Allow both snake_case and kebab-case way to require
3
+ require_relative "fast_polylines"
@@ -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.2.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ require "fast-polylines"
2
+
3
+ describe FastPolylines do
4
+ let(:points) { [[38.5, -120.2], [40.7, -120.95], [43.252, -126.453]] }
5
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
6
+ it "should work with kebab-case requirement" do
7
+ expect(FastPolylines.encode(points)).to eq polyline
8
+ expect(FastPolylines.decode(polyline)).to eq points
9
+ end
10
+ end
@@ -0,0 +1,119 @@
1
+ require "fast_polylines"
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
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
7
+ context "with default precision" do
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
+ let(:polyline) { "_p~iF~ps|U_ulLnnqC_mqNvxq`@" }
52
+ it "should raise for invalid input" do
53
+ expect { described_class.encode(points[0]) }.to raise_error(
54
+ TypeError,
55
+ "wrong argument type Float (expected Array)"
56
+ )
57
+ expect { described_class.encode([points]) }.to raise_error(
58
+ ArgumentError,
59
+ "wrong number of coordinates"
60
+ )
61
+ expect { described_class.encode([points[0..1]]) }.to raise_error(
62
+ TypeError,
63
+ "no implicit conversion to Float from Array"
64
+ )
65
+ end
66
+ # The method `_polyline_encode_number("", 16)` will check for a chunk
67
+ # of the size 32, which is the chunk size limit. This was errored due to
68
+ # a bad sign.
69
+ # Reported in issue #15, closed in PR #16
70
+ context "with points close to the chunk size limit" do
71
+ let(:points) { [[0, 0.00016]] }
72
+ let(:polyline) { "?_@" }
73
+ it "should encode points correctly" do
74
+ expect(described_class.encode(points)).to eq polyline
75
+ end
76
+ end
77
+ context "with default precision" do
78
+ it "should encode points correctly" do
79
+ expect(described_class.encode(points)).to eq polyline
80
+ end
81
+ end
82
+ context "with a 6 decimals precision" do
83
+ let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
84
+ it "should encode points correctly" do
85
+ expect(described_class.encode(points, 6)).to eq polyline
86
+ end
87
+ end
88
+ context "with points that were close together" do
89
+ let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
90
+ it "should encode points correctly" do
91
+ expect(described_class.encode(points)).to eq "krk{FdxdlO?e@"
92
+ end
93
+ end
94
+ context "with possible rounding ambiguity" do
95
+ let(:points) { [[39.13594499,-94.4243478], [39.13558757,-94.4243471]] }
96
+ it "should encode points as Google API do" do
97
+ expect(described_class.encode(points)).to eq "svzmFdgi_QdA?"
98
+ end
99
+ end
100
+ context "with points out of bounds" do
101
+ it "should raise" do
102
+ expect { described_class.encode([[180.1, 0.0]]) }.to raise_error ArgumentError
103
+ expect { described_class.encode([[0, 180.1]]) }.to raise_error ArgumentError
104
+ expect { described_class.encode([[-180.1, 0.0]]) }.to raise_error ArgumentError
105
+ expect { described_class.encode([[0, -180.1]]) }.to raise_error ArgumentError
106
+ end
107
+ end
108
+ context "with a 15 decimals precision" do
109
+ it "should raise" do
110
+ expect { described_class.encode([[1.2, 1.2]], 15) }.to raise_error(ArgumentError, /too high/)
111
+ end
112
+ end
113
+ context "with a negative precision" do
114
+ it "should raise" do
115
+ expect { described_class.encode([[1.2, 1.2]], -rand(1..10)) }.to raise_error(ArgumentError, /negative/)
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,11 +1,9 @@
1
1
  require "rubygems"
2
2
  require "bundler/setup"
3
3
 
4
- require "fast-polylines"
5
-
6
- require "rspec-benchmark"
7
- require "polylines"
4
+ $LOAD_PATH.unshift File.join(__dir__, "..", "lib")
5
+ $LOAD_PATH.unshift File.join(__dir__, "..", "ext")
8
6
 
9
7
  RSpec.configure do |config|
10
- include RSpec::Benchmark::Matchers
8
+ # Additional config goes here
11
9
  end
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: 0.1.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyrille Courtière
8
- autorequire:
8
+ - Ulysse Buonomo
9
+ autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2018-10-22 00:00:00.000000000 Z
12
+ date: 2020-11-24 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
- name: rspec
15
+ name: benchmark-ips
15
16
  requirement: !ruby/object:Gem::Requirement
16
17
  requirements:
17
18
  - - "~>"
18
19
  - !ruby/object:Gem::Version
19
- version: '3.5'
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: '3.5'
27
+ version: '2.7'
27
28
  - !ruby/object:Gem::Dependency
28
29
  name: polylines
29
30
  requirement: !ruby/object:Gem::Requirement
@@ -39,39 +40,43 @@ dependencies:
39
40
  - !ruby/object:Gem::Version
40
41
  version: '0.3'
41
42
  - !ruby/object:Gem::Dependency
42
- name: rspec-benchmark
43
+ name: rspec
43
44
  requirement: !ruby/object:Gem::Requirement
44
45
  requirements:
45
46
  - - "~>"
46
47
  - !ruby/object:Gem::Version
47
- version: '0.3'
48
+ version: '3.5'
48
49
  type: :development
49
50
  prerelease: false
50
51
  version_requirements: !ruby/object:Gem::Requirement
51
52
  requirements:
52
53
  - - "~>"
53
54
  - !ruby/object:Gem::Version
54
- version: '0.3'
55
- description:
55
+ version: '3.5'
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:
64
+ - ".rspec"
65
+ - CHANGELOG.md
62
66
  - README.md
67
+ - ext/fast_polylines/extconf.rb
68
+ - ext/fast_polylines/fast_polylines.c
63
69
  - 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
70
+ - lib/fast_polylines.rb
71
+ - lib/fast_polylines/version.rb
72
+ - spec/fast-polylines_spec.rb
73
+ - spec/fast_polylines_spec.rb
69
74
  - spec/spec_helper.rb
70
- homepage: http://github.com/klaxit/fast-polylines
75
+ homepage: https://github.com/klaxit/fast-polylines
71
76
  licenses:
72
77
  - MIT
73
78
  metadata: {}
74
- post_install_message:
79
+ post_install_message:
75
80
  rdoc_options: []
76
81
  require_paths:
77
82
  - lib
@@ -79,19 +84,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
79
84
  requirements:
80
85
  - - ">="
81
86
  - !ruby/object:Gem::Version
82
- version: '0'
87
+ version: 2.4.6
83
88
  required_rubygems_version: !ruby/object:Gem::Requirement
84
89
  requirements:
85
90
  - - ">="
86
91
  - !ruby/object:Gem::Version
87
92
  version: '0'
88
93
  requirements: []
89
- rubyforge_project:
90
- rubygems_version: 2.5.2
91
- signing_key:
94
+ rubygems_version: 3.1.2
95
+ signing_key:
92
96
  specification_version: 4
93
97
  summary: Fast & easy Google polylines
94
98
  test_files:
95
- - spec/fast-polylines/decoder_spec.rb
96
- - spec/fast-polylines/encoder_spec.rb
97
99
  - spec/spec_helper.rb
100
+ - spec/fast-polylines_spec.rb
101
+ - spec/fast_polylines_spec.rb
102
+ - ".rspec"
@@ -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 = "0.1.1".freeze
3
- end
@@ -1,39 +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
- it "should perform at least 5x faster than the Polylines gem" do
12
- expect {
13
- described_class.decode(polyline)
14
- }.to perform_faster_than {
15
- Polylines::Decoder.decode_polyline(polyline)
16
- }.at_least(5).times
17
- end
18
- end
19
- context "with 1e6 precision" do
20
- let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
21
- it "should decode a polyline correctly" do
22
- expect(described_class.decode(polyline, 1e6)).to eq points
23
- end
24
- end
25
- context "with points that were close together" do
26
- let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
27
- it "should decode a polyline correctly" do
28
- expect(described_class.decode("krk{FdxdlO?e@")).to eq points
29
- end
30
- end
31
- context "with points that cause float overflow" do
32
- let(:polyline) { "ahdiHsmeMHPDN|A`FVt@" }
33
- let(:points) { [[48.85137, 2.32682], [48.85132, 2.32673], [48.85129, 2.32665], [48.85082, 2.32552], [48.8507, 2.32525]] }
34
- it "should respect precision" do
35
- expect(described_class.decode(polyline)).to eq points
36
- end
37
- end
38
- end
39
- end
@@ -1,38 +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
- it "should perform at least 5x faster than the Polylines gem" do
12
- expect {
13
- described_class.encode(points)
14
- }.to perform_faster_than {
15
- Polylines::Encoder.encode_points(points)
16
- }.at_least(5).times
17
- end
18
- end
19
- context "with 1e6 precision" do
20
- let(:polyline) { "_izlhA~rlgdF_{geC~ywl@_kwzCn`{nI" }
21
- it "should encode points correctly" do
22
- expect(described_class.encode(points, 1e6)).to eq polyline
23
- end
24
- end
25
- context "with points that were close together" do
26
- let(:points) { [[41.35222, -86.04563], [41.35222, -86.04544]] }
27
- it "should encode points correctly" do
28
- expect(described_class.encode(points)).to eq "krk{FdxdlO?e@"
29
- end
30
- end
31
- context "with possible rounding ambiguity" do
32
- let(:points) { [[39.13594499,-94.4243478], [39.13558757,-94.4243471]] }
33
- it "should encode points as Google API do" do
34
- expect(described_class.encode(points)).to eq "svzmFdgi_QdA?"
35
- end
36
- end
37
- end
38
- end