Skip to content

Commit

Permalink
Merge pull request #91 from jan-bausch/support_graphhopper_post
Browse files Browse the repository at this point in the history
Replace GET with POST and update unit tests
  • Loading branch information
chrstnbwnkl committed Mar 21, 2023
2 parents db50137 + f0b1eb0 commit efe820a
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 119 deletions.
142 changes: 61 additions & 81 deletions routingpy/routers/graphhopper.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,32 +109,29 @@ def directions( # noqa: C901
points_encoded=True,
calc_points=None,
debug=None,
point_hint=None,
point_hints=None,
details=None,
ch_disable=None,
weighting=None,
heading=None,
custom_model=None,
headings=None,
heading_penalty=None,
pass_through=None,
block_area=None,
avoid=None,
algorithm=None,
round_trip_distance=None,
round_trip_seed=None,
alternative_route_max_paths=None,
alternative_route_max_weight_factor=None,
alternative_route_max_share_factor=None,
dry_run=None,
snap_prevention=None,
curb_side=None,
turn_costs=None,
snap_preventions=None,
curbsides=None,
**direction_kwargs
):
"""Get directions between an origin point and a destination point.
Use ``direction_kwargs`` for any missing ``directions`` request options.
For more information, visit https://docs.graphhopper.com/#tag/Routing-API/paths/~1route/get.
For more information, visit https://docs.graphhopper.com/#operation/postRoute.
:param locations: The coordinates tuple the route should be calculated
from in order of visit.
Expand Down Expand Up @@ -184,9 +181,9 @@ def directions( # noqa: C901
Default False.
:type debug: bool
:param point_hint: The point_hint is typically a road name to which the associated point parameter should be
:param point_hints: The point_hints is typically a road name to which the associated point parameter should be
snapped to. Specify no point_hint parameter or the same number as you have locations. Optional.
:type point_hint: list of str
:type point_hints: list of str
:param details: Optional parameter to retrieve path details. You can request additional details for the route:
street_name, time, distance, max_speed, toll, road_class, road_class_link, road_access, road_environment,
Expand All @@ -197,15 +194,14 @@ def directions( # noqa: C901
Default False.
:type ch_disable: bool
:param weighting: Which kind of 'best' route calculation you need. Other options are shortest
(e.g. for vehicle=foot or bike) and short_fastest if not only time but also distance is expensive.
Default "fastest".
:type weighting: str
:param custom_model: The custom_model modifies the routing behaviour of the specified profile.
See https://docs.graphhopper.com/#section/Custom-Model
:type custom_model: dict
:param heading: Optional parameter. Favour a heading direction for a certain point. Specify either one heading for the start point or as
:param headings: Optional parameter. Favour a heading direction for a certain point. Specify either one heading for the start point or as
many as there are points. In this case headings are associated by their order to the specific points.
Headings are given as north based clockwise angle between 0 and 360 degree.
:type heading: list of int
:type headings: list of int
:param heading_penalty: Optional parameter. Penalty for omitting a specified heading. The penalty corresponds to the accepted time
delay in seconds in comparison to the route without a heading.
Expand All @@ -216,14 +212,6 @@ def directions( # noqa: C901
Default False.
:type pass_through: bool
:param block_area: Optional parameter. Block road access via a point with the format
latitude,longitude or an area defined by a circle lat,lon,radius or a rectangle lat1,lon1,lat2,lon2.
:type block_area: str
:param avoid: Optional semicolon separated parameter. Specify which road classes you would like to avoid
(currently only supported for motor vehicles like car). Possible values are ferry, motorway, toll, tunnel and ford.
:type avoid: list of str
:param algorithm: Optional parameter. round_trip or alternative_route.
:type algorithm: str
Expand Down Expand Up @@ -253,18 +241,14 @@ def directions( # noqa: C901
:param dry_run: Print URL and parameters without sending the request.
:type dry_run: bool
:param snap_prevention: Optional parameter to avoid snapping to a certain road class or road environment.
:param snap_preventions: Optional parameter to avoid snapping to a certain road class or road environment.
Currently supported values are motorway, trunk, ferry, tunnel, bridge and ford. Optional.
:type snap_prevention: list of str
:type snap_preventions: list of str
:param curb_side: One of "any", "right", "left". It specifies on which side a point should be relative to the driver
:param curbsides: One of "any", "right", "left". It specifies on which side a point should be relative to the driver
when she leaves/arrives at a start/target/via point. You need to specify this parameter for either none
or all points. Only supported for motor vehicles and OpenStreetMap.
:type curb_side: list of str
:param turn_costs: Specifies if turn restrictions should be considered. Enabling this option increases the
route computation time. Only supported for motor vehicles and OpenStreetMap.
:type turn_costs: bool
:type curbsides: list of str
:returns: One or multiple route(s) from provided coordinates and restrictions.
:rtype: :class:`routingpy.direction.Direction` or :class:`routingpy.direction.Directions`
Expand All @@ -274,111 +258,107 @@ def directions( # noqa: C901
.. versionadded:: 0.3.0
``snap_prevention``, ``curb_side``, ``turn_costs`` parameters
.. versionchanged:: 1.2.0
Renamed `point_hint` to `point_hints`, `heading` to `headings`,
`snap_prevention` to `snap_preventions`, `curb_side` to `curbsides`,
.. versionadded:: 1.2.0
Added `custom_model` parameter
.. deprecated:: 1.2.0
Removed `weighting`, `block_area`, `avoid`, `turn_costs` parameters
"""

params = [("vehicle", profile)]
params = {"vehicle": profile}

for coordinate in locations:
coord_latlng = reversed([convert.format_float(f) for f in coordinate])
params.append(("point", ",".join(coord_latlng)))
coord_lnglat = ([convert.format_float(f) for f in coordinate])
params["points"] = ",".join(coord_lnglat)

if self.key is not None:
params.append(("key", self.key))
params["key"] = self.key

if format is not None:
params.append(("type", format))
params["type"] = format

if optimize is not None:
params.append(("optimize", convert.convert_bool(optimize)))
params["optimize"] = optimize

if instructions is not None:
params.append(("instructions", convert.convert_bool(instructions)))
params["instructions"] = instructions

if locale is not None:
params.append(("locale", locale))
params["locale"] = locale

if elevation is not None:
params.append(("elevation", convert.convert_bool(elevation)))
params["elevation"] = elevation

if points_encoded is not None:
params.append(("points_encoded", convert.convert_bool(points_encoded)))
params["points_encoded"] = points_encoded

if calc_points is not None:
params.append(("calc_points", convert.convert_bool(calc_points)))
params["calc_points"] = calc_points

if debug is not None:
params.append(("debug", convert.convert_bool(debug)))
params["debug"] = debug

if point_hint is not None:
for hint in point_hint:
params.append(("point_hint", hint))
if point_hints is not None:
params["point_hints"] = point_hints

if snap_prevention:
params.append(("snap_prevention", convert.delimit_list(snap_prevention)))
if snap_preventions:
params["snap_preventions"] = snap_preventions

if turn_costs:
params.append(("turn_costs", convert.convert_bool(turn_costs)))

if curb_side:
params.append(("curb_side", convert.delimit_list(curb_side)))
if curbsides:
params["curbsides"] = curbsides

### all below params will only work if ch is disabled

if details is not None:
params.extend([("details", detail) for detail in details])
params["details"] = details

if ch_disable is not None:
params.append(("ch.disable", convert.convert_bool(ch_disable)))
params["ch.disable"] = ch_disable

if weighting is not None:
params.append(("weighting", weighting))
if custom_model is not None:
params["custom_model"] = custom_model

if heading is not None:
params.append(("heading", convert.delimit_list(heading)))
if headings is not None:
params["headings"] = headings

if heading_penalty is not None:
params.append(("heading_penalty", heading_penalty))
params["heading_penalty"] = heading_penalty

if pass_through is not None:
params.append(("pass_through", convert.convert_bool(pass_through)))

if block_area is not None:
params.append(("block_area", block_area))

if avoid is not None:
params.append(("avoid", convert.delimit_list(avoid, ";")))
params["pass_through"] = pass_through

if algorithm is not None:

params.append(("algorithm", algorithm))
params["algorithm"] = algorithm

if algorithm == "round_trip":

if round_trip_distance is not None:
params.append(("round_trip.distance", round_trip_distance))
params["round_trip.distance"] = round_trip_distance

if round_trip_seed is not None:
params.append(("round_trip.seed", round_trip_seed))
params["round_trip.seed"] = round_trip_seed

if algorithm == "alternative_route":

if alternative_route_max_paths is not None:
params.append(("alternative_route.max_paths", alternative_route_max_paths))
params["alternative_route.max_paths"] = alternative_route_max_paths

if alternative_route_max_weight_factor is not None:
params.append(
("alternative_route.max_weight_factor", alternative_route_max_weight_factor)
)
params["alternative_route.max_weight_factor"] = alternative_route_max_weight_factor

if alternative_route_max_share_factor:
params.append(
("alternative_route_max_share_factor", alternative_route_max_share_factor)
)
params["alternative_route_max_share_factor"] = alternative_route_max_share_factor

params.extend(direction_kwargs.items())
params.update(direction_kwargs)

return self.parse_directions_json(
self.client._request("/route", get_params=params, dry_run=dry_run),
self.client._request("/route", post_params=params, dry_run=dry_run),
algorithm,
elevation,
points_encoded,
Expand Down
39 changes: 7 additions & 32 deletions tests/test_graphhopper.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#
"""Tests for the Graphhopper module."""

import json

from copy import deepcopy

import responses
Expand All @@ -41,9 +43,10 @@ def test_full_directions(self):
query = deepcopy(ENDPOINTS_QUERIES[self.name]["directions"])
query["algorithm"] = None
query["fake_option"] = 42
expected = deepcopy(ENDPOINTS_EXPECTED[self.name]["directions"])

responses.add(
responses.GET,
responses.POST,
"https://graphhopper.com/api/1/route",
status=200,
json=ENDPOINTS_RESPONSES[self.name]["directions"],
Expand All @@ -52,16 +55,7 @@ def test_full_directions(self):

routes = self.client.directions(**query)
self.assertEqual(1, len(responses.calls))
self.assertURLEqual(
"https://graphhopper.com/api/1/route?avoid=tunnel%3Bford&"
"block_area=48.23424%2C8.34234&calc_points=false&ch.disable=true&debug=true&details=time&details=tolls&"
"elevation=true&heading=50%2C50%2C50&heading_penalty=100&instructions=false&key=sample_key&locale=en&"
"optimize=true&pass_through=true&point=49.415776%2C8.680916&point=49.420577%2C8.688641&"
"point=49.445776%2C8.780916&point_hint=Graphhopper%20Lane&point_hint=OSM%20Street&point_hint=Routing%20Broadway&"
"&points_encoded=true&vehicle=car&type=json&weighting=short_fastest&snap_prevention=trunk%2Cferry&curb_side=any%2Cright&turn_costs=true&fake_option=42",
responses.calls[0].request.url,
)

self.assertEqual(json.loads(responses.calls[0].request.body.decode("utf-8")), expected)
self.assertIsInstance(routes, Direction)
self.assertIsInstance(routes.geometry, list)
self.assertIsInstance(routes.duration, int)
Expand All @@ -73,7 +67,7 @@ def test_full_directions_alternatives(self):
query = deepcopy(ENDPOINTS_QUERIES[self.name]["directions"])

responses.add(
responses.GET,
responses.POST,
"https://graphhopper.com/api/1/route",
status=200,
json=ENDPOINTS_RESPONSES[self.name]["directions"],
Expand All @@ -82,16 +76,6 @@ def test_full_directions_alternatives(self):

routes = self.client.directions(**query)
self.assertEqual(1, len(responses.calls))
self.assertURLEqual(
"https://graphhopper.com/api/1/route?algorithm=alternative_route&alternative_route.max_paths=2&"
"alternative_route.max_weight_factor=1.7&alternative_route_max_share_factor=0.7&avoid=tunnel%3Bford&"
"block_area=48.23424%2C8.34234&calc_points=false&ch.disable=true&debug=true&details=time&details=tolls&"
"elevation=true&heading=50%2C50%2C50&heading_penalty=100&instructions=false&key=sample_key&locale=en&"
"optimize=true&pass_through=true&point=49.415776%2C8.680916&point=49.420577%2C8.688641&"
"point=49.445776%2C8.780916&point_hint=Graphhopper%20Lane&point_hint=OSM%20Street&point_hint=Routing%20Broadway"
"&points_encoded=true&vehicle=car&type=json&weighting=short_fastest&snap_prevention=trunk%2Cferry&curb_side=any%2Cright&turn_costs=true",
responses.calls[0].request.url,
)
self.assertIsInstance(routes, Directions)
self.assertEqual(3, len(routes))
self.assertIsInstance(routes[0], Direction)
Expand All @@ -110,7 +94,7 @@ def test_full_directions_not_encoded(self):
res["paths"][0]["points"] = dict(coordinates=decode_polyline5(res["paths"][0]["points"]))

responses.add(
responses.GET,
responses.POST,
"https://graphhopper.com/api/1/route",
status=200,
json=ENDPOINTS_RESPONSES[self.name]["directions"],
Expand All @@ -119,15 +103,6 @@ def test_full_directions_not_encoded(self):

route = self.client.directions(**query)
self.assertEqual(1, len(responses.calls))
self.assertURLEqual(
"https://graphhopper.com/api/1/route?avoid=tunnel%3Bford&"
"block_area=48.23424%2C8.34234&calc_points=false&ch.disable=true&debug=true&details=time&details=tolls&"
"elevation=true&heading=50%2C50%2C50&heading_penalty=100&instructions=false&key=sample_key&locale=en&"
"optimize=true&pass_through=true&point=49.415776%2C8.680916&point=49.420577%2C8.688641&"
"point=49.445776%2C8.780916&point_hint=Graphhopper%20Lane&point_hint=OSM%20Street&point_hint=Routing%20Broadway"
"&points_encoded=false&vehicle=car&type=json&weighting=short_fastest&snap_prevention=trunk%2Cferry&curb_side=any%2Cright&turn_costs=true",
responses.calls[0].request.url,
)

self.assertIsInstance(route, Direction)
self.assertIsInstance(route.geometry, list)
Expand Down

0 comments on commit efe820a

Please sign in to comment.