Merge pull request #189 from CartoDB/development

Add routing_with_waypoints functions to Data Services API
This commit is contained in:
Carla 2016-05-25 15:47:32 +02:00
commit b33d696817
31 changed files with 3811 additions and 68 deletions

View File

@ -78,6 +78,7 @@ Steps to deploy a new Data Services API version :
SELECT CDB_Conf_SetConf('user_config', '{"is_organization": false, "entity_name": "<YOUR_USERNAME>"}') SELECT CDB_Conf_SetConf('user_config', '{"is_organization": false, "entity_name": "<YOUR_USERNAME>"}')
SELECT CDB_Conf_SetConf('mapzen_conf', '{"routing": {"api_key": "valhalla_app_key", "monthly_quota": 999999}, "geocoder": {"api_key": "search_app_key", "monthly_quota": 999999}}'); SELECT CDB_Conf_SetConf('mapzen_conf', '{"routing": {"api_key": "valhalla_app_key", "monthly_quota": 999999}, "geocoder": {"api_key": "search_app_key", "monthly_quota": 999999}}');
SELECT CDB_Conf_SetConf('logger_con', '{"geocoder_log_path": "/tmp/geocodings.log"}') SELECT CDB_Conf_SetConf('logger_con', '{"geocoder_log_path": "/tmp/geocodings.log"}')
SELECT CDB_Conf_SetConf('data_observatory_conf', '{"connection": {"whitelist": [], "production": "host=localhost port=5432 dbname=dataservices_db user=geocoder_api", "staging": "host=localhost port=5432 dbname=dataservices_db user=geocoder_api"}}')
``` ```
- configure plproxy to point to the a database (you can use a specific database for the server or your same user) - configure plproxy to point to the a database (you can use a specific database for the server or your same user)

View File

@ -13,8 +13,8 @@ OLD_VERSIONS = $(wildcard old_versions/*.sql)
# @see http://www.postgresql.org/docs/current/static/extend-pgxs.html # @see http://www.postgresql.org/docs/current/static/extend-pgxs.html
DATA = $(NEW_EXTENSION_ARTIFACT) \ DATA = $(NEW_EXTENSION_ARTIFACT) \
$(OLD_VERSIONS) \ $(OLD_VERSIONS) \
cdb_dataservices_client--0.4.0--0.5.0.sql \ cdb_dataservices_client--0.5.0--0.6.0.sql \
cdb_dataservices_client--0.5.0--0.4.0.sql cdb_dataservices_client--0.6.0--0.5.0.sql
REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql))) REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql)))

View File

@ -0,0 +1,44 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_client UPDATE TO '0.6.0'" to load this file. \quit
--
-- Public dataservices API function
--
-- These are the only ones with permissions to publicuser role
-- and should also be the only ones with SECURITY DEFINER
CREATE OR REPLACE FUNCTION cdb_dataservices_client.cdb_route_with_waypoints (waypoints geometry(Point, 4326)[], mode text, options text[] DEFAULT ARRAY[]::text[], units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_client.simple_route AS $$
DECLARE
ret cdb_dataservices_client.simple_route;
username text;
orgname text;
BEGIN
IF session_user = 'publicuser' OR session_user ~ 'cartodb_publicuser_*' THEN
RAISE EXCEPTION 'The api_key must be provided';
END IF;
SELECT u, o INTO username, orgname FROM cdb_dataservices_client._cdb_entity_config() AS (u text, o text);
-- JSON value stored "" is taken as literal
IF username IS NULL OR username = '' OR username = '""' THEN
RAISE EXCEPTION 'Username is a mandatory argument, check it out';
END IF;
SELECT * FROM cdb_dataservices_client._cdb_route_with_waypoints(username, orgname, waypoints, mode, options, units) INTO ret;
RETURN ret;
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
CREATE OR REPLACE FUNCTION cdb_dataservices_client._cdb_route_with_waypoints (username text, organization_name text, waypoints geometry(Point, 4326)[], mode text, options text[] DEFAULT ARRAY[]::text[], units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_client.simple_route AS $$
CONNECT cdb_dataservices_client._server_conn_str();
SELECT * FROM cdb_dataservices_server.cdb_route_with_waypoints (username, organization_name, waypoints, mode, options, units);
$$ LANGUAGE plproxy;
GRANT EXECUTE ON FUNCTION cdb_dataservices_client.cdb_route_with_waypoints(waypoints geometry(Point, 4326)[], mode text, options text[], units text) TO publicuser;

View File

@ -0,0 +1,5 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_client UPDATE TO '0.5.0'" to load this file. \quit
DROP FUNCTION IF EXISTS cdb_dataservices_client._cdb_route_with_waypoints (text, text, geometry(Point, 4326)[], text, text[], text);
DROP FUNCTION IF EXISTS cdb_dataservices_client.cdb_route_with_waypoints (geometry(Point, 4326)[], text, text[], text);

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
comment = 'CartoDB dataservices client API extension' comment = 'CartoDB dataservices client API extension'
default_version = '0.5.0' default_version = '0.6.0'
requires = 'plproxy, cartodb' requires = 'plproxy, cartodb'
superuser = true superuser = true
schema = cdb_dataservices_client schema = cdb_dataservices_client

View File

@ -89,6 +89,15 @@
- { name: options, type: "text[]", default: 'ARRAY[]::text[]' } - { name: options, type: "text[]", default: 'ARRAY[]::text[]' }
- { name: units, type: "text", default: "'kilometers'"} - { name: units, type: "text", default: "'kilometers'"}
- name: cdb_route_with_waypoints
return_type: cdb_dataservices_client.simple_route
multi_field: true
params:
- { name: waypoints, type: "geometry(Point, 4326)[]" }
- { name: mode, type: text }
- { name: options, type: "text[]", default: 'ARRAY[]::text[]' }
- { name: units, type: "text", default: "'kilometers'"}
- name: obs_get_demographic_snapshot - name: obs_get_demographic_snapshot
return_type: json return_type: json
params: params:

View File

@ -11,7 +11,17 @@ BEGIN
RETURN ret; RETURN ret;
END; END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
-- Exercise the public and the proxied function CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_with_waypoints(username text, orgname text, waypoints geometry(Point, 4326)[], mode TEXT, options text[] DEFAULT ARRAY[]::text[], units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_client.simple_route AS $$
DECLARE
ret cdb_dataservices_client.simple_route;
BEGIN
RAISE NOTICE 'cdb_dataservices_server.cdb_route_with_waypoints invoked with params (%, %, %, %, %, %)', username, orgname, waypoints, mode, options, units;
SELECT NULL, 2.22, 500 INTO ret;
RETURN ret;
END;
$$ LANGUAGE 'plpgsql';
-- Exercise the public and the proxied functions
SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car'); SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car');
NOTICE: cdb_dataservices_client._cdb_route_point_to_point(7): [contrib_regression] REMOTE NOTICE: cdb_dataservices_server.cdb_route_point_to_point invoked with params (test_user, <NULL>, 0101000000D53E1D8F19F455C0185B087250F24440, 0101000000465F419AB1F255C0D8B628B341EE4440, car, {}, kilometers) NOTICE: cdb_dataservices_client._cdb_route_point_to_point(7): [contrib_regression] REMOTE NOTICE: cdb_dataservices_server.cdb_route_point_to_point invoked with params (test_user, <NULL>, 0101000000D53E1D8F19F455C0185B087250F24440, 0101000000465F419AB1F255C0D8B628B341EE4440, car, {}, kilometers)
CONTEXT: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_point_to_point(username, orgname, origin, destination, mode, options, units)" CONTEXT: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_point_to_point(username, orgname, origin, destination, mode, options, units)"
@ -39,3 +49,30 @@ PL/pgSQL function cdb_route_point_to_point(geometry,geometry,text,text[],text) l
(,5.33,100) (,5.33,100)
(1 row) (1 row)
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car');
NOTICE: cdb_dataservices_client._cdb_route_with_waypoints(6): [contrib_regression] REMOTE NOTICE: cdb_dataservices_server.cdb_route_with_waypoints invoked with params (test_user, <NULL>, {0101000000D53E1D8F19F455C0185B087250F24440:0101000000650113B875F355C05665DF15C1EF4440:0101000000465F419AB1F255C0D8B628B341EE4440}, car, {}, kilometers)
CONTEXT: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_with_waypoints(username, orgname, waypoints, mode, options, units)"
PL/pgSQL function cdb_route_with_waypoints(geometry[],text,text[],text) line 16 at SQL statement
cdb_route_with_waypoints
--------------------------
(,2.22,500)
(1 row)
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car', ARRAY['mode_type=shortest']::text[]);
NOTICE: cdb_dataservices_client._cdb_route_with_waypoints(6): [contrib_regression] REMOTE NOTICE: cdb_dataservices_server.cdb_route_with_waypoints invoked with params (test_user, <NULL>, {0101000000D53E1D8F19F455C0185B087250F24440:0101000000650113B875F355C05665DF15C1EF4440:0101000000465F419AB1F255C0D8B628B341EE4440}, car, {mode_type=shortest}, kilometers)
CONTEXT: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_with_waypoints(username, orgname, waypoints, mode, options, units)"
PL/pgSQL function cdb_route_with_waypoints(geometry[],text,text[],text) line 16 at SQL statement
cdb_route_with_waypoints
--------------------------
(,2.22,500)
(1 row)
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car', ARRAY[]::text[], 'miles');
NOTICE: cdb_dataservices_client._cdb_route_with_waypoints(6): [contrib_regression] REMOTE NOTICE: cdb_dataservices_server.cdb_route_with_waypoints invoked with params (test_user, <NULL>, {0101000000D53E1D8F19F455C0185B087250F24440:0101000000650113B875F355C05665DF15C1EF4440:0101000000465F419AB1F255C0D8B628B341EE4440}, car, {}, miles)
CONTEXT: SQL statement "SELECT * FROM cdb_dataservices_client._cdb_route_with_waypoints(username, orgname, waypoints, mode, options, units)"
PL/pgSQL function cdb_route_with_waypoints(geometry[],text,text[],text) line 16 at SQL statement
cdb_route_with_waypoints
--------------------------
(,2.22,500)
(1 row)

View File

@ -15,7 +15,24 @@ END;
$$ LANGUAGE 'plpgsql'; $$ LANGUAGE 'plpgsql';
-- Exercise the public and the proxied function CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_with_waypoints(username text, orgname text, waypoints geometry(Point, 4326)[], mode TEXT, options text[] DEFAULT ARRAY[]::text[], units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_client.simple_route AS $$
DECLARE
ret cdb_dataservices_client.simple_route;
BEGIN
RAISE NOTICE 'cdb_dataservices_server.cdb_route_with_waypoints invoked with params (%, %, %, %, %, %)', username, orgname, waypoints, mode, options, units;
SELECT NULL, 2.22, 500 INTO ret;
RETURN ret;
END;
$$ LANGUAGE 'plpgsql';
-- Exercise the public and the proxied functions
SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car'); SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car');
SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car', ARRAY['mode_type=shortest']::text[]); SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car', ARRAY['mode_type=shortest']::text[]);
SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car', ARRAY[]::text[], 'miles'); SELECT cdb_route_point_to_point('POINT(-87.81406 41.89308)'::geometry,'POINT(-87.79209 41.86138)'::geometry, 'car', ARRAY[]::text[], 'miles');
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car');
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car', ARRAY['mode_type=shortest']::text[]);
SELECT cdb_route_with_waypoints(Array['POINT(-87.81406 41.89308)'::geometry,'POINT(-87.80406 41.87308)'::geometry,'POINT(-87.79209 41.86138)'::geometry], 'car', ARRAY[]::text[], 'miles');

View File

@ -1,9 +1,11 @@
# Routing Functions # Routing Functions
Routing is the navigation from a defined start location to a defined end location. The calculated results are displayed as turn-by-turn directions on your map, based on the transportation mode that you specified. Routing services through CartoDB are available by requesting a single function in the Data Services API. Routing is the navigation from a defined start location to a defined end location. The calculated results are displayed as turn-by-turn directions on your map, based on the transportation mode that you specified. Routing services through CartoDB are available by using the available functions in the Data Services API.
### cdb_route_point_to_point(_origin geometry(Point), destination geometry(Point), mode text, [options text[], units text]_) ### cdb_route_point_to_point(_origin geometry(Point), destination geometry(Point), mode text, [options text[], units text]_)
Returns a route from origin to destination.
#### Arguments #### Arguments
Name | Type | Description | Accepted values Name | Type | Description | Accepted values
@ -36,6 +38,44 @@ INSERT INTO <TABLE> (duration, length, the_geom) SELECT duration, length, shape
UPDATE <TABLE> SET the_geom = (SELECT shape FROM cdb_route_point_to_point('POINT(-3.70237112 40.41706163)'::geometry,'POINT(-3.69909883 40.41236875)'::geometry, 'car', ARRAY['mode_type=shortest']::text[])) UPDATE <TABLE> SET the_geom = (SELECT shape FROM cdb_route_point_to_point('POINT(-3.70237112 40.41706163)'::geometry,'POINT(-3.69909883 40.41236875)'::geometry, 'car', ARRAY['mode_type=shortest']::text[]))
``` ```
### cdb_route_with_waypoints(_waypoints geometry(Point)[], mode text, [options text[], units text]_)
Returns a route that goes from origin to destination and whose path travels through the defined locations.
#### Arguments
Name | Type | Description | Accepted values
--- | --- | --- | ---
`waypoints` | `geometry(Point)[]` | Array of ordered points, in 4326 projection, which defines the origin point, one or more locations for the route path to travel through, and the destination. The first element of the array defines the origin and the last element the destination of the route. |
`mode` | `text` | Type of transport used to calculate the isolines. | `car`, `walk`, `bicycle` or `public_transport`
`options` | `text[]` | (Optional) Multiple options to add more capabilities to the analysis. See [Optional routing parameters](#optional-routing-parameters) for details.
`units` | `text` | Unit used to represent the length of the route. | `kilometers`, `miles`. By default is `kilometers`
#### Returns
Name | Type | Description
--- | --- | ---
`duration` | `integer` | Duration in seconds of the calculated route.
`length` | `real` | Length in the defined unit in the `units` field. `kilometers` by default .
`the_geom` | `geometry(LineString)` | LineString geometry of the calculated route in the 4326 projection.
*Note*: A request to the function _cdb\_route\_with\_waypoints(waypoints geometry(Point)[], mode text, [options text[], units text])_ with only two points in the geometry array are automatically defined as origin and destination. It is equivalent to performing the following request with these two locations as parameters: _cdb\_route\_point\_to\_point(origin geometry(Point), destination geometry(Point), mode text, [options text[], units text])_.
#### Examples
##### Insert the values from the calculated route in your table
```bash
INSERT INTO <TABLE> (duration, length, the_geom) SELECT duration, length, shape FROM cdb_route_with_waypoints(Array['POINT(-3.7109 40.4234)'::GEOMETRY, 'POINT(-3.7059 40.4203)'::geometry, 'POINT(-3.7046 40.4180)'::geometry]::geometry[], 'walk')
```
##### Update the geometry field with the calculated route shape
```bash
UPDATE <TABLE> SET the_geom = (SELECT shape FROM cdb_route_with_waypoints(Array['POINT(-3.7109 40.4234)'::GEOMETRY, 'POINT(-3.7059 40.4203)'::geometry, 'POINT(-3.7046 40.4180)'::geometry]::geometry[], 'car', ARRAY['mode_type=shortest']::text[]))
```
### Optional routing parameters ### Optional routing parameters
The optional value parameters must be passed using the format: `option=value`. The optional value parameters must be passed using the format: `option=value`.

View File

@ -13,8 +13,8 @@ OLD_VERSIONS = $(wildcard old_versions/*.sql)
# @see http://www.postgresql.org/docs/current/static/extend-pgxs.html # @see http://www.postgresql.org/docs/current/static/extend-pgxs.html
DATA = $(NEW_EXTENSION_ARTIFACT) \ DATA = $(NEW_EXTENSION_ARTIFACT) \
$(OLD_VERSIONS) \ $(OLD_VERSIONS) \
cdb_dataservices_server--0.7.4--0.8.0.sql \ cdb_dataservices_server--0.8.0--0.9.0.sql \
cdb_dataservices_server--0.8.0--0.7.4.sql cdb_dataservices_server--0.9.0--0.8.0.sql
REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql))) REGRESS = $(notdir $(basename $(wildcard test/sql/*test.sql)))
TEST_DIR = test/ TEST_DIR = test/

View File

@ -0,0 +1,102 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.9.0'" to load this file. \quit
DROP FUNCTION IF EXISTS cdb_dataservices_server._cdb_mapzen_route_point_to_point(text, text, geometry(Geometry, 4326), geometry(Geometry, 4326), text, text[], text);
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_with_waypoints(
username TEXT,
orgname TEXT,
waypoints geometry(Point, 4326)[],
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_with_waypoints($1, $2, $3, $4, $5, $6) as route;", ["text", "text", "geometry(Point, 4326)[]", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, waypoints, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_with_waypoints(
username TEXT,
orgname TEXT,
waypoints geometry(Point, 4326)[],
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
import json
from cartodb_services.mapzen import MapzenRouting, MapzenRoutingResponse
from cartodb_services.mapzen.types import polyline_to_linestring
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Coordinate
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reached the limit of your quota')
try:
client = MapzenRouting(user_routing_config.mapzen_api_key)
if not waypoints or len(waypoints) < 2:
plpy.notice("Empty origin or destination")
quota_service.increment_empty_service_use()
return [None, None, None]
waypoint_coords = []
for idx, points in enumerate(waypoints, start=0):
lat = plpy.execute("SELECT ST_Y('%s') AS lat" % waypoints[idx])[0]['lat']
lon = plpy.execute("SELECT ST_X('%s') AS lon" % waypoints[idx])[0]['lon']
waypoint_coords.append(Coordinate(lon,lat))
resp = client.calculate_route_point_to_point(waypoint_coords, mode, options, units)
if resp and resp.shape:
shape_linestring = polyline_to_linestring(resp.shape)
if shape_linestring:
quota_service.increment_success_service_use()
return [shape_linestring, resp.length, resp.duration]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to obtain route using mapzen provider: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER;
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
waypoints = [origin, destination]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_with_waypoints($1, $2, $3, $4, $5, $6) as route;", ["text", "text", "geometry(Point, 4326)[]", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, waypoints, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;

View File

@ -0,0 +1,86 @@
--DO NOT MODIFY THIS FILE, IT IS GENERATED AUTOMATICALLY FROM SOURCES
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "ALTER EXTENSION cdb_dataservices_server UPDATE TO '0.8.0'" to load this file. \quit
DROP FUNCTION IF EXISTS cdb_dataservices_server.cdb_route_with_waypoints(text, text, geometry(Geometry, 4326)[], text, text[], text);
DROP FUNCTION IF EXISTS cdb_dataservices_server._cdb_mapzen_route_with_waypoints(text, text, geometry(Geometry, 4326)[], text, text[], text);
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
import json
from cartodb_services.mapzen import MapzenRouting, MapzenRoutingResponse
from cartodb_services.mapzen.types import polyline_to_linestring
from cartodb_services.metrics import QuotaService
from cartodb_services.tools import Coordinate
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
user_routing_config = GD["user_routing_config_{0}".format(username)]
quota_service = QuotaService(user_routing_config, redis_conn)
if not quota_service.check_user_quota():
plpy.error('You have reached the limit of your quota')
try:
client = MapzenRouting(user_routing_config.mapzen_api_key)
if not origin or not destination:
plpy.notice("Empty origin or destination")
quota_service.increment_empty_service_use()
return [None, None, None]
orig_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % origin)[0]['lat']
orig_lon = plpy.execute("SELECT ST_X('%s') AS lon" % origin)[0]['lon']
origin_coordinates = Coordinate(orig_lon, orig_lat)
dest_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % destination)[0]['lat']
dest_lon = plpy.execute("SELECT ST_X('%s') AS lon" % destination)[0]['lon']
dest_coordinates = Coordinate(dest_lon, dest_lat)
resp = client.calculate_route_point_to_point(origin_coordinates, dest_coordinates, mode, options, units)
if resp and resp.shape:
shape_linestring = polyline_to_linestring(resp.shape)
if shape_linestring:
quota_service.increment_success_service_use()
return [shape_linestring, resp.length, resp.duration]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
else:
quota_service.increment_empty_service_use()
return [None, None, None]
except BaseException as e:
import sys, traceback
type_, value_, traceback_ = sys.exc_info()
quota_service.increment_failed_service_use()
error_msg = 'There was an error trying to obtain route using mapzen provider: {0}'.format(e)
plpy.notice(traceback.format_tb(traceback_))
plpy.error(error_msg)
finally:
quota_service.increment_total_service_use()
$$ LANGUAGE plpythonu SECURITY DEFINER;
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_point_to_point($1, $2, $3, $4, $5, $6, $7) as route;", ["text", "text", "geometry(Point, 4326)", "geometry(Point, 4326)", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, origin, destination, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
comment = 'CartoDB dataservices server extension' comment = 'CartoDB dataservices server extension'
default_version = '0.8.0' default_version = '0.9.0'
requires = 'plpythonu, plproxy, postgis, cdb_geocoder' requires = 'plpythonu, plproxy, postgis, cdb_geocoder'
superuser = true superuser = true
schema = cdb_dataservices_server schema = cdb_dataservices_server

View File

@ -4,12 +4,10 @@ CREATE TYPE cdb_dataservices_server.simple_route AS (
duration integer duration integer
); );
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_with_waypoints(
CREATE OR REPLACE FUNCTION cdb_dataservices_server._cdb_mapzen_route_point_to_point(
username TEXT, username TEXT,
orgname TEXT, orgname TEXT,
origin geometry(Point, 4326), waypoints geometry(Point, 4326)[],
destination geometry(Point, 4326),
mode TEXT, mode TEXT,
options text[] DEFAULT ARRAY[]::text[], options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers') units text DEFAULT 'kilometers')
@ -30,20 +28,18 @@ RETURNS cdb_dataservices_server.simple_route AS $$
try: try:
client = MapzenRouting(user_routing_config.mapzen_api_key) client = MapzenRouting(user_routing_config.mapzen_api_key)
if not origin or not destination: if not waypoints or len(waypoints) < 2:
plpy.notice("Empty origin or destination") plpy.notice("Empty origin or destination")
quota_service.increment_empty_service_use() quota_service.increment_empty_service_use()
return [None, None, None] return [None, None, None]
orig_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % origin)[0]['lat'] waypoint_coords = []
orig_lon = plpy.execute("SELECT ST_X('%s') AS lon" % origin)[0]['lon'] for waypoint in waypoints:
origin_coordinates = Coordinate(orig_lon, orig_lat) lat = plpy.execute("SELECT ST_Y('%s') AS lat" % waypoint)[0]['lat']
dest_lat = plpy.execute("SELECT ST_Y('%s') AS lat" % destination)[0]['lat'] lon = plpy.execute("SELECT ST_X('%s') AS lon" % waypoint)[0]['lon']
dest_lon = plpy.execute("SELECT ST_X('%s') AS lon" % destination)[0]['lon'] waypoint_coords.append(Coordinate(lon,lat))
dest_coordinates = Coordinate(dest_lon, dest_lat)
resp = client.calculate_route_point_to_point(origin_coordinates, dest_coordinates, mode, options, units)
resp = client.calculate_route_point_to_point(waypoint_coords, mode, options, units)
if resp and resp.shape: if resp and resp.shape:
shape_linestring = polyline_to_linestring(resp.shape) shape_linestring = polyline_to_linestring(resp.shape)
if shape_linestring: if shape_linestring:

View File

@ -0,0 +1,38 @@
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
waypoints = [origin, destination]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_with_waypoints($1, $2, $3, $4, $5, $6) as route;", ["text", "text", "geometry(Point, 4326)[]", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, waypoints, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_with_waypoints(
username TEXT,
orgname TEXT,
waypoints geometry(Point, 4326)[],
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_with_waypoints($1, $2, $3, $4, $5, $6) as route;", ["text", "text", "geometry(Point, 4326)[]", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, waypoints, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;

View File

@ -1,18 +0,0 @@
CREATE OR REPLACE FUNCTION cdb_dataservices_server.cdb_route_point_to_point(
username TEXT,
orgname TEXT,
origin geometry(Point, 4326),
destination geometry(Point, 4326),
mode TEXT,
options text[] DEFAULT ARRAY[]::text[],
units text DEFAULT 'kilometers')
RETURNS cdb_dataservices_server.simple_route AS $$
plpy.execute("SELECT cdb_dataservices_server._connect_to_redis('{0}')".format(username))
redis_conn = GD["redis_connection_{0}".format(username)]['redis_metrics_connection']
plpy.execute("SELECT cdb_dataservices_server._get_routing_config({0}, {1})".format(plpy.quote_nullable(username), plpy.quote_nullable(orgname)))
user_routing_config = GD["user_routing_config_{0}".format(username)]
mapzen_plan = plpy.prepare("SELECT * FROM cdb_dataservices_server._cdb_mapzen_route_point_to_point($1, $2, $3, $4, $5, $6, $7) as route;", ["text", "text", "geometry(Point, 4326)", "geometry(Point, 4326)", "text", "text[]", "text"])
result = plpy.execute(mapzen_plan, [username, orgname, origin, destination, mode, options, units])
return [result[0]['shape'],result[0]['length'], result[0]['duration']]
$$ LANGUAGE plpythonu;

View File

@ -0,0 +1,24 @@
-- Check for routing point to point signatures
SELECT exists(SELECT *
FROM pg_proc p
INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid)
WHERE ns.nspname = 'cdb_dataservices_server'
AND proname = 'cdb_route_point_to_point'
AND oidvectortypes(p.proargtypes) = 'text, text, geometry, geometry, text, text[], text');
exists
--------
t
(1 row)
-- Check for routing waypoint route signatures
SELECT exists(SELECT *
FROM pg_proc p
INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid)
WHERE ns.nspname = 'cdb_dataservices_server'
AND proname = 'cdb_route_with_waypoints'
AND oidvectortypes(p.proargtypes) = 'text, text, geometry[], text, text[], text');
exists
--------
t
(1 row)

View File

@ -5,8 +5,11 @@ SELECT exists(SELECT *
WHERE ns.nspname = 'cdb_dataservices_server' WHERE ns.nspname = 'cdb_dataservices_server'
AND proname = 'cdb_route_point_to_point' AND proname = 'cdb_route_point_to_point'
AND oidvectortypes(p.proargtypes) = 'text, text, geometry, geometry, text, text[], text'); AND oidvectortypes(p.proargtypes) = 'text, text, geometry, geometry, text, text[], text');
exists
--------
t
(1 row)
-- Check for routing waypoint route signatures
SELECT exists(SELECT *
FROM pg_proc p
INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid)
WHERE ns.nspname = 'cdb_dataservices_server'
AND proname = 'cdb_route_with_waypoints'
AND oidvectortypes(p.proargtypes) = 'text, text, geometry[], text, text[], text');

View File

@ -1,7 +0,0 @@
-- Check for routing point to point signatures
SELECT exists(SELECT *
FROM pg_proc p
INNER JOIN pg_namespace ns ON (p.pronamespace = ns.oid)
WHERE ns.nspname = 'cdb_dataservices_server'
AND proname = 'cdb_route_point_to_point'
AND oidvectortypes(p.proargtypes) = 'text, text, geometry, geometry, text, text[], text');

View File

@ -33,11 +33,11 @@ class MapzenRouting:
self._url = base_url self._url = base_url
@qps_retry @qps_retry
def calculate_route_point_to_point(self, origin, destination, mode, def calculate_route_point_to_point(self, waypoints, mode,
options=[], units=METRICS_UNITS): options=[], units=METRICS_UNITS):
parsed_options = self.__parse_options(options) parsed_options = self.__parse_options(options)
mode_param = self.__parse_mode_param(mode, parsed_options) mode_param = self.__parse_mode_param(mode, parsed_options)
directions = self.__parse_directions(origin, destination) directions = self.__parse_directions(waypoints)
json_request_params = self.__parse_json_parameters(directions, json_request_params = self.__parse_json_parameters(directions,
mode_param, mode_param,
units) units)
@ -67,11 +67,16 @@ class MapzenRouting:
return json.dumps(json_options) return json.dumps(json_options)
def __parse_directions(self, origin, destination): def __parse_directions(self, waypoints):
return {"locations": [ path = []
{"lon": origin.longitude, "lat": origin.latitude}, for idx, point in enumerate(waypoints):
{"lon": destination.longitude, "lat": destination.latitude} if idx == 0 or idx == len(waypoints) - 1:
]} point_type = 'break'
else:
point_type = 'through'
path.append({"lon": str(point.longitude), "lat": str(point.latitude), "type": point_type})
return {"locations": path}
def __parse_routing_response(self, response): def __parse_routing_response(self, response):
try: try:

View File

@ -10,7 +10,7 @@ from setuptools import setup, find_packages
setup( setup(
name='cartodb_services', name='cartodb_services',
version='0.6.1', version='0.6.2',
description='CartoDB Services API Python Library', description='CartoDB Services API Python Library',

View File

@ -17,9 +17,9 @@ requests_mock.Mocker.TEST_PREFIX = 'test_'
@requests_mock.Mocker() @requests_mock.Mocker()
class MapzenRoutingTestCase(unittest.TestCase): class MapzenRoutingTestCase(unittest.TestCase):
GOOD_SHAPE = [(38.5, -120.2), (43.2, -126.4)] GOOD_SHAPE_SIMPLE = [(38.5, -120.2), (43.2, -126.4)]
GOOD_RESPONSE = """{{ GOOD_RESPONSE_SIMPLE = """{{
"id": "ethervoid-route", "id": "ethervoid-route",
"trip": {{ "trip": {{
"status": 0, "status": 0,
@ -51,7 +51,50 @@ class MapzenRoutingTestCase(unittest.TestCase):
}} }}
] ]
}} }}
}}""".format(GOOD_SHAPE) }}""".format(GOOD_SHAPE_SIMPLE)
GOOD_SHAPE_MULTI = [(40.4, -3.7), (40.1, -3.4), (40.6, -3.9)]
GOOD_RESPONSE_MULTI = """{{
"id": "ethervoid-route",
"trip": {{
"language":"en-US",
"summary":{{
"length": 1.261,
"time": 913
}},
"locations":[
{{
"side_of_street":"right",
"lon": -3.7,
"lat": 40.4,
"type":"break"
}},
{{
"lon": -3.4,
"lat": 40.1,
"type": "through"
}},
{{
"lon": -3.9,
"lat": 40.6,
"type": "break"
}}
],
"units":"kilometers",
"legs":[
{{
"shape": "_squF~sqU~qy@_ry@_t`B~s`B",
"summary": {{
"length":1.261,
"time":913
}}
}}
],
"status_message": "Found route between points",
"status": 0
}}
}}""".format(GOOD_SHAPE_MULTI)
MALFORMED_RESPONSE = """{"manolo": "escobar"}""" MALFORMED_RESPONSE = """{"manolo": "escobar"}"""
@ -61,23 +104,39 @@ class MapzenRoutingTestCase(unittest.TestCase):
def test_calculate_simple_routing_with_valid_params(self, req_mock): def test_calculate_simple_routing_with_valid_params(self, req_mock):
req_mock.register_uri('GET', requests_mock.ANY, req_mock.register_uri('GET', requests_mock.ANY,
text=self.GOOD_RESPONSE) text=self.GOOD_RESPONSE_SIMPLE)
origin = Coordinate('-120.2', '38.5') origin = Coordinate('-120.2', '38.5')
destination = Coordinate('-126.4', '43.2') destination = Coordinate('-126.4', '43.2')
response = self.routing.calculate_route_point_to_point(origin, waypoints = [origin, destination]
destination, response = self.routing.calculate_route_point_to_point(waypoints,
'car') 'car')
self.assertEqual(response.shape, self.GOOD_SHAPE) self.assertEqual(response.shape, self.GOOD_SHAPE_SIMPLE)
self.assertEqual(response.length, 444.59) self.assertEqual(response.length, 444.59)
self.assertEqual(response.duration, 16969) self.assertEqual(response.duration, 16969)
def test_uknown_mode_raise_exception(self, req_mock): def test_uknown_mode_raise_exception(self, req_mock):
req_mock.register_uri('GET', requests_mock.ANY, req_mock.register_uri('GET', requests_mock.ANY,
text=self.GOOD_RESPONSE) text=self.GOOD_RESPONSE_SIMPLE)
origin = Coordinate('-120.2', '38.5') origin = Coordinate('-120.2', '38.5')
destination = Coordinate('-126.4', '43.2') destination = Coordinate('-126.4', '43.2')
waypoints = [origin, destination]
assert_raises(WrongParams, assert_raises(WrongParams,
self.routing.calculate_route_point_to_point, self.routing.calculate_route_point_to_point,
origin, destination, 'unknown') waypoints, 'unknown')
def test_calculate_routing_waypoints_with_valid_params(self, req_mock):
req_mock.register_uri('GET', requests_mock.ANY,
text=self.GOOD_RESPONSE_MULTI)
origin = Coordinate('-3.7', '40.4')
pass_through = Coordinate('-3.4', '40.1')
destination = Coordinate('-3.9', '40.6')
waypoints = [origin, pass_through, destination]
response = self.routing.calculate_route_point_to_point(waypoints,
'walk')
self.assertEqual(response.length, 1.261)
self.assertEqual(response.duration, 913)
self.assertEqual(response.shape, self.GOOD_SHAPE_MULTI)

View File

@ -32,3 +32,24 @@ class TestRoutingFunctions(TestCase):
IntegrationTestHelper.execute_query(self.sql_api_url, query) IntegrationTestHelper.execute_query(self.sql_api_url, query)
except Exception as e: except Exception as e:
assert_equal(e.message[0], "The api_key must be provided") assert_equal(e.message[0], "The api_key must be provided")
def test_if_select_with_routing_with_waypoints_is_ok(self):
query = "SELECT duration, length, shape as the_geom " \
"FROM cdb_route_with_waypoints('Array['POINT(-3.7109 40.4234)'::GEOMETRY, "\
"'POINT(-3.7059 40.4203)'::geometry, 'POINT(-3.7046 40.4180)'::geometry]" \
"::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_with_waypoints_without_api_key_raise_error(self):
query = "SELECT duration, length, shape as the_geom " \
"FROM cdb_route_with_waypoints('Array['POINT(-3.7109 40.4234)'::GEOMETRY, "\
"'POINT(-3.7059 40.4203)'::geometry, 'POINT(-3.7046 40.4180)'::geometry]" \
"::geometry[], 'car', " \
"ARRAY['mode_type=shortest']::text[])&api_key={0}"
try:
IntegrationTestHelper.execute_query(self.sql_api_url, query)
except Exception as e:
assert_equal(e.message[0], "The api_key must be provided")