From 64c402b1c07d9b1ef9f3920e94ac77d9290059fd Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sat, 16 Oct 2021 22:35:37 +0200 Subject: [PATCH] [#115] Added `google.maps.get_travel_time` method --- CHANGELOG.md | 2 + platypush/plugins/google/maps/__init__.py | 117 ++++++++++++++++++++++ platypush/schemas/maps.py | 34 +++++++ platypush/utils/__init__.py | 13 ++- 4 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 platypush/schemas/maps.py diff --git a/CHANGELOG.md b/CHANGELOG.md index cdbc74aced..ca11d0f4ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Given the high speed of development in the first phase, changes are being report ### Added - Support for IR virtual devices in Switchbot plugin. +- Added [`google.maps.get_travel_time`](https://docs.platypush.tech/platypush/plugins/google.maps.html#platypush.plugins.google.maps.GoogleMapsPlugin.get_travel_time) + method (closes #115). ## [0.22.3] - 2021-10-01 diff --git a/platypush/plugins/google/maps/__init__.py b/platypush/plugins/google/maps/__init__.py index 4eec5de8cd..d4382057e4 100644 --- a/platypush/plugins/google/maps/__init__.py +++ b/platypush/plugins/google/maps/__init__.py @@ -2,10 +2,17 @@ .. moduleauthor:: Fabio Manganiello """ +from datetime import datetime +from typing import List, Union, Optional + import requests from platypush.plugins import action from platypush.plugins.google import GooglePlugin +from platypush.schemas.maps import MapsTravelTimeSchema +from platypush.utils import to_datetime + +datetime_types = Union[str, int, float, datetime] class GoogleMapsPlugin(GooglePlugin): @@ -98,4 +105,114 @@ class GoogleMapsPlugin(GooglePlugin): return {'elevation': elevation} + @action + def get_travel_time(self, origins: List[str], destinations: List[str], + departure_time: Optional[datetime_types] = None, + arrival_time: Optional[datetime_types] = None, + units: str = 'metric', + avoid: Optional[List[str]] = None, + language: Optional[str] = None, + mode: Optional[str] = None, + traffic_model: Optional[str] = None, + transit_mode: Optional[List[str]] = None, + transit_route_preference: Optional[str] = None): + """ + Get the estimated travel time between a set of departure points and a set of destinations. + + :param origins: The starting point(s) for calculating travel distance and time. Locations are supplied in the + form of a place ID, an address, or latitude/longitude coordinates: + + * *Place ID*: If you supply a place ID, you must prefix it with ``place_id:``. + * *Address*: If you pass an address, the service geocodes the string and converts it to a + latitude/longitude coordinate to calculate distance. + * *Coordinates*: If you pass latitude/longitude coordinates, they they will snap to the nearest road. + + :param destinations: The arrival point(s) for calculating travel distance and time. Locations are supplied in + the form of a place ID, an address, or latitude/longitude coordinates: + + * *Place ID*: If you supply a place ID, you must prefix it with ``place_id:``. + * *Address*: If you pass an address, the service geocodes the string and converts it to a + latitude/longitude coordinate to calculate distance. + * *Coordinates*: If you pass latitude/longitude coordinates, they they will snap to the nearest road. + + :param departure_time: Preferred departure time, as a UNIX timestamp, an ISO8601 string or a datetime object. + :param arrival_time: Preferred arrival time, as a UNIX timestamp, an ISO8601 string or a datetime object. + :param units: ``metric`` (default) or ``imperial``. + :param avoid: Any of the following values: + + * ``tolls``: indicates that the calculated route should avoid toll roads/bridges. + * ``highways``: indicates that the calculated route should avoid highways. + * ``ferries``: indicates that the calculated route should avoid ferries. + * ``indoor`` indicates that the calculated route should avoid indoor steps for walking and transit + directions. + + :param language: Target language for the results (ISO string). If none is specified then the target language + is inferred from the ``Accept-Language`` header or the preferred language associated to the account. + :param mode: Any of the following: + + * ``DRIVING`` (default) + * ``WALKING`` + * ``BICYCLING`` + * ``TRANSIT`` + + :param traffic_model: Specifies the assumptions to use when calculating time in traffic. This setting affects + the value returned in the ``duration_in_traffic`` field in the response, which contains the predicted time + in traffic based on historical averages. Available values: + + * ``best_guess`` (default) indicates that the returned ``duration_in_traffic`` should be the best + estimate of travel time given what is known about both historical traffic conditions and live + traffic. Live traffic becomes more important the closer the departure_time is to now. + * ``pessimistic`` indicates that the returned ``duration_in_traffic`` should be longer than the actual + travel time on most days, though occasional days with particularly bad traffic conditions may exceed + this value. + * ``optimistic`` indicates that the returned ``duration_in_traffic`` should be shorter than the actual + travel time on most days, though occasional days with particularly good traffic conditions may be + faster than this value. + + :param transit_mode: Specifies one or more preferred modes of transit. This parameter may only be specified + for transit directions. Available values: + + * ``bus`` indicates that the calculated route should prefer travel by bus. + * ``subway`` indicates that the calculated route should prefer travel by subway. + * ``train`` indicates that the calculated route should prefer travel by train. + * ``tram`` indicates that the calculated route should prefer travel by tram and light rail. + * ``rail`` indicates that the calculated route should prefer travel by train, tram, light rail, and + subway. This is equivalent to ``transit_mode=["train", "tram", "subway"]``. + + :param transit_route_preference: Specifies preferences for transit routes. Using this parameter, you can + bias the options returned, rather than accepting the default best route chosen by the API. This parameter + may only be specified for transit directions. Available values: + + - ``less_walking`` indicates that the calculated route should prefer limited amounts of walking. + - ``fewer_transfers`` indicates that the calculated route should prefer a limited number of transfers. + + :return: .. schema:: maps.MapsTravelTimeSchema + """ + rs = requests.get( + 'https://maps.googleapis.com/maps/api/distancematrix/json', + params={ + 'origins': '|'.join(origins), + 'destinations': '|'.join(destinations), + 'units': units, + **({'departure_time': to_datetime(departure_time)} if departure_time else {}), + **({'arrival_time': to_datetime(arrival_time)} if arrival_time else {}), + **({'avoid': '|'.join(avoid)} if avoid else {}), + **({'language': language} if language else {}), + **({'mode': mode} if mode else {}), + **({'traffic_model': traffic_model} if traffic_model else {}), + **({'transit_mode': transit_mode} if transit_mode else {}), + **({'transit_route_preference': transit_route_preference} + if transit_route_preference else {}), + 'key': self.api_key, + }).json() + + assert not rs.get('error_message'), f'{rs["status"]}: {rs["error_message"]}' + rows = rs.get('rows', []) + assert rows, 'The API returned no rows' + elements = rows[0].get('elements') + assert elements, 'The API returned no elements' + + return MapsTravelTimeSchema().dump(elements, many=True) + + # vim:sw=4:ts=4:et: diff --git a/platypush/schemas/maps.py b/platypush/schemas/maps.py new file mode 100644 index 0000000000..e8d24b539b --- /dev/null +++ b/platypush/schemas/maps.py @@ -0,0 +1,34 @@ +from marshmallow import fields, INCLUDE +from marshmallow.schema import Schema + + +class MapsDistanceSchema(Schema): + text = fields.String(required=True, metadata=dict( + description='Distance expressed as readable text', + example='6.5 km', + )) + + value = fields.Number(required=True, metadata=dict( + description='Distance expressed as a numeric value according to the selected units', + example=6542, + )) + + +class MapsDurationSchema(Schema): + text = fields.String(required=True, metadata=dict( + description='Duration expressed as readable text', + example='13 mins', + )) + + value = fields.Number(required=True, metadata=dict( + description='Duration expressed in seconds', + example=777, + )) + + +class MapsTravelTimeSchema(Schema): + class Meta: + unknown = INCLUDE + + distance = fields.Nested(MapsDistanceSchema) + duration = fields.Nested(MapsDurationSchema) diff --git a/platypush/utils/__init__.py b/platypush/utils/__init__.py index 1ffc768320..df00254791 100644 --- a/platypush/utils/__init__.py +++ b/platypush/utils/__init__.py @@ -1,4 +1,5 @@ import ast +import datetime import hashlib import importlib import inspect @@ -10,8 +11,9 @@ import signal import socket import ssl import urllib.request -from typing import Optional, Tuple +from typing import Optional, Tuple, Union +from dateutil import parser, tz from redis import Redis logger = logging.getLogger('utils') @@ -451,4 +453,13 @@ def get_redis() -> Redis: from platypush.config import Config return Redis(**(Config.get('backend.redis') or {}).get('redis_args', {})) + +def to_datetime(t: Union[str, int, float, datetime.datetime]) -> datetime.datetime: + if isinstance(t, int) or isinstance(t, float): + return datetime.datetime.fromtimestamp(t, tz=tz.tzutc()) + if isinstance(t, str): + return parser.parse(t) + return t + + # vim:sw=4:ts=4:et: