Python library supports Mapzen routing
This commit is contained in:
@@ -0,0 +1 @@
|
||||
from routing import MapzenRouting, MapzenRoutingResponse
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/local/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
|
||||
|
||||
class WrongParams(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return repr('Wrong parameters passed: ' + json.dumps(self.value))
|
||||
|
||||
|
||||
class MalformedResult(Exception):
|
||||
def __str__(self):
|
||||
return repr('Result structure is malformed')
|
||||
@@ -0,0 +1,118 @@
|
||||
import requests
|
||||
import json
|
||||
import re
|
||||
from polyline.codec import PolylineCodec
|
||||
|
||||
from exceptions import WrongParams
|
||||
from cartodb_services.tools import Coordinate
|
||||
|
||||
|
||||
class MapzenRouting:
|
||||
'A Mapzen Routing wrapper for python'
|
||||
|
||||
PRODUCTION_ROUTING_BASE_URL = 'https://valhalla.mapzen.com/route'
|
||||
|
||||
ACCEPTED_MODES = {
|
||||
"walk": "pedestrian",
|
||||
"car": "auto",
|
||||
"public_transport": "bus",
|
||||
"bicycle": "bicycle"
|
||||
}
|
||||
|
||||
AUTO_SHORTEST = 'auto_shortest'
|
||||
|
||||
OPTIONAL_PARAMS = [
|
||||
'mode_type',
|
||||
]
|
||||
|
||||
METRICS_UNITS = 'kilometers'
|
||||
IMPERIAL_UNITS = 'miles'
|
||||
|
||||
def __init__(self, app_key, base_url=PRODUCTION_ROUTING_BASE_URL):
|
||||
self._app_key = app_key
|
||||
self._url = base_url
|
||||
|
||||
def calculate_route_point_to_point(self, origin, destination, mode,
|
||||
options=[], units=METRICS_UNITS):
|
||||
parsed_options = self.__parse_options(options)
|
||||
mode_param = self.__parse_mode_param(mode, parsed_options)
|
||||
directions = self.__parse_directions(origin, destination)
|
||||
json_request_params = self.__parse_json_parameters(directions,
|
||||
mode_param,
|
||||
units)
|
||||
request_params = self.__parse_request_parameters(json_request_params)
|
||||
response = requests.get(self._url, params=request_params)
|
||||
if response.status_code == requests.codes.ok:
|
||||
return self.__parse_routing_response(response.text)
|
||||
else:
|
||||
response.raise_for_status()
|
||||
|
||||
def __parse_options(self, options):
|
||||
return dict(option.split('=') for option in options)
|
||||
|
||||
def __parse_request_parameters(self, json_request):
|
||||
request_options = {"json": json_request}
|
||||
request_options.update({'api_key': self._app_key})
|
||||
|
||||
return request_options
|
||||
|
||||
def __parse_json_parameters(self, directions, mode, units):
|
||||
json_options = directions
|
||||
json_options.update({'costing': self.ACCEPTED_MODES[mode]})
|
||||
json_options.update({"directions_options": {'units': units,
|
||||
'narrative': False}})
|
||||
|
||||
return json.dumps(json_options)
|
||||
|
||||
def __parse_directions(self, origin, destination):
|
||||
return {"locations": [
|
||||
{"lon": origin.longitude, "lat": origin.latitude},
|
||||
{"lon": destination.longitude, "lat": destination.latitude}
|
||||
]}
|
||||
|
||||
def __parse_routing_response(self, response):
|
||||
try:
|
||||
parsed_json_response = json.loads(response)
|
||||
legs = parsed_json_response['trip']['legs'][0]
|
||||
shape = PolylineCodec().decode(legs['shape'])
|
||||
length = legs['summary']['length']
|
||||
duration = legs['summary']['time']
|
||||
routing_response = MapzenRoutingResponse(shape, length, duration)
|
||||
|
||||
return routing_response
|
||||
except IndexError:
|
||||
return []
|
||||
except KeyError:
|
||||
raise MalformedResult()
|
||||
|
||||
def __parse_mode_param(self, mode, options):
|
||||
if mode in self.ACCEPTED_MODES:
|
||||
mode_source = self.ACCEPTED_MODES[mode]
|
||||
else:
|
||||
raise WrongParams("{0} is not an accepted mode type".format(mode))
|
||||
|
||||
if mode == self.ACCEPTED_MODES['car'] and 'mode_type' in options and \
|
||||
options['mode_type'] == 'shortest':
|
||||
mode = self.AUTO_SHORTEST
|
||||
|
||||
return mode
|
||||
|
||||
|
||||
class MapzenRoutingResponse:
|
||||
|
||||
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
|
||||
@@ -0,0 +1,16 @@
|
||||
import plpy
|
||||
|
||||
|
||||
def polyline_to_linestring(polyline):
|
||||
"""Convert a Mapzen polyline shape to a PostGIS multipolygon"""
|
||||
coordinates = []
|
||||
for point in polyline:
|
||||
# Divide by 10 because mapzen uses one more decimal than the
|
||||
# google standard (https://mapzen.com/documentation/turn-by-turn/decoding/)
|
||||
coordinates.append("%s %s" % (point[1]/10, point[0]/10))
|
||||
wkt_coordinates = ','.join(coordinates)
|
||||
|
||||
sql = "SELECT ST_GeomFromText('LINESTRING({0})', 4326) as geom".format(wkt_coordinates)
|
||||
geometry = plpy.execute(sql, 1)[0]['geom']
|
||||
|
||||
return geometry
|
||||
@@ -1,3 +1,3 @@
|
||||
from config import GeocoderConfig, IsolinesRoutingConfig, InternalGeocoderConfig, ConfigException
|
||||
from config import GeocoderConfig, IsolinesRoutingConfig, InternalGeocoderConfig, RoutingConfig, ConfigException
|
||||
from quota import QuotaService
|
||||
from user import UserMetricsService
|
||||
|
||||
@@ -27,6 +27,27 @@ class ServiceConfig(object):
|
||||
def organization(self):
|
||||
return self._orgname
|
||||
|
||||
class RoutingConfig(ServiceConfig):
|
||||
|
||||
ROUTING_CONFIG_KEYS = ['username', 'orgname', 'mapzen_app_key']
|
||||
MAPZEN_APP_KEY = 'mapzen_app_key'
|
||||
USERNAME_KEY = 'username'
|
||||
ORGNAME_KEY = 'orgname'
|
||||
|
||||
def __init__(self, redis_connection, username, orgname=None,
|
||||
mapzen_app_key=None):
|
||||
super(RoutingConfig, self).__init__(redis_connection, username,
|
||||
orgname)
|
||||
self._mapzen_app_key = mapzen_app_key
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return 'routing_mapzen'
|
||||
|
||||
@property
|
||||
def mapzen_app_key(self):
|
||||
return self._mapzen_app_key
|
||||
|
||||
|
||||
class IsolinesRoutingConfig(ServiceConfig):
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from redis_tools import RedisConnection
|
||||
from coordinates import Coordinate
|
||||
@@ -0,0 +1,19 @@
|
||||
class Coordinate:
|
||||
"""Class that represents a generic form of coordinates to be used
|
||||
by the services """
|
||||
|
||||
def __init__(self, longitude, latitude):
|
||||
self._longitude = longitude
|
||||
self._latitude = latitude
|
||||
|
||||
@property
|
||||
def latitude(self):
|
||||
return self._latitude
|
||||
|
||||
@property
|
||||
def longitude(self):
|
||||
return self._longitude
|
||||
|
||||
def to_json(self):
|
||||
return "{{\"lon\": {0},\"lat\": {1}}}".format(self._longitude,
|
||||
self._latitude)
|
||||
@@ -6,8 +6,6 @@ class RedisConnection:
|
||||
|
||||
REDIS_DEFAULT_USER_DB = 5
|
||||
REDIS_DEFAULT_TIMEOUT = 2 #seconds
|
||||
#REDIS_SENTINEL_DEFAULT_PORT = 26379
|
||||
#REDIS_DEFAULT_PORT = 6379
|
||||
|
||||
def __init__(self, sentinel_master_id, redis_host, redis_port,
|
||||
redis_db=REDIS_DEFAULT_USER_DB, **kwargs):
|
||||
|
||||
Reference in New Issue
Block a user