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