Skip to content

Commit 3dcc3db

Browse files
author
Guillaume Latour
committed
implement traffic density endpoint
1 parent de916f7 commit 3dcc3db

File tree

6 files changed

+156
-54
lines changed

6 files changed

+156
-54
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ maintainers = [
2222
name = "tomtom_api"
2323
readme = "readme.md"
2424
requires-python = ">=3.8"
25-
version = "2024.6.27"
25+
version = "2024.7.4"
2626

2727
[project.urls]
2828
repository = "https://stash.macq.eu/projects/RDDS/repos/tomtom-api"

src/tomtom_api/client.py

+36-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from tomtom_api.traffic_stats.models.geospatial import TomtomNetwork, TomtomRoad
1212
from tomtom_api.traffic_stats.models.jobs.area import TomtomAreaJob
1313
from tomtom_api.traffic_stats.models.jobs.route import TomtomRouteJob
14+
from tomtom_api.traffic_stats.models.jobs.traffic_density import TomtomTrafficDensityJob
1415
from tomtom_api.traffic_stats.models.responses import (
1516
JsonIpResponse,
1617
TomtomErrorResponse,
@@ -147,7 +148,7 @@ def route_analysis(
147148
date_ranges: List[TomtomDateRange],
148149
time_sets: List[TomtomTimeSet],
149150
accept_mode: Optional[Literal["AUTO", "MANUAL"]] = "AUTO",
150-
map_version: Optional[float] = 2023.03,
151+
map_version: Optional[float] = None,
151152
average_sample_size_threshold: Optional[int] = None,
152153
) -> TomtomResponseAnalysis:
153154
"""
@@ -176,7 +177,7 @@ def area_analysis(
176177
date_range: TomtomDateRange,
177178
time_sets: List[TomtomTimeSet],
178179
accept_mode: Optional[Literal["AUTO", "MANUAL"]] = "AUTO",
179-
map_version: Optional[float] = 2023.03,
180+
map_version: Optional[float] = None,
180181
average_sample_size_threshold: Optional[int] = None,
181182
) -> TomtomResponseAnalysis:
182183
job = TomtomAreaJob(
@@ -191,10 +192,33 @@ def area_analysis(
191192
)
192193
return self.post_job_area_analysis(job)
193194

195+
def traffic_density(
196+
self,
197+
job_name: str,
198+
distance_unit: Literal["KILOMETERS", "MILES"],
199+
network: TomtomNetwork,
200+
date_range: TomtomDateRange,
201+
time_sets: List[TomtomTimeSet],
202+
accept_mode: Optional[Literal["AUTO", "MANUAL"]] = "AUTO",
203+
map_version: Optional[float] = None,
204+
average_sample_size_threshold: Optional[int] = None,
205+
) -> TomtomResponseAnalysis:
206+
job = TomtomTrafficDensityJob(
207+
job_name=job_name,
208+
distance_unit=distance_unit,
209+
network=network,
210+
date_range=date_range,
211+
time_sets=time_sets,
212+
accept_mode=accept_mode,
213+
map_version=map_version,
214+
average_sample_size_threshold=average_sample_size_threshold,
215+
)
216+
return self.post_job_traffic_density(job)
217+
194218
def find_route(
195219
self,
196220
road: TomtomRoad,
197-
map_version: Optional[str] = "2016.12",
221+
map_version: Optional[str] = None,
198222
map_type: Optional[str] = "DSEG_NOSPLIT",
199223
) -> TomtomResponseRouteFound:
200224
"""
@@ -234,6 +258,15 @@ def post_job_area_analysis(self, job: TomtomAreaJob) -> TomtomResponseAnalysis:
234258
tomtom_response = TomtomResponseAnalysis.from_dict(request_response.json())
235259
return tomtom_response
236260

261+
def post_job_traffic_density(
262+
self, job: TomtomTrafficDensityJob
263+
) -> TomtomResponseAnalysis:
264+
url = f"https://{self.base_url}/traffic/trafficstats/trafficdensity/{self.version}?key={self.key}"
265+
266+
request_response = self.request("post", url, data=json.dumps(job.to_dict()))
267+
tomtom_response = TomtomResponseAnalysis.from_dict(request_response.json())
268+
return tomtom_response
269+
237270
def status(self, job_id: int) -> TomtomResponseStatus:
238271
"""When a job has been initiated via the API request it is possible to check the status.
239272

src/tomtom_api/traffic_stats/models/jobs/area.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(
1818
time_sets: List[TomtomTimeSet],
1919
distance_unit: Literal['KILOMETERS', 'MILES'] = 'KILOMETERS',
2020
accept_mode: Optional[Literal['AUTO', 'MANUAL']] = 'AUTO',
21-
map_version: Optional[float] = 2020.09,
21+
map_version: Optional[float] = None,
2222
average_sample_size_threshold: Optional[int] = None
2323
):
2424
self.network = network

src/tomtom_api/traffic_stats/models/jobs/base.py

+35-30
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,25 @@
99

1010
class TomtomJob:
1111
"""Data structure allowing the manipulation of the tomtom jobs"""
12+
1213
job_name: str
1314
date_ranges: List[TomtomDateRange]
1415
time_sets: List[TomtomTimeSet]
15-
distance_unit: str = 'KILOMETERS'
16-
accept_mode: Optional[str] = 'AUTO'
16+
distance_unit: str = "KILOMETERS"
17+
accept_mode: Optional[str] = "AUTO"
1718
# see here for more info about map versions
1819
# https://developer.tomtom.com/traffic-stats/documentation/api/available-maps
19-
map_version: Optional[float] = 2020.09
20+
map_version: Optional[float] = None
2021
average_sample_size_threshold: Optional[int] = None
2122

2223
def __init__(
2324
self,
2425
job_name: str,
2526
time_sets: List[TomtomTimeSet],
26-
distance_unit: Literal['KILOMETERS', 'MILES'] = 'KILOMETERS',
27-
accept_mode: Optional[Literal['AUTO', 'MANUAL']] = 'AUTO',
28-
map_version: Optional[float] = 2020.09,
29-
average_sample_size_threshold: Optional[int] = None
27+
distance_unit: Literal["KILOMETERS", "MILES"] = "KILOMETERS",
28+
accept_mode: Optional[Literal["AUTO", "MANUAL"]] = "AUTO",
29+
map_version: Optional[float] = None,
30+
average_sample_size_threshold: Optional[int] = None,
3031
):
3132
"""Data structure containing all the job information.
3233
@@ -59,16 +60,16 @@ def __init__(
5960

6061
if len(time_sets) > MAX_TIME_SETS_COUNT:
6162
raise ValueError(
62-
f'Impossible to query for more than {MAX_TIME_SETS_COUNT} time sets, ({len(time_sets)} given)'
63+
f"Impossible to query for more than {MAX_TIME_SETS_COUNT} time sets, ({len(time_sets)} given)"
6364
)
6465
self.time_sets = time_sets
6566

66-
if distance_unit.upper() not in ['KILOMETERS', 'MILES']:
67-
raise ValueError(f'The given `distance_unit` is invalid ({distance_unit})')
67+
if distance_unit.upper() not in ["KILOMETERS", "MILES"]:
68+
raise ValueError(f"The given `distance_unit` is invalid ({distance_unit})")
6869
self.distance_unit = str(distance_unit).upper()
6970

70-
if accept_mode is not None and accept_mode.upper() not in ['AUTO', 'MANUAL']:
71-
raise ValueError(f'The given `accept_mode` is invalid ({accept_mode})')
71+
if accept_mode is not None and accept_mode.upper() not in ["AUTO", "MANUAL"]:
72+
raise ValueError(f"The given `accept_mode` is invalid ({accept_mode})")
7273
self.accept_mode = accept_mode
7374

7475
self.map_version = map_version
@@ -77,19 +78,23 @@ def __init__(
7778
@classmethod
7879
def from_dict(cls, dict_object: Dict[str, Any]) -> TomtomJob:
7980
return cls(
80-
job_name=dict_object['jobName'],
81-
time_sets=[TomtomTimeSet.from_dict(t) for t in dict_object['timeSets']],
82-
distance_unit=dict_object['distanceUnit'],
83-
accept_mode=dict_object['acceptMode'],
84-
map_version=dict_object['mapVersion'],
85-
average_sample_size_threshold=None if 'averageSampleSizeThreshold' not in dict_object else dict_object[
86-
'averageSampleSizeThreshold']
81+
job_name=dict_object["jobName"],
82+
time_sets=[TomtomTimeSet.from_dict(t) for t in dict_object["timeSets"]],
83+
distance_unit=dict_object["distanceUnit"],
84+
accept_mode=dict_object["acceptMode"],
85+
map_version=dict_object["mapVersion"],
86+
average_sample_size_threshold=(
87+
None
88+
if "averageSampleSizeThreshold" not in dict_object
89+
else dict_object["averageSampleSizeThreshold"]
90+
),
8791
)
8892

89-
def md5(self, salt: str = '') -> str:
93+
def md5(self, salt: str = "") -> str:
9094
import hashlib
95+
9196
json = self.to_json()
92-
md5_hash = hashlib.md5(f'{json}{salt}'.encode('utf-8'))
97+
md5_hash = hashlib.md5(f"{json}{salt}".encode("utf-8"))
9398
return md5_hash.hexdigest()
9499

95100
def to_dict(self) -> Dict[str, Any]:
@@ -102,21 +107,21 @@ def to_dict(self) -> Dict[str, Any]:
102107
The dictionary with the keys specified in the documentation.
103108
"""
104109
job_dict = {
105-
'jobName': self.job_name,
106-
'distanceUnit': self.distance_unit,
107-
'mapVersion': None,
108-
'acceptMode': None,
109-
'timeSets': [t.to_dict() for t in self.time_sets],
110-
'averageSampleSizeThreshold': None
110+
"jobName": self.job_name,
111+
"distanceUnit": self.distance_unit,
112+
"mapVersion": None,
113+
"acceptMode": None,
114+
"timeSets": [t.to_dict() for t in self.time_sets],
115+
"averageSampleSizeThreshold": None,
111116
}
112117

113118
# manage optional fields
114119
if self.accept_mode is not None:
115-
job_dict['acceptMode'] = self.accept_mode
120+
job_dict["acceptMode"] = self.accept_mode
116121
if self.map_version is not None:
117-
job_dict['mapVersion'] = self.map_version
122+
job_dict["mapVersion"] = self.map_version
118123
if self.average_sample_size_threshold is not None:
119-
job_dict['averageSampleSizeThreshold'] = self.average_sample_size_threshold
124+
job_dict["averageSampleSizeThreshold"] = self.average_sample_size_threshold
120125

121126
job_dict = {k: v for k, v in job_dict.items() if v is not None}
122127

src/tomtom_api/traffic_stats/models/jobs/route.py

+26-19
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import Any, Dict, List, Literal, Optional
44

5-
from tomtom_api.traffic_stats import (MAX_DATE_RANGE_COUNT, MAX_ROAD_COUNT)
5+
from tomtom_api.traffic_stats import MAX_DATE_RANGE_COUNT, MAX_ROAD_COUNT
66
from tomtom_api.traffic_stats.models.geospatial import TomtomRoad
77
from tomtom_api.traffic_stats.models.jobs.base import TomtomJob
88
from tomtom_api.traffic_stats.models.time import TomtomDateRange, TomtomTimeSet
@@ -17,10 +17,10 @@ def __init__(
1717
routes: List[TomtomRoad],
1818
date_ranges: List[TomtomDateRange],
1919
time_sets: List[TomtomTimeSet],
20-
distance_unit: Literal['KILOMETERS', 'MILES'] = 'KILOMETERS',
21-
accept_mode: Optional[Literal['AUTO', 'MANUAL']] = 'AUTO',
22-
map_version: Optional[float] = 2020.09,
23-
average_sample_size_threshold: Optional[int] = None
20+
distance_unit: Literal["KILOMETERS", "MILES"] = "KILOMETERS",
21+
accept_mode: Optional[Literal["AUTO", "MANUAL"]] = "AUTO",
22+
map_version: Optional[float] = None,
23+
average_sample_size_threshold: Optional[int] = None,
2424
):
2525
"""Data structure containing all the job information.
2626
@@ -55,12 +55,14 @@ def __init__(
5555
"""
5656

5757
if len(routes) > MAX_ROAD_COUNT:
58-
raise ValueError(f'Impossible to query for more than {MAX_ROAD_COUNT} roads, ({len(routes)} given)')
58+
raise ValueError(
59+
f"Impossible to query for more than {MAX_ROAD_COUNT} roads, ({len(routes)} given)"
60+
)
5961
self.routes = routes
6062

6163
if len(date_ranges) > MAX_DATE_RANGE_COUNT:
6264
raise ValueError(
63-
f'Impossible to query for more than {MAX_DATE_RANGE_COUNT} date range, ({len(date_ranges)} given)'
65+
f"Impossible to query for more than {MAX_DATE_RANGE_COUNT} date range, ({len(date_ranges)} given)"
6466
)
6567
self.date_ranges = date_ranges
6668

@@ -70,21 +72,26 @@ def __init__(
7072
distance_unit=distance_unit,
7173
accept_mode=accept_mode,
7274
map_version=map_version,
73-
average_sample_size_threshold=average_sample_size_threshold
75+
average_sample_size_threshold=average_sample_size_threshold,
7476
)
7577

7678
@classmethod
7779
def from_dict(cls, dict_object: Dict[str, Any]) -> TomtomRouteJob:
7880
return cls(
79-
job_name=dict_object['jobName'],
80-
routes=[TomtomRoad.from_dict(r) for r in dict_object['routes']],
81-
date_ranges=[TomtomDateRange.from_dict(d) for d in dict_object['dateRanges']],
82-
time_sets=[TomtomTimeSet.from_dict(t) for t in dict_object['timeSets']],
83-
distance_unit=dict_object['distanceUnit'],
84-
accept_mode=dict_object['acceptMode'],
85-
map_version=dict_object['mapVersion'],
86-
average_sample_size_threshold=None if 'averageSampleSizeThreshold' not in dict_object else dict_object[
87-
'averageSampleSizeThreshold']
81+
job_name=dict_object["jobName"],
82+
routes=[TomtomRoad.from_dict(r) for r in dict_object["routes"]],
83+
date_ranges=[
84+
TomtomDateRange.from_dict(d) for d in dict_object["dateRanges"]
85+
],
86+
time_sets=[TomtomTimeSet.from_dict(t) for t in dict_object["timeSets"]],
87+
distance_unit=dict_object["distanceUnit"],
88+
accept_mode=dict_object["acceptMode"],
89+
map_version=dict_object["mapVersion"],
90+
average_sample_size_threshold=(
91+
None
92+
if "averageSampleSizeThreshold" not in dict_object
93+
else dict_object["averageSampleSizeThreshold"]
94+
),
8895
)
8996

9097
def to_dict(self) -> Dict[str, Any]:
@@ -98,8 +105,8 @@ def to_dict(self) -> Dict[str, Any]:
98105
"""
99106
job_dict = {
100107
**super().to_dict(),
101-
'dateRanges': [d.to_dict() for d in self.date_ranges],
102-
'routes': [r.to_dict() for r in self.routes]
108+
"dateRanges": [d.to_dict() for d in self.date_ranges],
109+
"routes": [r.to_dict() for r in self.routes],
103110
}
104111

105112
return job_dict
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Dict, List, Literal, Optional
4+
5+
from tomtom_api.traffic_stats.models.geospatial import TomtomNetwork
6+
from tomtom_api.traffic_stats.models.jobs.base import TomtomJob
7+
from tomtom_api.traffic_stats.models.time import TomtomDateRange, TomtomTimeSet
8+
9+
10+
class TomtomTrafficDensityJob(TomtomJob):
11+
network: TomtomNetwork
12+
13+
def __init__(
14+
self,
15+
job_name: str,
16+
network: TomtomNetwork,
17+
date_range: TomtomDateRange,
18+
time_sets: List[TomtomTimeSet],
19+
distance_unit: Literal['KILOMETERS', 'MILES'] = 'KILOMETERS',
20+
accept_mode: Optional[Literal['AUTO', 'MANUAL']] = 'AUTO',
21+
map_version: Optional[float] = None,
22+
average_sample_size_threshold: Optional[int] = None
23+
):
24+
self.network = network
25+
self.date_range = date_range
26+
super().__init__(job_name, time_sets, distance_unit, accept_mode, map_version, average_sample_size_threshold)
27+
28+
def to_dict(self) -> Dict[str, Any]:
29+
"""Transform this data type to a python dictionary.
30+
This dict is meant to be used to generate the payload given to some Tomtom API endpoints.
31+
32+
Returns
33+
-------
34+
Dict[str, float]
35+
The dictionary with the keys specified in the documentation.
36+
"""
37+
job_dict = {
38+
**super().to_dict(),
39+
'dateRange': self.date_range.to_dict(),
40+
'network': self.network.to_dict()
41+
}
42+
43+
return job_dict
44+
45+
@classmethod
46+
def from_dict(cls, dict_object: Dict[str, Any]) -> TomtomTrafficDensityJob:
47+
return cls(
48+
job_name=dict_object['jobName'],
49+
network=TomtomNetwork.from_dict(dict_object['network']),
50+
date_range=TomtomDateRange.from_dict(dict_object['dateRange']),
51+
time_sets=[TomtomTimeSet.from_dict(t) for t in dict_object['timeSets']],
52+
distance_unit=dict_object['distanceUnit'],
53+
accept_mode=dict_object['acceptMode'],
54+
map_version=dict_object['mapVersion'],
55+
average_sample_size_threshold=None if 'averageSampleSizeThreshold' not in dict_object else dict_object[
56+
'averageSampleSizeThreshold']
57+
)

0 commit comments

Comments
 (0)