1
- import asyncio
2
1
import os
3
2
4
3
import nextmv
5
4
import nextmv .cloud
6
5
import nextroute .schema as nextrouteSchema
7
6
import numpy as np
8
7
from nextpipe import FlowSpec , app , needs , step
9
- from traveltimepy import Coordinates , Location , Property , Transportation , TravelTimeSdk
8
+ from traveltimepy import Client
9
+ from traveltimepy .requests .common import Location , Coordinates , Property
10
+ from traveltimepy .requests .time_filter_fast import TimeFilterFastArrivalSearches , TimeFilterFastOneToMany
11
+ from traveltimepy .requests .transportation import TransportationFast
10
12
11
13
12
- async def async_part ( input_data : dict ):
14
+ def create_traveltime_client ( ):
13
15
import os
14
-
16
+
15
17
# Check if API credentials are available
16
18
app_id = os .getenv ("TT_APP_ID" )
17
19
api_key = os .getenv ("TT_API_KEY" )
18
-
20
+
19
21
if not app_id or not api_key :
20
22
raise ValueError (
21
23
"TravelTime API credentials not found. Please set the following environment variables:\n "
22
24
"- TT_APP_ID: Your TravelTime application ID\n "
23
25
"- TT_API_KEY: Your TravelTime API key\n \n "
24
26
"You can get these credentials from: https://docs.traveltime.com/api/overview/getting-keys"
25
27
)
26
-
27
- sdk = TravelTimeSdk (app_id = app_id , api_key = api_key )
28
- nextroute_input = nextrouteSchema .Input .from_dict (input_data )
29
- # Create locations for all stops
28
+
29
+ return Client (app_id = app_id , api_key = api_key )
30
+
31
+
32
+ def build_locations_list (nextroute_input ):
33
+ """
34
+ Build locations list from Nextroute input data.
35
+
36
+ Args:
37
+ nextroute_input: Parsed Nextroute input schema
38
+
39
+ Returns:
40
+ List of TravelTime Location objects
41
+ """
30
42
locations = []
31
- location_ids = []
32
43
33
44
# Add stops first
34
45
for stop in nextroute_input .stops :
@@ -38,7 +49,6 @@ async def async_part(input_data: dict):
38
49
coords = Coordinates (lat = stop .location .lat , lng = stop .location .lon )
39
50
)
40
51
)
41
- location_ids .append (stop .id )
42
52
43
53
# Add vehicle start/end locations for each vehicle
44
54
for vehicle in nextroute_input .vehicles :
@@ -53,7 +63,6 @@ async def async_part(input_data: dict):
53
63
)
54
64
)
55
65
)
56
- location_ids .append (start_id )
57
66
58
67
# Add end location
59
68
end_id = f"{ vehicle .id } -end"
@@ -66,23 +75,21 @@ async def async_part(input_data: dict):
66
75
)
67
76
)
68
77
)
69
- location_ids .append (end_id )
70
78
71
- # Prepare the search_ids dictionary - each location to all other locations
72
- search_ids = {}
73
- for origin in location_ids :
74
- # Each origin should search for all destinations except itself
75
- search_ids [origin ] = [dest for dest in location_ids if dest != origin ]
79
+ return locations
76
80
77
- # Execute the API call
78
- results = await sdk .time_filter_fast_async (
79
- locations = locations ,
80
- search_ids = search_ids ,
81
- transportation = Transportation (type = "driving" ),
82
- properties = [Property ("distance" ), Property ("travel_time" )],
83
- one_to_many = False ,
84
- )
85
81
82
+ def build_travel_matrices (results , location_ids ):
83
+ """
84
+ Build travel time and distance matrices from TravelTime API results.
85
+
86
+ Args:
87
+ results: TravelTime API response
88
+ location_ids: List of location IDs in matrix order
89
+
90
+ Returns:
91
+ Tuple of (duration_matrix, distance_matrix) as numpy arrays
92
+ """
86
93
# Initialize matrices with zeros or infinity where appropriate
87
94
n = len (location_ids )
88
95
duration_matrix = np .full ((n , n ), np .inf )
@@ -93,31 +100,81 @@ async def async_part(input_data: dict):
93
100
np .fill_diagonal (distance_matrix , 0 )
94
101
95
102
# Process results to fill in the matrix
96
- for result in results :
97
- origin_idx = location_ids .index (result .search_id )
103
+ for result in results .results :
104
+ origin_id = result .search_id
105
+ origin_idx = location_ids .index (origin_id )
106
+
107
+ for location_result in result .locations :
108
+ destination_idx = location_ids .index (location_result .id )
109
+ # Extract travel time and distance
110
+ props = location_result .properties
111
+ duration_matrix [origin_idx ][destination_idx ] = props .travel_time
112
+ distance_matrix [origin_idx ][destination_idx ] = props .distance
98
113
99
- for location in result .locations :
100
- destination_idx = location_ids .index (location .id )
101
- # Convert travel time to seconds if needed
102
- duration_matrix [origin_idx ][destination_idx ] = location .properties .travel_time
103
- distance_matrix [origin_idx ][destination_idx ] = location .properties .distance
114
+ return duration_matrix , distance_matrix
104
115
105
- # Convert numpy arrays to lists for JSON serialization
106
- duration_matrix_list = duration_matrix .tolist ()
107
- distance_matrix_list = distance_matrix .tolist ()
116
+
117
+ def sync_part (input_data : dict , client ):
118
+ """
119
+ Calculate travel matrices using TravelTime API client.
120
+
121
+ Args:
122
+ input_data: Nextroute input data dictionary
123
+ client: TravelTime API client instance
124
+
125
+ Returns:
126
+ Dictionary containing duration_matrix, distance_matrix, and location_ids
127
+ """
128
+ nextroute_input = nextrouteSchema .Input .from_dict (input_data )
129
+
130
+ # Build locations list from input data
131
+ locations = build_locations_list (nextroute_input )
132
+ location_ids = [loc .id for loc in locations ]
133
+
134
+ one_to_many_searches = []
135
+ for origin_id in location_ids :
136
+ # Get all destinations except the origin itself
137
+ destinations = location_ids .copy ()
138
+ destinations .remove (origin_id )
139
+
140
+ if destinations : # Only create search if there are destinations
141
+ one_to_many_searches .append (
142
+ TimeFilterFastOneToMany (
143
+ id = origin_id ,
144
+ departure_location_id = origin_id ,
145
+ arrival_location_ids = destinations ,
146
+ transportation = TransportationFast .DRIVING ,
147
+ travel_time = 7200 , # 2 hours maximum
148
+ properties = [Property .TRAVEL_TIME , Property .DISTANCE ]
149
+ )
150
+ )
151
+
152
+ # Execute the API call
153
+ results = client .time_filter_fast (
154
+ locations = locations ,
155
+ arrival_searches = TimeFilterFastArrivalSearches (
156
+ one_to_many = one_to_many_searches ,
157
+ many_to_one = []
158
+ )
159
+ )
160
+
161
+ # Build travel matrices from API results
162
+ duration_matrix , distance_matrix = build_travel_matrices (results , location_ids )
108
163
109
164
return {
110
- "duration_matrix" : duration_matrix_list ,
111
- "distance_matrix" : distance_matrix_list ,
165
+ "duration_matrix" : duration_matrix . tolist () ,
166
+ "distance_matrix" : distance_matrix . tolist () ,
112
167
"location_ids" : location_ids # Include location IDs for reference
113
168
}
114
169
170
+
115
171
# >>> Workflow definition
116
172
class Flow (FlowSpec ):
117
173
@step
118
174
def get_durations_distances (input : dict ) -> nextrouteSchema .Input :
119
175
"""Get distances and durations from TravelTime."""
120
- results = asyncio .run (async_part (input ))
176
+ with create_traveltime_client () as client :
177
+ results = sync_part (input , client )
121
178
nextroute_input = nextrouteSchema .Input .from_dict (input )
122
179
nextroute_input .duration_matrix = results ["duration_matrix" ]
123
180
nextroute_input .distance_matrix = results ["distance_matrix" ]
@@ -129,10 +186,10 @@ def get_durations_distances(input: dict) -> nextrouteSchema.Input:
129
186
# Check routes from vehicle start to first few stops
130
187
start_idx = location_ids .index ("vehicle-1-start" )
131
188
for i in range (3 ): # Check first 3 stops
132
- stop_idx = location_ids .index (f"location-{ i + 1 } " )
189
+ stop_idx = location_ids .index (f"location-{ i + 1 } " )
133
190
duration = results ["duration_matrix" ][start_idx ][stop_idx ]
134
191
distance = results ["distance_matrix" ][start_idx ][stop_idx ]
135
- nextmv .log (f"Vehicle start to location-{ i + 1 } : { duration } seconds, { distance } meters" )
192
+ nextmv .log (f"Vehicle start to location-{ i + 1 } : { duration } seconds, { distance } meters" )
136
193
137
194
return nextroute_input .to_dict ()
138
195
0 commit comments