95 lines
4.8 KiB
Python
95 lines
4.8 KiB
Python
from odoo import models, fields, tools, _, api
|
|
from odoo.exceptions import UserError
|
|
import requests
|
|
import logging
|
|
import datetime
|
|
from typing import Literal
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GeoRouter(models.AbstractModel):
|
|
""" Abstract class used to call Google Route API and convert responses
|
|
into useful distance and route data.
|
|
"""
|
|
_name = 'base.geo_router'
|
|
_description = "Geographic Router"
|
|
|
|
units = Literal['metric', 'imperial']
|
|
|
|
@api.model
|
|
def _get_api_key(self):
|
|
apikey = self.env['ir.config_parameter'].sudo().get_param('base_geolocalize.google_map_api_key')
|
|
if not apikey:
|
|
raise UserError(_(
|
|
"API key for GeoCoding (Places) required.\n"
|
|
"Visit https://developers.google.com/maps/documentation/geocoding/get-api-key for more information."
|
|
))
|
|
return apikey
|
|
|
|
@api.model
|
|
def get_driving_distance_time(self, origin, destination, departure_time: datetime.datetime = None,
|
|
arrival_time: datetime.datetime = None, units: units = 'metric',
|
|
avoid_tolls=False, avoid_highways=False, avoid_ferries=False) -> (float, float):
|
|
""" Calculates the route between two addresses and returns the distance it either in Kilometers or Miles and
|
|
the time in minutes.
|
|
|
|
:param origin: The origin address (res.partner) from which to drive (origin).
|
|
:param destination: The destination address (res.partner).
|
|
:param departure_time: The departure time to use when planning the route (traffic aware).
|
|
Cannot be set simultaneously with arrival_time.
|
|
:param arrival_time: The arrival time to use when planning the route (traffic aware).
|
|
Cannot be set simultaneously with departure_time.
|
|
:param units: Type of units to return: 'imperial' for Miles, 'metric' for Kilometers.
|
|
:param avoid_tolls: Whether Google should calculate a route avoiding tolls.
|
|
:param avoid_highways: Whether Google should calculate a route avoiding highways.
|
|
:param avoid_ferries: Whether Google should calculate a route avoiding ferries.
|
|
:returns: tuple(distance: float, time: float)
|
|
"""
|
|
if departure_time and arrival_time:
|
|
raise ValueError("Route cannot be calculated with both an arrival time and departure time.")
|
|
apikey = self._get_api_key()
|
|
origin_addr = self.env['base.geocoder'].geo_query_address(street=origin.street, zip=origin.zip,
|
|
city=origin.city, state=origin.state_id.name,
|
|
country=origin.country_id.name)
|
|
destination_addr = self.env['base.geocoder'].geo_query_address(street=destination.street, zip=destination.zip,
|
|
city=destination.city,
|
|
state=destination.state_id.name,
|
|
country=destination.country_id.name)
|
|
params = {
|
|
'origin': {
|
|
'address': origin_addr,
|
|
},
|
|
'destination': {
|
|
'address': destination_addr,
|
|
},
|
|
'travelMode': 'DRIVE',
|
|
'computeAlternativeRoutes': False,
|
|
'routeModifiers': {
|
|
'avoidTolls': avoid_tolls,
|
|
'avoidHighways': avoid_highways,
|
|
'avoidFerries': avoid_ferries,
|
|
},
|
|
'languageCode': 'en-US',
|
|
'units': units,
|
|
}
|
|
if departure_time or arrival_time:
|
|
params.update({
|
|
'routingPreference': 'TRAFFIC_AWARE',
|
|
'departureTime' if departure_time else 'arrivalTime': departure_time or arrival_time
|
|
})
|
|
|
|
url = "https://routes.googleapis.com/directions/v2:computeRoutes"
|
|
distance_field = 'distanceMeters' if units == 'metric' else 'distanceMiles'
|
|
result = requests.post(url,
|
|
json=params,
|
|
headers={'X-Goog-Api-Key': apikey,
|
|
'Content-Type': 'application/json',
|
|
'X-Goog-FieldMask': f"routes.duration,routes.{distance_field}"}).json()
|
|
if 'routes' not in result:
|
|
_logger.error(f"Failed to retrieve route between {origin_addr} and {destination_addr}. "
|
|
f"Google response: {result}.")
|
|
route = result['routes'][0]
|
|
distance = (route[distance_field] / 1000) if units == 'metric' else route[distance_field]
|
|
time = int(route['duration'].strip('s')) / 60
|
|
return distance, time
|