Skip to content

Commit 4c6cd06

Browse files
authored
Adding HERE provider (#7)
* Adding HERE
1 parent e75ca46 commit 4c6cd06

File tree

7 files changed

+132
-6
lines changed

7 files changed

+132
-6
lines changed

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
This tool compares the travel times obtained from [TravelTime Routes API](https://docs.traveltime.com/api/reference/routes),
44
[Google Maps Directions API](https://developers.google.com/maps/documentation/directions/get-directions),
5-
[TomTom Routing API](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/routing-service)
5+
[TomTom Routing API](https://developer.tomtom.com/routing-api/documentation/tomtom-maps/routing-service),
6+
[HERE Routing API](https://www.here.com/docs/bundle/routing-api-v8-api-reference)
67
and [Mapbox Directions API](https://docs.mapbox.com/api/navigation/directions/).
78
Source code is available on [GitHub](https://github.com/traveltime-dev/traveltime-google-comparison).
89

910
## Features
1011

11-
- Get travel times from TravelTime API, Google Maps API, TomTom API and Mapbox in parallel, for provided origin/destination pairs and a set
12+
- Get travel times from TravelTime API, Google Maps API, TomTom API, HERE API and Mapbox API in parallel, for provided origin/destination pairs and a set
1213
of departure times.
1314
- Departure times are calculated based on user provided start time, end time and interval.
1415
- Analyze the differences between the results and print out the average error percentage.
@@ -48,6 +49,12 @@ For TomTom API:
4849
export TOMTOM_API_KEY=[Your TomTom API Key]
4950
```
5051

52+
For HERE API:
53+
54+
```bash
55+
export HERE_API_KEY=[Your HERE API Key]
56+
```
57+
5158
For Mapbox API:
5259

5360
```bash
@@ -96,6 +103,8 @@ Optional arguments:
96103
It is enforced on per-second basis, to avoid bursts.
97104
- `--mapbox-max-rpm [int]`: Set max number of parallel requests sent to Mapbox API per minute. Default is 60.
98105
It is enforced on per-second basis, to avoid bursts.
106+
- `--here-max-rpm [int]`: Set max number of parallel requests sent to HERE API per minute. Default is 60.
107+
It is enforced on per-second basis, to avoid bursts.
99108
- `--traveltime-max-rpm [int]`: Set max number of parallel requests sent to TravelTime API per minute. Default is 60.
100109
It is enforced on per-second basis, to avoid bursts.
101110

src/traveltime_google_comparison/collect.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
GOOGLE_API = "google"
1717
TOMTOM_API = "tomtom"
18+
HERE_API = "here"
1819
MAPBOX_API = "mapbox"
1920
TRAVELTIME_API = "traveltime"
2021

@@ -24,6 +25,8 @@ def get_capitalized_provider_name(provider: str) -> str:
2425
return "Google"
2526
elif provider == "tomtom":
2627
return "TomTom"
28+
elif provider == "here":
29+
return "HERE"
2730
elif provider == "mapbox":
2831
return "Mapbox"
2932
elif provider == "traveltime":
@@ -40,6 +43,7 @@ class Fields:
4043
TRAVEL_TIME = {
4144
GOOGLE_API: "google_travel_time",
4245
TOMTOM_API: "tomtom_travel_time",
46+
HERE_API: "here_travel_time",
4347
MAPBOX_API: "mapbox_travel_time",
4448
TRAVELTIME_API: "tt_travel_time",
4549
}
@@ -147,6 +151,7 @@ async def collect_travel_times(
147151
{
148152
Fields.TRAVEL_TIME[GOOGLE_API]: "first",
149153
Fields.TRAVEL_TIME[TOMTOM_API]: "first",
154+
Fields.TRAVEL_TIME[HERE_API]: "first",
150155
Fields.TRAVEL_TIME[MAPBOX_API]: "first",
151156
Fields.TRAVEL_TIME[TRAVELTIME_API]: "first",
152157
}

src/traveltime_google_comparison/config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@
1010

1111
DEFAULT_GOOGLE_RPM = 60
1212
DEFAULT_TOMTOM_RPM = 60
13+
DEFAULT_HERE_RPM = 60
1314
DEFAULT_MAPBOX_RPM = 60
1415
DEFAULT_TRAVELTIME_RPM = 60
1516

1617
GOOGLE_API_KEY_VAR_NAME = "GOOGLE_API_KEY"
1718
TOMTOM_API_KEY_VAR_NAME = "TOMTOM_API_KEY"
19+
HERE_API_KEY_VAR_NAME = "HERE_API_KEY"
1820
MAPBOX_API_KEY_VAR_NAME = "MAPBOX_API_KEY"
1921
TRAVELTIME_APP_ID_VAR_NAME = "TRAVELTIME_APP_ID"
2022
TRAVELTIME_API_KEY_VAR_NAME = "TRAVELTIME_API_KEY"
@@ -59,6 +61,13 @@ def parse_args():
5961
default=DEFAULT_TOMTOM_RPM,
6062
help="Maximum number of requests sent to TomTom API per minute",
6163
)
64+
parser.add_argument(
65+
"--here-max-rpm",
66+
required=False,
67+
type=int,
68+
default=DEFAULT_HERE_RPM,
69+
help="Maximum number of requests sent to HERE API per minute",
70+
)
6271
parser.add_argument(
6372
"--mapbox-max-rpm",
6473
required=False,
@@ -101,6 +110,14 @@ def retrieve_tomtom_api_key():
101110
return tomtom_api_key
102111

103112

113+
def retrieve_here_api_key():
114+
here_api_key = os.environ.get(HERE_API_KEY_VAR_NAME)
115+
116+
if not here_api_key:
117+
raise ValueError(f"{HERE_API_KEY_VAR_NAME} not set in environment variables.")
118+
return here_api_key
119+
120+
104121
def retrieve_mapbox_api_key():
105122
mapbox_api_key = os.environ.get(MAPBOX_API_KEY_VAR_NAME)
106123

src/traveltime_google_comparison/main.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from traveltime_google_comparison import config
88
from traveltime_google_comparison.analysis import run_analysis
99
from traveltime_google_comparison.collect import (
10+
HERE_API,
1011
MAPBOX_API,
1112
Fields,
1213
GOOGLE_API,
@@ -25,7 +26,7 @@
2526

2627

2728
async def run():
28-
providers = [GOOGLE_API, TOMTOM_API, MAPBOX_API]
29+
providers = [GOOGLE_API, TOMTOM_API, HERE_API, MAPBOX_API]
2930
args = config.parse_args()
3031
csv = pd.read_csv(
3132
args.input, usecols=[Fields.ORIGIN, Fields.DESTINATION]
@@ -38,6 +39,7 @@ async def run():
3839
request_handlers = factory.initialize_request_handlers(
3940
args.google_max_rpm,
4041
args.tomtom_max_rpm,
42+
args.here_max_rpm,
4143
args.mapbox_max_rpm,
4244
args.traveltime_max_rpm,
4345
)
@@ -50,6 +52,7 @@ async def run():
5052
Fields.DEPARTURE_TIME,
5153
Fields.TRAVEL_TIME[GOOGLE_API],
5254
Fields.TRAVEL_TIME[TOMTOM_API],
55+
Fields.TRAVEL_TIME[HERE_API],
5356
Fields.TRAVEL_TIME[MAPBOX_API],
5457
Fields.TRAVEL_TIME[TRAVELTIME_API],
5558
],
@@ -61,6 +64,7 @@ async def run():
6164
filtered_travel_times_df = travel_times_df.loc[
6265
travel_times_df[Fields.TRAVEL_TIME[GOOGLE_API]].notna()
6366
& travel_times_df[Fields.TRAVEL_TIME[TOMTOM_API]].notna()
67+
& travel_times_df[Fields.TRAVEL_TIME[HERE_API]].notna()
6468
& travel_times_df[Fields.TRAVEL_TIME[MAPBOX_API]].notna()
6569
& travel_times_df[Fields.TRAVEL_TIME[TRAVELTIME_API]].notna(),
6670
:,

src/traveltime_google_comparison/requests/factory.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,41 @@
11
from typing import Dict
22

33
from traveltime_google_comparison.collect import (
4-
MAPBOX_API,
54
TOMTOM_API,
5+
HERE_API,
6+
MAPBOX_API,
67
TRAVELTIME_API,
78
GOOGLE_API,
89
)
910
from traveltime_google_comparison.config import (
1011
retrieve_google_api_key,
12+
retrieve_here_api_key,
1113
retrieve_mapbox_api_key,
1214
retrieve_tomtom_api_key,
1315
retrieve_traveltime_credentials,
1416
)
1517
from traveltime_google_comparison.requests.base_handler import BaseRequestHandler
1618
from traveltime_google_comparison.requests.google_handler import GoogleRequestHandler
1719
from traveltime_google_comparison.requests.tomtom_handler import TomTomRequestHandler
20+
from traveltime_google_comparison.requests.here_handler import HereRequestHandler
1821
from traveltime_google_comparison.requests.mapbox_handler import MapboxRequestHandler
1922
from traveltime_google_comparison.requests.traveltime_handler import (
2023
TravelTimeRequestHandler,
2124
)
2225

2326

2427
def initialize_request_handlers(
25-
google_max_rpm, tomtom_max_rpm, mapbox_max_rpm, traveltime_max_rpm
28+
google_max_rpm, tomtom_max_rpm, here_max_rpm, mapbox_max_rpm, traveltime_max_rpm
2629
) -> Dict[str, BaseRequestHandler]:
2730
google_api_key = retrieve_google_api_key()
2831
tomtom_api_key = retrieve_tomtom_api_key()
32+
here_api_key = retrieve_here_api_key()
2933
mapbox_api_key = retrieve_mapbox_api_key()
3034
credentials = retrieve_traveltime_credentials()
3135
return {
3236
GOOGLE_API: GoogleRequestHandler(google_api_key, google_max_rpm),
3337
TOMTOM_API: TomTomRequestHandler(tomtom_api_key, tomtom_max_rpm),
38+
HERE_API: HereRequestHandler(here_api_key, here_max_rpm),
3439
MAPBOX_API: MapboxRequestHandler(mapbox_api_key, mapbox_max_rpm),
3540
TRAVELTIME_API: TravelTimeRequestHandler(
3641
credentials.app_id, credentials.api_key, traveltime_max_rpm
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import logging
2+
from datetime import datetime
3+
4+
import aiohttp
5+
from aiolimiter import AsyncLimiter
6+
from traveltimepy import Coordinates
7+
8+
from traveltime_google_comparison.config import Mode
9+
from traveltime_google_comparison.requests.base_handler import (
10+
BaseRequestHandler,
11+
RequestResult,
12+
)
13+
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class HereApiError(Exception):
18+
pass
19+
20+
21+
class HereRequestHandler(BaseRequestHandler):
22+
HERE_ROUTES_URL = "https://router.hereapi.com/v8/routes"
23+
24+
default_timeout = aiohttp.ClientTimeout(total=60)
25+
26+
def __init__(self, api_key, max_rpm):
27+
self.api_key = api_key
28+
self._rate_limiter = AsyncLimiter(max_rpm // 60, 1)
29+
30+
async def send_request(
31+
self,
32+
origin: Coordinates,
33+
destination: Coordinates,
34+
departure_time: datetime,
35+
mode: Mode,
36+
) -> RequestResult:
37+
params = {
38+
"transportMode": get_here_specific_mode(mode),
39+
"origin": f"{origin.lat},{origin.lng}",
40+
"destination": f"{destination.lat},{destination.lng}",
41+
"return": "summary",
42+
"departureTime": departure_time.strftime("%Y-%m-%dT%H:%M:%S"),
43+
"apikey": self.api_key,
44+
}
45+
try:
46+
async with aiohttp.ClientSession(
47+
timeout=self.default_timeout
48+
) as session, session.get(self.HERE_ROUTES_URL, params=params) as response:
49+
data = await response.json()
50+
if response.status == 200:
51+
first_route = data["routes"][0]
52+
53+
if not first_route:
54+
raise HereApiError(
55+
"No route found between origin and destination."
56+
)
57+
58+
# I think for a simple routing request, there should only be one section. But just in case
59+
# I'm taking the sum of all sections
60+
total_duration = sum(
61+
section["summary"]["duration"]
62+
for section in first_route["sections"]
63+
)
64+
65+
return RequestResult(travel_time=total_duration)
66+
else:
67+
error_message = data.get("detailedError", "")
68+
logger.error(
69+
f"Error in HERE API response: {response.status} - {error_message}"
70+
)
71+
return RequestResult(None)
72+
except Exception as e:
73+
logger.error(f"Exception during requesting HERE API, {e}")
74+
return RequestResult(None)
75+
76+
77+
def get_here_specific_mode(mode: Mode) -> str:
78+
if mode == Mode.DRIVING:
79+
return "car"
80+
elif mode == Mode.PUBLIC_TRANSPORT:
81+
return "bus" # HERE doesn't have a general mode for transit / PT
82+
# TODO: figure out how to compare PT modes accorss different providers
83+
84+
else:
85+
raise ValueError(f"Unsupported mode: `{mode.value}`")

src/traveltime_google_comparison/requests/tomtom_handler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ def get_tomtom_specific_mode(mode: Mode) -> str:
7171
if mode == Mode.DRIVING:
7272
return "car"
7373
elif mode == Mode.PUBLIC_TRANSPORT:
74-
return "bus"
74+
return "bus" # TomTom doesn't have a general mode for transit / PT
75+
# TODO: figure out how to compare PT modes accorss different providers
7576
else:
7677
raise ValueError(f"Unsupported mode: `{mode.value}`")

0 commit comments

Comments
 (0)