fast-polylines 0.1.1 → 2.2.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
- 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