Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: router for OpenTripPlanner #109

Merged
merged 8 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ test.py
*.pyc
*.idea/
.DS_Store

# Visual Studio Code
.vscode
.history
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ or **time-distance matrices**.
- `Here Maps`_
- `Google Maps`_
- `Graphhopper`_
- `OpenTripPlannerV2`_
- `Local Valhalla`_
- `Local OSRM`_

Expand Down
11 changes: 10 additions & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ MapboxValhalla

.. automethod:: __init__

OpenTripPlannerV2
--------------

.. autoclass:: routingpy.routers.OpenTripPlannerV2
:members:
:inherited-members:
:show-inheritance:

.. automethod:: __init__

ORS
---

Expand Down Expand Up @@ -176,4 +186,3 @@ Indices and search

* :ref:`genindex`
* :ref:`search`

39 changes: 23 additions & 16 deletions routingpy/client_default.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ def _request(
:raises routingpy.exceptions.TransportError: when something went wrong while trying to
execute a request.

:returns: raw JSON response.
:rtype: dict
:returns: raw JSON response or GeoTIFF image
:rtype: dict or bytes
"""

if not first_request_time:
Expand Down Expand Up @@ -192,13 +192,10 @@ def _request(
"Server down.\nRetrying for the {}{} time.".format(tried, get_ordinal(tried)),
UserWarning,
)

return self._request(url, get_params, post_params, first_request_time, retry_counter + 1)

try:
result = self._get_body(response)

return result
return self._get_body(response)

except exceptions.RouterApiError:
if self.skip_api_error:
Expand Down Expand Up @@ -229,22 +226,32 @@ def req(self):
@staticmethod
def _get_body(response):
status_code = response.status_code
content_type = response.headers["content-type"]

try:
body = response.json()
except json.decoder.JSONDecodeError:
raise exceptions.JSONParseError("Can't decode JSON response:{}".format(response.text))
if status_code == 200:
if content_type in ["application/json", "application/x-www-form-urlencoded"]:
try:
return response.json()

except json.decoder.JSONDecodeError:
raise exceptions.JSONParseError(
"Can't decode JSON response:{}".format(response.text)
)

elif content_type == "image/tiff":
return response.content

else:
raise exceptions.UnsupportedContentType(status_code, response.text)

if status_code == 429:
raise exceptions.OverQueryLimit(status_code, body)
raise exceptions.OverQueryLimit(status_code, response.text)

if 400 <= status_code < 500:
raise exceptions.RouterApiError(status_code, body)
raise exceptions.RouterApiError(status_code, response.text)

if 500 <= status_code:
raise exceptions.RouterServerError(status_code, body)
raise exceptions.RouterServerError(status_code, response.text)

if status_code != 200:
raise exceptions.RouterError(status_code, body)

return body
raise exceptions.RouterError(status_code, response.text)
32 changes: 32 additions & 0 deletions routingpy/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#
"""Converts Python types to string representations suitable for GET queries.
"""
import datetime


def delimit_list(arg, delimiter=","):
Expand Down Expand Up @@ -76,3 +77,34 @@ def _has_method(arg, method):
:rtype: bool
"""
return hasattr(arg, method) and callable(getattr(arg, method))


def seconds_to_iso8601(seconds):
"""Convert the given number of seconds to ISO 8601 duration format.

Example:
>>> seconds_to_iso8601(3665)
'PT1H1M5S'

:param seconds: The number of seconds to convert.
:type seconds: int

:returns: The duration in ISO 8601 format.
:rtype: string
"""
duration = datetime.timedelta(seconds=seconds)
hours = duration.seconds // 3600
minutes = (duration.seconds // 60) % 60
nilsnolde marked this conversation as resolved.
Show resolved Hide resolved
seconds = duration.seconds % 60

iso8601_duration = "PT"
if hours:
iso8601_duration += f"{hours}H"

if minutes:
iso8601_duration += f"{minutes}M"

if seconds or not (hours or minutes):
iso8601_duration += f"{seconds}S"

return iso8601_duration
6 changes: 6 additions & 0 deletions routingpy/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,9 @@ class OverQueryLimit(RouterError, RetriableRequest):
"""

pass


class UnsupportedContentType(Exception): # pragma: no cover
"""The response has an unsupported content type."""

pass
2 changes: 1 addition & 1 deletion routingpy/isochrone.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def center(self) -> Optional[Union[List[float], Tuple[float]]]:
return self._center

@property
def interval(self) -> int:
def interval(self) -> Optional[int]:
"""
The interval of the isochrone in seconds or in meters.

Expand Down
51 changes: 51 additions & 0 deletions routingpy/raster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
# Copyright (C) 2021 GIS OPS UG
#
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
#
"""
:class:`Raster` returns rasters results.
"""
from typing import Optional


class Raster(object):
"""
Contains a parsed single raster response. Access via properties ``image``, ``max_travel_time``
"""

def __init__(self, image=None, max_travel_time=None):
self._image = image
self._max_travel_time = max_travel_time

@property
def image(self) -> Optional[bytes]:
"""
The image of the raster.

:rtype: bytes
"""
return self._image

@property
def max_travel_time(self) -> int:
"""
The max travel time of the raster in seconds.

:return: int
"""
return self._max_travel_time

def __repr__(self): # pragma: no cover
return "Raster({})".format(self.max_travel_time)
26 changes: 14 additions & 12 deletions routingpy/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,28 @@
from .mapbox_osrm import MapboxOSRM
from .mapbox_valhalla import MapboxValhalla
from .openrouteservice import ORS
from .opentripplanner_v2 import OpenTripPlannerV2
from .osrm import OSRM
from .valhalla import Valhalla

# Provide synonyms
_SERVICE_TO_ROUTER = {
"ors": ORS,
"openrouteservice": ORS,
"osrm": OSRM,
"google": Google,
khamaileon marked this conversation as resolved.
Show resolved Hide resolved
"graphhopper": Graphhopper,
"here": HereMaps,
"heremaps": HereMaps,
"mapbox_osrm": MapboxOSRM,
"mapbox-osrm": MapboxOSRM,
"mapboxosrm": MapboxOSRM,
"mapbox": MapboxOSRM,
"valhalla": Valhalla,
"mapbox_valhalla": MapboxValhalla,
"mapbox-osrm": MapboxOSRM,
"mapbox-valhalla": MapboxValhalla,
"mapbox": MapboxOSRM,
"mapboxosrm": MapboxOSRM,
"mapboxvalhalla": MapboxValhalla,
"graphhopper": Graphhopper,
"google": Google,
"here": HereMaps,
"heremaps": HereMaps,
"openrouteservice": ORS,
"ors": ORS,
"osrm": OSRM,
"otp_v2": OpenTripPlannerV2,
"valhalla": Valhalla,
}


Expand All @@ -61,7 +63,7 @@ def get_router_by_name(router_name):
:param router_name: Name of the router as string.
:type router_name: str

:rtype: Union[:class:`routingpy.routers.google.Google`, :class:`routingpy.routers.graphhopper.Graphhopper`, :class:`routingpy.routers.heremaps.HereMaps`, :class:`routingpy.routers.mapbox_osrm.MapBoxOSRM`, :class:`routingpy.routers.mapbox_valhalla.MapBoxValhalla`, :class:`routingpy.routers.openrouteservice.ORS`, :class:`routingpy.routers.osrm.OSRM`, :class:`routingpy.routers.valhalla.Valhalla`]
:rtype: Union[:class:`routingpy.routers.google.Google`, :class:`routingpy.routers.graphhopper.Graphhopper`, :class:`routingpy.routers.heremaps.HereMaps`, :class:`routingpy.routers.mapbox_osrm.MapBoxOSRM`, :class:`routingpy.routers.mapbox_valhalla.MapBoxValhalla`, :class:`routingpy.routers.openrouteservice.ORS`, :class:`routingpy.routers.osrm.OSRM`, :class:`routingpy.routers.otp_v2.OpenTripPlannerV2`, :class:`routingpy.routers.valhalla.Valhalla`]

"""
try:
Expand Down
2 changes: 1 addition & 1 deletion routingpy/routers/openrouteservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ def isochrones(
intervals: List[int],
interval_type: Optional[str] = "time",
units: Optional[str] = None,
location_type: Optional[str] = None,
location_type: Optional[str] = "start",
smoothing: Optional[float] = None,
attributes: Optional[List[str]] = None,
intersections: Optional[bool] = None,
Expand Down
Loading