Repository: mapbox/mapbox-sdk-py (python)

Issue:
Problem with polyline module dependencies
@perrygeo I've traced our issues with failing tests on Travis to the `polyline` module requiring exactly one version of `six`. As shown by `pipdeptree`:

```polyline==1.1 -> six [required: ==1.8.0, installed: 1.8.0]```

Everybody would be better off if the requirement were `six>=1.8.0`. I'm going to pursue this upstream and pin the version locally for testing purposes.

Pull request:
Vendorize polyline
Closes #74

Patch:
diff --git a/mapbox/encoding.py b/mapbox/encoding.py
index 62259d4..f009fb8 100644
--- a/mapbox/encoding.py
+++ b/mapbox/encoding.py
@@ -1,6 +1,7 @@
-from polyline.codec import PolylineCodec
 import json
 
+from .polyline.codec import PolylineCodec
+
 
 def _geom_points(geom):
     """GeoJSON geometry to a sequence of point tuples
diff --git a/mapbox/polyline/LICENSE b/mapbox/polyline/LICENSE
new file mode 100644
index 0000000..dd5d6d2
--- /dev/null
+++ b/mapbox/polyline/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Bruno M. Custódio
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/mapbox/polyline/README.rst b/mapbox/polyline/README.rst
new file mode 100644
index 0000000..e103b78
--- /dev/null
+++ b/mapbox/polyline/README.rst
@@ -0,0 +1,22 @@
+polyline
+========
+
+.. image:: https://travis-ci.org/bmcustodio/polyline.svg?branch=master
+    :target: https://travis-ci.org/bmcustodio/polyline
+
+.. image:: https://badge.fury.io/py/polyline.svg
+    :target: http://badge.fury.io/py/polyline
+
+.. image:: https://pypip.in/d/polyline/badge.png
+        :target: https://crate.io/packages/polyline/
+
+``polyline`` is a Python implementation of Google's Encoded Polyline Algorithm
+Format (http://goo.gl/PvXf8Y). It is essentially a port of
+https://github.com/mapbox/polyline built with Python 2 and 3 support in mind.
+
+The documentation for ``polyline`` can be found at http://polyline.rtfd.org/.
+
+License
+-------
+
+MIT © `Bruno M. Custódio <mailto:bruno@brunomcustodio.com>`_
diff --git a/mapbox/polyline/__init__.py b/mapbox/polyline/__init__.py
new file mode 100644
index 0000000..439eb0c
--- /dev/null
+++ b/mapbox/polyline/__init__.py
@@ -0,0 +1,1 @@
+__version__ = '1.1'
diff --git a/mapbox/polyline/codec.py b/mapbox/polyline/codec.py
new file mode 100644
index 0000000..f9a6c6a
--- /dev/null
+++ b/mapbox/polyline/codec.py
@@ -0,0 +1,53 @@
+import itertools
+import six
+
+
+class PolylineCodec(object):
+    def _pcitr(self, iterable):
+        return six.moves.zip(iterable, itertools.islice(iterable, 1, None))
+
+    def _write(self, output, value):
+        coord = int(round(value * 1e5, 0))
+        coord <<= 1
+        coord = coord if coord >= 0 else ~coord
+
+        while coord >= 0x20:
+            output.write(six.unichr((0x20 | (coord & 0x1f)) + 63))
+            coord >>= 5
+
+        output.write(six.unichr(coord + 63))
+
+    def _trans(self, value, index):
+        byte, result, shift = None, 0, 0
+
+        while (byte is None or byte >= 0x20):
+            byte = ord(value[index]) - 63
+            index += 1
+            result |= (byte & 0x1f) << shift
+            shift += 5
+            comp = result & 1
+
+        return ~(result >> 1) if comp else (result >> 1), index
+
+    def decode(self, expression):
+        coordinates, index, lat, lng, length = [], 0, 0, 0, len(expression)
+
+        while (index < length):
+            lat_change, index = self._trans(expression, index)
+            lng_change, index = self._trans(expression, index)
+            lat += lat_change
+            lng += lng_change
+            coordinates.append((lat / 1e5, lng / 1e5))
+
+        return coordinates
+
+    def encode(self, coordinates):
+        output = six.StringIO()
+        self._write(output, coordinates[0][0])
+        self._write(output, coordinates[0][1])
+
+        for prev, curr in self._pcitr(coordinates):
+            self._write(output, curr[0] - prev[0])
+            self._write(output, curr[1] - prev[1])
+
+        return output.getvalue()
diff --git a/setup.py b/setup.py
index ec1ef25..52bcefc 100644
--- a/setup.py
+++ b/setup.py
@@ -35,7 +35,6 @@ setup(name='mapbox',
           'requests',
           'uritemplate.py',
           'boto3',
-          'polyline'
       ],
       extras_require={
           'test': ['coveralls', 'pytest', 'pytest-cov', 'responses']})


Test patch:
diff --git a/tests/test_polyline_codec.py b/tests/test_polyline_codec.py
new file mode 100644
index 0000000..cf9d12c
--- /dev/null
+++ b/tests/test_polyline_codec.py
@@ -0,0 +1,70 @@
+import unittest
+
+from mapbox.polyline.codec import PolylineCodec
+
+
+class PolylineCodecTestCase(unittest.TestCase):
+    def setUp(self):
+        self.codec = PolylineCodec()
+
+    def test_decode_multiple_points(self):
+        d = self.codec.decode('gu`wFnfys@???nKgE??gE?????oK????fE??fE')
+        self.assertEqual(d, [
+            (40.641, -8.654),
+            (40.641, -8.654),
+            (40.641, -8.656),
+            (40.642, -8.656),
+            (40.642, -8.655),
+            (40.642, -8.655),
+            (40.642, -8.655),
+            (40.642, -8.653),
+            (40.642, -8.653),
+            (40.642, -8.653),
+            (40.641, -8.653),
+            (40.641, -8.654)
+        ])
+
+    def test_decode_official_example(self):
+        d = self.codec.decode('_p~iF~ps|U_ulLnnqC_mqNvxq`@')
+        self.assertEqual(d, [
+            (38.500, -120.200),
+            (40.700, -120.950),
+            (43.252, -126.453)
+        ])
+
+    def test_decode_single_point(self):
+        d = self.codec.decode('gu`wFf`ys@')
+        self.assertEqual(d, [
+            (40.641, -8.653)
+        ])
+
+    def test_encode_multiple_points(self):
+        e = self.codec.encode([
+            (40.641, -8.654),
+            (40.641, -8.654),
+            (40.641, -8.656),
+            (40.642, -8.656),
+            (40.642, -8.655),
+            (40.642, -8.655),
+            (40.642, -8.655),
+            (40.642, -8.653),
+            (40.642, -8.653),
+            (40.642, -8.653),
+            (40.641, -8.653),
+            (40.641, -8.654)
+        ])
+        self.assertEqual(e, 'gu`wFnfys@???nKgE??gE?????oK????fE??fE')
+
+    def test_encode_official_example(self):
+        e = self.codec.encode([
+            (38.500, -120.200),
+            (40.700, -120.950),
+            (43.252, -126.453)
+        ])
+        self.assertEqual(e, '_p~iF~ps|U_ulLnnqC_mqNvxq`@')
+
+    def test_encode_single_point(self):
+        e = self.codec.encode([
+            (40.641, -8.653)
+        ])
+        self.assertEqual(e, 'gu`wFf`ys@')