Pure Python implementation of clients for Mapbox services
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from routing import MapboxRouting, MapboxRoutingResponse
|
||||
from geocoder import MapboxGeocoder
|
||||
from isolines import MapboxIsolines
|
||||
from matrix_client import MapboxMatrixClient
|
||||
from exceptions import ServiceException
|
||||
@@ -0,0 +1,18 @@
|
||||
'''
|
||||
Exceptions for the Mapbox services Python wrapper.
|
||||
'''
|
||||
|
||||
|
||||
class ServiceException(Exception):
|
||||
'''
|
||||
Exception to be raised if any Service problem is found.
|
||||
'''
|
||||
|
||||
def __init__(self, code, message):
|
||||
self.code = code
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr('ServiceException ({code}): {message}'.format(
|
||||
code=self.code,
|
||||
message=self.message))
|
||||
@@ -0,0 +1,59 @@
|
||||
'''
|
||||
Python client for the Mapbox Geocoder service.
|
||||
'''
|
||||
|
||||
import json
|
||||
import requests
|
||||
from mapbox import Geocoder
|
||||
from cartodb_services.metrics import Traceable
|
||||
from cartodb_services.mapbox.exceptions import ServiceException
|
||||
|
||||
ACCESS_TOKEN = 'pk.eyJ1IjoiYWNhcmxvbiIsImEiOiJjamJuZjQ1Zjc0Ymt4Mnh0YmFrMmhtYnY4In0.gt9cw0VeKc3rM2mV5pcEmg'
|
||||
|
||||
EPHEMERAL_GEOCODER = 'mapbox.places'
|
||||
PERMANENT_GEOCODER = 'mapbox.places-permanent'
|
||||
DEFAULT_GEOCODER = EPHEMERAL_GEOCODER
|
||||
|
||||
ENTRY_FEATURES = 'features'
|
||||
ENTRY_CENTER = 'center'
|
||||
ENTRY_GEOMETRY = 'geometry'
|
||||
ENTRY_COORDINATES = 'coordinates'
|
||||
ENTRY_TYPE = 'type'
|
||||
TYPE_POINT = 'Point'
|
||||
|
||||
|
||||
class MapboxGeocoder(Traceable):
|
||||
'''
|
||||
Python wrapper for the Mapbox Geocoder service.
|
||||
'''
|
||||
|
||||
def __init__(self, token=ACCESS_TOKEN, name=DEFAULT_GEOCODER):
|
||||
self._token = token
|
||||
self._geocoder = Geocoder(access_token=self._token, name=name)
|
||||
|
||||
def _parse_geocoder_response(self, response):
|
||||
json_response = json.loads(response)
|
||||
feature = json_response[ENTRY_FEATURES][0]
|
||||
|
||||
return self._extract_lng_lat_from_feature(feature)
|
||||
|
||||
def _extract_lng_lat_from_feature(self, feature):
|
||||
geometry = feature[ENTRY_GEOMETRY]
|
||||
if geometry[ENTRY_TYPE] == TYPE_POINT:
|
||||
location = geometry[ENTRY_COORDINATES]
|
||||
else:
|
||||
location = feature[ENTRY_CENTER]
|
||||
|
||||
longitude = location[0]
|
||||
latitude = location[1]
|
||||
return [longitude, latitude]
|
||||
|
||||
def geocode(self, address, country=None):
|
||||
response = self._geocoder.forward(address=address,
|
||||
country=country,
|
||||
limit=1)
|
||||
|
||||
if response.status_code == requests.codes.ok:
|
||||
return self._parse_geocoder_response(response.text)
|
||||
else:
|
||||
raise ServiceException(response.status_code, response.content)
|
||||
@@ -0,0 +1,146 @@
|
||||
'''
|
||||
Python implementation for Mapbox services based isolines.
|
||||
Uses the Mapbox Time Matrix service.
|
||||
'''
|
||||
|
||||
import json
|
||||
from cartodb_services.tools.spherical import (get_angles,
|
||||
calculate_dest_location)
|
||||
from cartodb_services.mapbox.matrix_client import (validate_profile,
|
||||
DEFAULT_PROFILE,
|
||||
PROFILE_WALKING,
|
||||
PROFILE_DRIVING,
|
||||
PROFILE_CYCLING,
|
||||
ENTRY_DURATIONS)
|
||||
|
||||
MAX_SPEEDS = {
|
||||
PROFILE_WALKING: 3.3333333, # In m/s, assuming 12km/h walking speed
|
||||
PROFILE_CYCLING: 16.67, # In m/s, assuming 60km/h max speed
|
||||
PROFILE_DRIVING: 41.67 # In m/s, assuming 140km/h max speed
|
||||
}
|
||||
|
||||
DEFAULT_NUM_ANGLES = 24
|
||||
DEFAULT_MAX_ITERS = 5
|
||||
DEFAULT_TOLERANCE = 0.1
|
||||
|
||||
MATRIX_NUM_ANGLES = DEFAULT_NUM_ANGLES
|
||||
MATRIX_MAX_ITERS = DEFAULT_MAX_ITERS
|
||||
MATRIX_TOLERANCE = DEFAULT_TOLERANCE
|
||||
|
||||
UNIT_FACTOR_ISOCHRONE = 1.0
|
||||
UNIT_FACTOR_ISODISTANCE = 1000.0
|
||||
DEFAULT_UNIT_FACTOR = UNIT_FACTOR_ISOCHRONE
|
||||
|
||||
|
||||
class MapboxIsolines():
|
||||
'''
|
||||
Python wrapper for Mapbox services based isolines.
|
||||
'''
|
||||
|
||||
def __init__(self, matrix_client, routing_client):
|
||||
self._matrix_client = matrix_client
|
||||
self._routing_client = routing_client
|
||||
|
||||
def _calculate_matrix_cost(self, origin, targets, isorange,
|
||||
profile=DEFAULT_PROFILE,
|
||||
unit_factor=UNIT_FACTOR_ISOCHRONE,
|
||||
number_of_angles=MATRIX_NUM_ANGLES):
|
||||
response = self._matrix_client.matrix([origin] + targets,
|
||||
profile)
|
||||
json_response = json.loads(response)
|
||||
|
||||
costs = [None] * number_of_angles
|
||||
|
||||
for idx, cost in enumerate(json_response[ENTRY_DURATIONS][0][1:]):
|
||||
if cost:
|
||||
costs[idx] = cost * unit_factor
|
||||
else:
|
||||
costs[idx] = isorange
|
||||
|
||||
return costs
|
||||
|
||||
def calculate_isochrone(self, origin, time_range,
|
||||
profile=DEFAULT_PROFILE):
|
||||
validate_profile(profile)
|
||||
|
||||
max_speed = MAX_SPEEDS[profile]
|
||||
upper_rmax = max_speed * time_range # an upper bound for the radius
|
||||
|
||||
return self.calculate_isoline(origin=origin,
|
||||
isorange=time_range,
|
||||
upper_rmax=upper_rmax,
|
||||
cost_method=self._calculate_matrix_cost,
|
||||
profile=profile,
|
||||
unit_factor=UNIT_FACTOR_ISOCHRONE,
|
||||
number_of_angles=MATRIX_NUM_ANGLES,
|
||||
max_iterations=MATRIX_MAX_ITERS,
|
||||
tolerance=MATRIX_TOLERANCE)
|
||||
|
||||
def calculate_isodistance(self, origin, distance_range,
|
||||
profile=DEFAULT_PROFILE):
|
||||
validate_profile(profile)
|
||||
|
||||
max_speed = MAX_SPEEDS[profile]
|
||||
time_range = distance_range / max_speed
|
||||
|
||||
return self.calculate_isochrone(origin=origin,
|
||||
time_range=time_range,
|
||||
profile=profile)
|
||||
|
||||
def calculate_isoline(self, origin, isorange, upper_rmax,
|
||||
cost_method=_calculate_matrix_cost,
|
||||
profile=DEFAULT_PROFILE,
|
||||
unit_factor=DEFAULT_UNIT_FACTOR,
|
||||
number_of_angles=DEFAULT_NUM_ANGLES,
|
||||
max_iterations=DEFAULT_MAX_ITERS,
|
||||
tolerance=DEFAULT_TOLERANCE):
|
||||
# Formally, a solution is an array of {angle, radius, lat, lon, cost}
|
||||
# with cardinality number_of_angles
|
||||
# we're looking for a solution in which
|
||||
# abs(cost - isorange) / isorange <= TOLERANCE
|
||||
|
||||
# Initial setup
|
||||
angles = get_angles(number_of_angles)
|
||||
rmax = [upper_rmax] * number_of_angles
|
||||
rmin = [0.0] * number_of_angles
|
||||
location_estimates = [calculate_dest_location(origin, a,
|
||||
upper_rmax / 2.0)
|
||||
for a in angles]
|
||||
|
||||
# Iterate to refine the first solution
|
||||
for i in xrange(0, max_iterations):
|
||||
# Calculate the "actual" cost for each location estimate.
|
||||
# NOTE: sometimes it cannot calculate the cost and returns None.
|
||||
# Just assume isorange and stop the calculations there
|
||||
|
||||
costs = cost_method(origin=origin, targets=location_estimates,
|
||||
isorange=isorange, profile=profile,
|
||||
unit_factor=unit_factor,
|
||||
number_of_angles=number_of_angles)
|
||||
|
||||
errors = [(cost - isorange) / float(isorange) for cost in costs]
|
||||
max_abs_error = max([abs(e) for e in errors])
|
||||
if max_abs_error <= tolerance:
|
||||
# good enough, stop there
|
||||
break
|
||||
|
||||
# let's refine the solution, binary search
|
||||
for j in xrange(0, number_of_angles):
|
||||
|
||||
if abs(errors[j]) > tolerance:
|
||||
if errors[j] > 0:
|
||||
rmax[j] = (rmax[j] + rmin[j]) / 2.0
|
||||
else:
|
||||
rmin[j] = (rmax[j] + rmin[j]) / 2.0
|
||||
|
||||
location_estimates[j] = calculate_dest_location(origin,
|
||||
angles[j],
|
||||
(rmax[j] + rmin[j]) / 2.0)
|
||||
|
||||
# delete points that got None
|
||||
location_estimates_filtered = []
|
||||
for i, c in enumerate(costs):
|
||||
if c != isorange:
|
||||
location_estimates_filtered.append(location_estimates[i])
|
||||
|
||||
return location_estimates_filtered
|
||||
@@ -0,0 +1,73 @@
|
||||
'''
|
||||
Python client for the Mapbox Time Matrix service.
|
||||
'''
|
||||
|
||||
import requests
|
||||
from cartodb_services.metrics import Traceable
|
||||
from cartodb_services.tools.coordinates import (validate_coordinates,
|
||||
marshall_coordinates)
|
||||
from exceptions import ServiceException
|
||||
|
||||
ACCESS_TOKEN = 'pk.eyJ1IjoiYWNhcmxvbiIsImEiOiJjamJuZjQ1Zjc0Ymt4Mnh0YmFrMmhtYnY4In0.gt9cw0VeKc3rM2mV5pcEmg'
|
||||
|
||||
BASEURI = ('https://api.mapbox.com/directions-matrix/v1/mapbox/{profile}/'
|
||||
'{coordinates}'
|
||||
'?access_token={token}'
|
||||
'&sources=0' # Set the first coordinate as source...
|
||||
'&destinations=all') # ...and the rest as destinations
|
||||
|
||||
NUM_COORDINATES_MIN = 2 # https://www.mapbox.com/api-documentation/#matrix
|
||||
NUM_COORDINATES_MAX = 25 # https://www.mapbox.com/api-documentation/#matrix
|
||||
|
||||
PROFILE_DRIVING_TRAFFIC = 'driving-traffic'
|
||||
PROFILE_DRIVING = 'driving'
|
||||
PROFILE_CYCLING = 'cycling'
|
||||
PROFILE_WALKING = 'walking'
|
||||
DEFAULT_PROFILE = PROFILE_DRIVING
|
||||
|
||||
VALID_PROFILES = [PROFILE_DRIVING_TRAFFIC,
|
||||
PROFILE_DRIVING,
|
||||
PROFILE_CYCLING,
|
||||
PROFILE_WALKING]
|
||||
|
||||
ENTRY_DURATIONS = 'durations'
|
||||
|
||||
|
||||
def validate_profile(profile):
|
||||
if profile not in VALID_PROFILES:
|
||||
raise ValueError('{profile} is not a valid profile. '
|
||||
'Valid profiles are: {valid_profiles}'.format(
|
||||
profile=profile,
|
||||
valid_profiles=', '.join(
|
||||
[x for x in VALID_PROFILES])))
|
||||
|
||||
|
||||
class MapboxMatrixClient(Traceable):
|
||||
'''
|
||||
Python wrapper for the Mapbox Time Matrix service.
|
||||
'''
|
||||
|
||||
def __init__(self, token=ACCESS_TOKEN):
|
||||
self.token = token
|
||||
|
||||
def _uri(self, coordinates, profile=DEFAULT_PROFILE):
|
||||
return BASEURI.format(profile=profile, coordinates=coordinates,
|
||||
token=self.token)
|
||||
|
||||
def _parse_matrix_response(self, response):
|
||||
return response
|
||||
|
||||
def matrix(self, coordinates, profile=DEFAULT_PROFILE):
|
||||
validate_profile(profile)
|
||||
validate_coordinates(coordinates,
|
||||
NUM_COORDINATES_MIN, NUM_COORDINATES_MAX)
|
||||
|
||||
coords = marshall_coordinates(coordinates)
|
||||
|
||||
uri = self._uri(coords, profile)
|
||||
response = requests.get(uri)
|
||||
|
||||
if response.status_code == requests.codes.ok:
|
||||
return self._parse_matrix_response(response.text)
|
||||
else:
|
||||
raise ServiceException(response.status_code, response.content)
|
||||
@@ -0,0 +1,107 @@
|
||||
'''
|
||||
Python client for the Mapbox Routing service.
|
||||
'''
|
||||
|
||||
import json
|
||||
import requests
|
||||
from cartodb_services.metrics import Traceable
|
||||
from cartodb_services.tools import PolyLine
|
||||
from cartodb_services.tools.coordinates import (validate_coordinates,
|
||||
marshall_coordinates)
|
||||
from cartodb_services.mapbox.exceptions import ServiceException
|
||||
|
||||
ACCESS_TOKEN = 'pk.eyJ1IjoiYWNhcmxvbiIsImEiOiJjamJuZjQ1Zjc0Ymt4Mnh0YmFrMmhtYnY4In0.gt9cw0VeKc3rM2mV5pcEmg'
|
||||
|
||||
BASEURI = ('https://api.mapbox.com/directions/v5/mapbox/{profile}/'
|
||||
'{coordinates}'
|
||||
'?access_token={token}'
|
||||
'&overview={overview}')
|
||||
|
||||
NUM_WAYPOINTS_MIN = 2 # https://www.mapbox.com/api-documentation/#directions
|
||||
NUM_WAYPOINTS_MAX = 25 # https://www.mapbox.com/api-documentation/#directions
|
||||
|
||||
PROFILE_DRIVING_TRAFFIC = 'driving-traffic'
|
||||
PROFILE_DRIVING = 'driving'
|
||||
PROFILE_CYCLING = 'cycling'
|
||||
PROFILE_WALKING = 'walking'
|
||||
DEFAULT_PROFILE = PROFILE_DRIVING
|
||||
|
||||
DEFAULT_OVERVIEW = 'full'
|
||||
|
||||
VALID_PROFILES = [PROFILE_DRIVING_TRAFFIC,
|
||||
PROFILE_DRIVING,
|
||||
PROFILE_CYCLING,
|
||||
PROFILE_WALKING]
|
||||
|
||||
ENTRY_ROUTES = 'routes'
|
||||
ENTRY_GEOMETRY = 'geometry'
|
||||
ENTRY_DURATION = 'duration'
|
||||
ENTRY_DISTANCE = 'distance'
|
||||
|
||||
|
||||
class MapboxRouting(Traceable):
|
||||
'''
|
||||
Python wrapper for the Mapbox Routing service.
|
||||
'''
|
||||
|
||||
def __init__(self, token=ACCESS_TOKEN):
|
||||
self._token = token
|
||||
|
||||
def _uri(self, coordinates, profile=DEFAULT_PROFILE,
|
||||
overview=DEFAULT_OVERVIEW):
|
||||
return BASEURI.format(profile=profile, coordinates=coordinates,
|
||||
token=self._token, overview=overview)
|
||||
|
||||
def _validate_profile(self, profile):
|
||||
if profile not in VALID_PROFILES:
|
||||
raise ValueError('{profile} is not a valid profile. '
|
||||
'Valid profiles are: {valid_profiles}'.format(
|
||||
profile=profile,
|
||||
valid_profiles=', '.join(
|
||||
[x for x in VALID_PROFILES])))
|
||||
|
||||
def _parse_routing_response(self, response):
|
||||
json_response = json.loads(response)
|
||||
route = json_response[ENTRY_ROUTES][0] # Force the first route
|
||||
|
||||
geometry = PolyLine().decode(route[ENTRY_GEOMETRY])
|
||||
distance = route[ENTRY_DISTANCE]
|
||||
duration = route[ENTRY_DURATION]
|
||||
|
||||
return MapboxRoutingResponse(geometry, distance, duration)
|
||||
|
||||
def directions(self, waypoints, profile=DEFAULT_PROFILE):
|
||||
self._validate_profile(profile)
|
||||
validate_coordinates(waypoints, NUM_WAYPOINTS_MIN, NUM_WAYPOINTS_MAX)
|
||||
|
||||
coordinates = marshall_coordinates(waypoints)
|
||||
|
||||
uri = self._uri(coordinates, profile)
|
||||
response = requests.get(uri)
|
||||
|
||||
if response.status_code == requests.codes.ok:
|
||||
return self._parse_routing_response(response.text)
|
||||
elif response.status_code == requests.codes.bad_request:
|
||||
return MapboxRoutingResponse(None, None, None)
|
||||
else:
|
||||
raise ServiceException(response.status_code, response.content)
|
||||
|
||||
|
||||
class MapboxRoutingResponse:
|
||||
|
||||
def __init__(self, shape, length, duration):
|
||||
self._shape = shape
|
||||
self._length = length
|
||||
self._duration = duration
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return self._length
|
||||
|
||||
@property
|
||||
def duration(self):
|
||||
return self._duration
|
||||
@@ -17,5 +17,23 @@ class Coordinate:
|
||||
def to_json(self):
|
||||
return "{{\"lon\": {0},\"lat\": {1}}}".format(self._longitude,
|
||||
self._latitude)
|
||||
|
||||
def __str__(self):
|
||||
return "{0}, {1}".format(self._longitude, self._latitude)
|
||||
return "{0}, {1}".format(self._longitude, self._latitude)
|
||||
|
||||
|
||||
def validate_coordinates(coordinates,
|
||||
num_coordinates_min, num_coordinates_max):
|
||||
if not coordinates:
|
||||
raise ValueError('Invalid (empty) coordinates.')
|
||||
|
||||
if len(coordinates) < num_coordinates_min \
|
||||
or len(coordinates) > num_coordinates_max:
|
||||
raise ValueError('Invalid number of coordinates. '
|
||||
'Must be between {min} and {max}'.format(
|
||||
min=num_coordinates_min,
|
||||
max=num_coordinates_max))
|
||||
|
||||
def marshall_coordinates(coordinates):
|
||||
return ';'.join([str(coordinate).replace(' ', '')
|
||||
for coordinate in coordinates])
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from math import cos, sin, pi, radians, degrees, asin, atan2
|
||||
from cartodb_services.tools import Coordinate
|
||||
|
||||
EARTH_RADIUS_METERS = 6367444
|
||||
|
||||
|
||||
def get_angles(number_of_angles): # Angle in radians
|
||||
step = (2.0 * pi) / number_of_angles
|
||||
return [(x * step) for x in xrange(0, number_of_angles)]
|
||||
|
||||
def calculate_dest_location(origin, angle, radius): # Angle in radians
|
||||
origin_lat_radians = radians(origin.latitude)
|
||||
origin_long_radians = radians(origin.longitude)
|
||||
dest_lat_radians = asin(sin(origin_lat_radians) * cos(radius / EARTH_RADIUS_METERS) + cos(origin_lat_radians) * sin(radius / EARTH_RADIUS_METERS) * cos(angle))
|
||||
dest_lng_radians = origin_long_radians + atan2(sin(angle) * sin(radius / EARTH_RADIUS_METERS) * cos(origin_lat_radians), cos(radius / EARTH_RADIUS_METERS) - sin(origin_lat_radians) * sin(dest_lat_radians))
|
||||
|
||||
return Coordinate(degrees(dest_lng_radians), degrees(dest_lat_radians))
|
||||
@@ -6,6 +6,7 @@ rollbar==0.13.2
|
||||
# Dependency for googlemaps package
|
||||
requests==2.9.1
|
||||
rratelimit==0.0.4
|
||||
mapbox
|
||||
|
||||
# Test
|
||||
mock==1.3.0
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import unittest
|
||||
from cartodb_services.mapbox import MapboxGeocoder
|
||||
from cartodb_services.mapbox import ServiceException
|
||||
|
||||
INVALID_TOKEN = 'invalid_token'
|
||||
VALID_ADDRESS = 'Calle Siempreviva 3, Valladolid'
|
||||
WELL_KNOWN_LONGITUDE = -4.730947
|
||||
WELL_KNOWN_LATITUDE = 41.668654
|
||||
|
||||
|
||||
class MapboxGeocoderTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.geocoder = MapboxGeocoder()
|
||||
|
||||
def test_invalid_token(self):
|
||||
invalid_geocoder = MapboxGeocoder(token=INVALID_TOKEN)
|
||||
with self.assertRaises(ServiceException):
|
||||
invalid_geocoder.geocode(VALID_ADDRESS)
|
||||
|
||||
def test_valid_request(self):
|
||||
place = self.geocoder.geocode(VALID_ADDRESS)
|
||||
|
||||
self.assertEqual(place[0], WELL_KNOWN_LONGITUDE)
|
||||
self.assertEqual(place[1], WELL_KNOWN_LATITUDE)
|
||||
@@ -0,0 +1,36 @@
|
||||
import unittest
|
||||
from cartodb_services.mapbox.isolines import MapboxIsolines
|
||||
from cartodb_services.mapbox.matrix_client import DEFAULT_PROFILE
|
||||
from cartodb_services.mapbox.matrix_client import MapboxMatrixClient
|
||||
from cartodb_services.mapbox.routing import MapboxRouting
|
||||
from cartodb_services.tools import Coordinate
|
||||
from cartodb_services.tools.coordinates import (validate_coordinates,
|
||||
marshall_coordinates)
|
||||
|
||||
VALID_ORIGIN = Coordinate(-73.989, 40.733)
|
||||
|
||||
|
||||
class MapboxIsolinesTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
matrix_client = MapboxMatrixClient()
|
||||
routing_client = MapboxRouting()
|
||||
self.mapbox_isolines = MapboxIsolines(matrix_client, routing_client)
|
||||
|
||||
def test_calculate_isochrone(self):
|
||||
time_range = 10 * 60 # 10 minutes
|
||||
solution = self.mapbox_isolines.calculate_isochrone(
|
||||
origin=VALID_ORIGIN,
|
||||
profile=DEFAULT_PROFILE,
|
||||
time_range=time_range)
|
||||
|
||||
assert solution
|
||||
|
||||
def test_calculate_isodistance(self):
|
||||
distance_range = 10000
|
||||
solution = self.mapbox_isolines.calculate_isodistance(
|
||||
origin=VALID_ORIGIN,
|
||||
profile=DEFAULT_PROFILE,
|
||||
distance_range=distance_range)
|
||||
|
||||
assert solution
|
||||
54
server/lib/python/cartodb_services/test/test_mapboxmatrix.py
Normal file
54
server/lib/python/cartodb_services/test/test_mapboxmatrix.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import unittest
|
||||
from cartodb_services.mapbox import MapboxMatrixClient
|
||||
from cartodb_services.mapbox.matrix_client import DEFAULT_PROFILE
|
||||
from cartodb_services.mapbox import ServiceException
|
||||
from cartodb_services.tools import Coordinate
|
||||
|
||||
INVALID_TOKEN = 'invalid_token'
|
||||
VALID_ORIGIN = Coordinate(-73.989, 40.733)
|
||||
VALID_TARGET = Coordinate(-74, 40.733)
|
||||
VALID_COORDINATES = [VALID_ORIGIN] + [VALID_TARGET]
|
||||
NUM_COORDINATES_MAX = 25
|
||||
INVALID_COORDINATES_EMPTY = []
|
||||
INVALID_COORDINATES_MIN = [VALID_ORIGIN]
|
||||
INVALID_COORDINATES_MAX = [VALID_ORIGIN] + \
|
||||
[VALID_TARGET
|
||||
for x in range(0, NUM_COORDINATES_MAX + 1)]
|
||||
VALID_PROFILE = DEFAULT_PROFILE
|
||||
INVALID_PROFILE = 'invalid_profile'
|
||||
|
||||
|
||||
class MapboxMatrixTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.matrix_client = MapboxMatrixClient()
|
||||
|
||||
def test_invalid_profile(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.matrix_client.matrix(VALID_COORDINATES,
|
||||
INVALID_PROFILE)
|
||||
|
||||
def test_invalid_coordinates_empty(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.matrix_client.matrix(INVALID_COORDINATES_EMPTY,
|
||||
VALID_PROFILE)
|
||||
|
||||
def test_invalid_coordinates_max(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.matrix_client.matrix(INVALID_COORDINATES_MAX,
|
||||
VALID_PROFILE)
|
||||
|
||||
def test_invalid_coordinates_min(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.matrix_client.matrix(INVALID_COORDINATES_MIN,
|
||||
VALID_PROFILE)
|
||||
|
||||
def test_invalid_token(self):
|
||||
invalid_matrix = MapboxMatrixClient(token=INVALID_TOKEN)
|
||||
with self.assertRaises(ServiceException):
|
||||
invalid_matrix.matrix(VALID_COORDINATES,
|
||||
VALID_PROFILE)
|
||||
|
||||
def test_valid_request(self):
|
||||
distance_matrix = self.matrix_client.matrix(VALID_COORDINATES,
|
||||
VALID_PROFILE)
|
||||
assert distance_matrix
|
||||
@@ -0,0 +1,61 @@
|
||||
import unittest
|
||||
from cartodb_services.mapbox import MapboxRouting
|
||||
from cartodb_services.mapbox.routing import DEFAULT_PROFILE
|
||||
from cartodb_services.mapbox import ServiceException
|
||||
from cartodb_services.tools import Coordinate
|
||||
|
||||
INVALID_TOKEN = 'invalid_token'
|
||||
VALID_WAYPOINTS = [Coordinate(-73.989, 40.733), Coordinate(-74, 40.733)]
|
||||
NUM_WAYPOINTS_MAX = 25
|
||||
INVALID_WAYPOINTS_EMPTY = []
|
||||
INVALID_WAYPOINTS_MIN = [Coordinate(-73.989, 40.733)]
|
||||
INVALID_WAYPOINTS_MAX = [Coordinate(-73.989, 40.733)
|
||||
for x in range(0, NUM_WAYPOINTS_MAX + 2)]
|
||||
VALID_PROFILE = DEFAULT_PROFILE
|
||||
INVALID_PROFILE = 'invalid_profile'
|
||||
|
||||
WELL_KNOWN_SHAPE = [(40.73312, -73.98891), (40.73353, -73.98987),
|
||||
(40.73398, -73.99095), (40.73453, -73.99227),
|
||||
(40.73531, -73.99412), (40.73467, -73.99459),
|
||||
(40.73442, -73.99477), (40.73435, -73.99482),
|
||||
(40.73403, -73.99505), (40.73344, -73.99549),
|
||||
(40.73286, -73.9959), (40.73226, -73.99635),
|
||||
(40.73186, -73.99664), (40.73147, -73.99693),
|
||||
(40.73141, -73.99698), (40.73147, -73.99707),
|
||||
(40.73219, -73.99856), (40.73222, -73.99861),
|
||||
(40.73293, -74.00007), (40.733, -74.00001)]
|
||||
WELL_KNOWN_LENGTH = 1317.9
|
||||
|
||||
|
||||
class MapboxRoutingTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.routing = MapboxRouting()
|
||||
|
||||
def test_invalid_profile(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.routing.directions(VALID_WAYPOINTS, INVALID_PROFILE)
|
||||
|
||||
def test_invalid_waypoints_empty(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.routing.directions(INVALID_WAYPOINTS_EMPTY, VALID_PROFILE)
|
||||
|
||||
def test_invalid_waypoints_min(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.routing.directions(INVALID_WAYPOINTS_MIN, VALID_PROFILE)
|
||||
|
||||
def test_invalid_waypoints_max(self):
|
||||
with self.assertRaises(ValueError):
|
||||
self.routing.directions(INVALID_WAYPOINTS_MAX, VALID_PROFILE)
|
||||
|
||||
def test_invalid_token(self):
|
||||
invalid_routing = MapboxRouting(token=INVALID_TOKEN)
|
||||
with self.assertRaises(ServiceException):
|
||||
invalid_routing.directions(VALID_WAYPOINTS,
|
||||
VALID_PROFILE)
|
||||
|
||||
def test_valid_request(self):
|
||||
route = self.routing.directions(VALID_WAYPOINTS, VALID_PROFILE)
|
||||
|
||||
self.assertEqual(route.shape, WELL_KNOWN_SHAPE)
|
||||
self.assertEqual(route.length, WELL_KNOWN_LENGTH)
|
||||
assert route.duration # The duration may change between executions
|
||||
Reference in New Issue
Block a user