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 +4 -4
- data/README.md +87 -31
- data/ext/fast_polylines/.gitignore +3 -0
- data/ext/fast_polylines/extconf.rb +4 -0
- data/ext/fast_polylines/fast_polylines.c +151 -0
- data/lib/fast_polylines.rb +19 -0
- data/lib/fast_polylines/version.rb +5 -0
- data/spec/fast_polylines_spec.rb +94 -0
- data/spec/spec_helper.rb +1 -1
- metadata +18 -17
- data/lib/fast-polylines.rb +0 -5
- data/lib/fast-polylines/decoder.rb +0 -41
- data/lib/fast-polylines/encoder.rb +0 -47
- data/lib/fast-polylines/version.rb +0 -3
- data/spec/fast-polylines/decoder_spec.rb +0 -32
- data/spec/fast-polylines/encoder_spec.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a3a21f83abca7c1b9ef3bb824145ceea3189fbdb9d428a2faff1daa94e2a50fb
|
4
|
+
data.tar.gz: 0e6578cb705d0121daadb0a019bedd2d0d1b2589b2905134f933f4ae0859d3e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
[](https://badge.fury.io/rb/fast-polylines)
|
4
|
-
[](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
|
-
|
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
|
-
|
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
|
-
|
16
|
+
——————————————————————————————— ENCODING ————————————————————————————————
|
15
17
|
|
16
18
|
Warming up --------------------------------------
|
17
|
-
|
18
|
-
|
19
|
+
Polylines 277.000 i/100ms
|
20
|
+
FastPolylinesV1 2.050k i/100ms
|
21
|
+
FastPolylinesV2 73.822k i/100ms
|
19
22
|
Calculating -------------------------------------
|
20
|
-
|
21
|
-
|
23
|
+
Polylines 3.254k (± 1.8%) i/s - 16.343k in 5.023767s
|
24
|
+
FastPolylinesV1 25.715k (± 3.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
|
-
|
25
|
-
|
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
|
-
|
33
|
+
——————————————————————————————— DECODING ————————————————————————————————
|
29
34
|
|
30
35
|
Warming up --------------------------------------
|
31
|
-
|
32
|
-
|
36
|
+
Polylines 140.000 i/100ms
|
37
|
+
FastPolylinesV1 1.602k i/100ms
|
38
|
+
FastPolylinesV2 36.432k i/100ms
|
33
39
|
Calculating -------------------------------------
|
34
|
-
|
35
|
-
|
40
|
+
Polylines 1.401k (± 2.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
|
-
|
39
|
-
|
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
|
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
|
63
|
+
```ruby
|
64
|
+
require "fast-polylines"
|
57
65
|
|
58
|
-
FastPolylines
|
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
|
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
|
-
|
75
|
+
**Use a different precision**
|
68
76
|
|
69
|
-
|
70
|
-
|
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
|
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,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,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
|
data/spec/spec_helper.rb
CHANGED
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:
|
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:
|
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: '
|
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: '
|
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
|
-
-
|
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
|
-
-
|
64
|
-
-
|
65
|
-
-
|
66
|
-
- lib/
|
67
|
-
-
|
68
|
-
- spec/
|
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:
|
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/
|
95
|
-
- spec/fast-polylines/encoder_spec.rb
|
96
|
+
- spec/fast_polylines_spec.rb
|
96
97
|
- spec/spec_helper.rb
|
data/lib/fast-polylines.rb
DELETED
@@ -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,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
|