Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Der-Henning
GitHub Repository: Der-Henning/tgtg
Path: blob/main/tgtg_scanner/models/location.py
725 views
1
import logging
2
from dataclasses import dataclass
3
from typing import Union
4
5
import googlemaps
6
7
from tgtg_scanner.errors import LocationConfigurationError
8
9
log = logging.getLogger("tgtg")
10
11
12
@dataclass
13
class DistanceTime:
14
"""
15
Dataclass for distance and time.
16
"""
17
18
distance: float
19
duration: float
20
travel_mode: str
21
22
23
class Location:
24
WALKING_MODE = "walking"
25
DRIVING_MODE = "driving"
26
PUBLIC_TRANSPORT_MODE = "transit"
27
BIKING_MODE = "bicycling"
28
29
def __init__(self, enabled: bool = False, api_key: Union[str, None] = None, origin: Union[str, None] = None) -> None:
30
"""
31
Initializes Location class.
32
First run flag important only for validating origin address.
33
"""
34
self.enabled = enabled
35
self.origin = origin
36
if enabled:
37
if api_key is None or self.origin is None:
38
raise LocationConfigurationError("Location enabled but no API key or origin address given")
39
try:
40
self.gmaps = googlemaps.Client(key=api_key)
41
if not self._is_address_valid(self.origin):
42
raise LocationConfigurationError("Invalid origin address")
43
except (ValueError, googlemaps.exceptions.ApiError) as exc:
44
raise LocationConfigurationError(exc) from exc
45
46
# cached DistanceTime object for each item_id+mode
47
self.distancetime_dict: dict[str, DistanceTime] = {}
48
49
def calculate_distance_time(self, destination: str, travel_mode: str) -> Union[DistanceTime, None]:
50
"""
51
Calculates the distance and time taken to travel from origin to
52
destination using the given mode of transportation.
53
Returns distance and time in km and minutes respectively.
54
"""
55
if not self.enabled:
56
log.debug("Location service disabled")
57
return None
58
59
key = f"{destination}_{travel_mode}"
60
61
# use cached value if available
62
if key in self.distancetime_dict:
63
return self.distancetime_dict[key]
64
65
if not self._is_address_valid(destination):
66
return None
67
68
log.debug(f"Sending Google Maps API request: {destination} using {travel_mode} mode")
69
70
# calculate distance and time
71
directions = self.gmaps.directions(self.origin, destination, mode=travel_mode)
72
distance_time = DistanceTime(
73
float(directions[0]["legs"][0]["distance"]["value"]),
74
float(directions[0]["legs"][0]["duration"]["value"]),
75
travel_mode,
76
)
77
78
# cache value
79
self.distancetime_dict[key] = distance_time
80
81
return distance_time
82
83
def _is_address_valid(self, address: str) -> bool:
84
"""
85
Checks if the given address is valid using the
86
Google Maps Geocoding API.
87
"""
88
if len(self.gmaps.geocode(address)) == 0:
89
log.debug(f"Invalid address: {address}")
90
return False
91
return True
92
93