diff --git a/.gitignore b/.gitignore index dde3895..f33df99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .DS_Store *.pyc +cartodb_services.egg-info/ +build/ +dist/ diff --git a/server/lib/python/cartodb_services/cartodb_services/mapzen/routing.py b/server/lib/python/cartodb_services/cartodb_services/mapzen/routing.py index 2b08d46..be9aaff 100644 --- a/server/lib/python/cartodb_services/cartodb_services/mapzen/routing.py +++ b/server/lib/python/cartodb_services/cartodb_services/mapzen/routing.py @@ -1,10 +1,9 @@ import requests import json import re -from polyline.codec import PolylineCodec from exceptions import WrongParams -from cartodb_services.tools import Coordinate +from cartodb_services.tools import Coordinate, PolyLine class MapzenRouting: @@ -74,7 +73,7 @@ class MapzenRouting: try: parsed_json_response = json.loads(response) legs = parsed_json_response['trip']['legs'][0] - shape = PolylineCodec().decode(legs['shape']) + shape = PolyLine().decode(legs['shape']) length = legs['summary']['length'] duration = legs['summary']['time'] routing_response = MapzenRoutingResponse(shape, length, duration) diff --git a/server/lib/python/cartodb_services/cartodb_services/tools/__init__.py b/server/lib/python/cartodb_services/cartodb_services/tools/__init__.py index 200dcd4..dbf91b5 100644 --- a/server/lib/python/cartodb_services/cartodb_services/tools/__init__.py +++ b/server/lib/python/cartodb_services/cartodb_services/tools/__init__.py @@ -1,2 +1,3 @@ from redis_tools import RedisConnection -from coordinates import Coordinate \ No newline at end of file +from coordinates import Coordinate +from polyline import PolyLine diff --git a/server/lib/python/cartodb_services/cartodb_services/tools/polyline.py b/server/lib/python/cartodb_services/cartodb_services/tools/polyline.py new file mode 100644 index 0000000..a903e57 --- /dev/null +++ b/server/lib/python/cartodb_services/cartodb_services/tools/polyline.py @@ -0,0 +1,51 @@ +from itertools import tee, izip +from math import trunc + + +class PolyLine: + """ Polyline decoder https://developers.google.com/maps/documentation/utilities/polylinealgorithm?csw=1 """ + + def decode(self, data): + coordinates = [] + chunks = self._extract_chunks(data) + for chunk in chunks: + coordinate = self._process_chunk(chunk) + coordinate /= 1e5 + print coordinate + if len(coordinates) > 1: + # We have to sum the previous with the offset in this chunk + coordinate += coordinates[-2] + coordinates.append(round(coordinate, 5)) + + print coordinates + + return zip(coordinates, coordinates[1:])[::2] + + def _extract_chunks(self, data): + chunks, chunk = [], [] + for character in data: + byte = ord(character) - 63 + if byte & 0x20 > 0: + byte &= 0x1F + chunk.append(byte) + else: + chunk.append(byte) + chunks.append(chunk) + chunk = [] + + return chunks + + def _process_chunk(self, chunk): + coordinate = self._get_coordinate(chunk) + # Check if the coordinate is negative + if coordinate & 0x1: + return ~(coordinate >> 1) + else: + return coordinate >> 1 + + def _get_coordinate(self, chunk): + coordinate = 0 + for i, c in enumerate(chunk): + coordinate |= c << (i * 5) + + return coordinate diff --git a/server/lib/python/cartodb_services/requirements.txt b/server/lib/python/cartodb_services/requirements.txt index 1363d69..17e41c8 100644 --- a/server/lib/python/cartodb_services/requirements.txt +++ b/server/lib/python/cartodb_services/requirements.txt @@ -5,7 +5,6 @@ python-dateutil==2.2 googlemaps==2.4.2 # Dependency for googlemaps package requests<=2.9.1 -polyline==1.1 # Test mock==1.3.0 diff --git a/server/lib/python/cartodb_services/setup.py b/server/lib/python/cartodb_services/setup.py index 408f147..550bfbd 100644 --- a/server/lib/python/cartodb_services/setup.py +++ b/server/lib/python/cartodb_services/setup.py @@ -10,7 +10,7 @@ from setuptools import setup, find_packages setup( name='cartodb_services', - version='0.3.0', + version='0.3.1', description='CartoDB Services API Python Library', diff --git a/server/lib/python/cartodb_services/test/test_polyline.py b/server/lib/python/cartodb_services/test/test_polyline.py new file mode 100644 index 0000000..9d5be45 --- /dev/null +++ b/server/lib/python/cartodb_services/test/test_polyline.py @@ -0,0 +1,39 @@ +from cartodb_services.tools import PolyLine +from unittest import TestCase + + +class TestPolyline(TestCase): + + def setUp(self): + self.polyline = PolyLine() + + def test_should_decode_a_chunk_correctly(self): + decoded_polyline = self.polyline.decode('`~oia@`~oia@') + original_value = [(-179.98321, -179.98321)] + + assert decoded_polyline == original_value + + def test_should_decode_polyline_correctly(self): + original_polyline_1 = [(38.5, -120.2), + (40.7, -120.95), + (43.252, -126.453)] + decoded_polyline_1 = self.polyline.decode('_p~iF~ps|U_ulLnnqC_mqNvxq`@') + + assert decoded_polyline_1 == original_polyline_1 + + original_polyline_2 = [(17.95783,-5.58105), + (15.79225,2.90039), + (7.60211,-10.76660)] + decoded_polyline_2 = self.polyline.decode('mkrlBp`aa@z}eL_pwr@js~p@tilrA') + + assert decoded_polyline_2 == original_polyline_2 + + original_polyline_3 = [(62.75473,-157.14844), + (65.07213,169.80469) , + (48.92250,158.55469), + (44.33957,-150.46875)] + decoded_polyline_3 = self.polyline.decode('ax_~Jv`d~\wrcMa`qj}@dfqaBngtcAhb~Zncc}y@') + + assert decoded_polyline_3 == original_polyline_3 + + diff --git a/test/integration/test_routing_functions.py b/test/integration/test_routing_functions.py new file mode 100644 index 0000000..3f02f01 --- /dev/null +++ b/test/integration/test_routing_functions.py @@ -0,0 +1,34 @@ +from unittest import TestCase +from nose.tools import assert_raises +from nose.tools import assert_not_equal, assert_equal +from ..helpers.integration_test_helper import IntegrationTestHelper + + +class TestRoutingFunctions(TestCase): + + def setUp(self): + self.env_variables = IntegrationTestHelper.get_environment_variables() + self.sql_api_url = "https://{0}.{1}/api/v2/sql".format( + self.env_variables['username'], + self.env_variables['host'], + self.env_variables['api_key'] + ) + + def test_if_select_with_routing_point_to_point_is_ok(self): + query = "SELECT duration, length, shape as the_geom " \ + "FROM cdb_route_point_to_point('POINT(-3.70237112 40.41706163)'::geometry, " \ + "'POINT(-3.69909883 40.41236875)'::geometry, 'car', " \ + "ARRAY['mode_type=shortest']::text[])&api_key={0}".format( + self.env_variables['api_key']) + routing = IntegrationTestHelper.execute_query(self.sql_api_url, query) + assert_not_equal(routing['the_geom'], None) + + def test_if_select_with_routing_point_to_point_without_api_key_raise_error(self): + query = "SELECT duration, length, shape as the_geom " \ + "FROM cdb_route_point_to_point('POINT(-3.70237112 40.41706163)'::geometry, " \ + "'POINT(-3.69909883 40.41236875)'::geometry, 'car', " \ + "ARRAY['mode_type=shortest']::text[])" + try: + IntegrationTestHelper.execute_query(self.sql_api_url, query) + except Exception as e: + assert_equal(e.message[0], "The api_key must be provided")