mosstool.trip.gmns
Convertor to https://github.com/zephyr-data-specs/GMNS and Static traffic assignment
class
STA:
26class STA: 27 """ 28 run static traffic assignment on GMNS map with persons 29 """ 30 31 def __init__(self, map: Map, work_dir: str): 32 self._map_convertor = MapConvertor(map) 33 self._work_dir = work_dir 34 35 def _get_od(self, start: Position, end: Position): 36 """ 37 get origin and destination of a trip 38 39 Args: 40 - t: Trip, trip 41 42 Return: 43 - (origin, destination): (str, str), origin and destination 44 """ 45 46 oid, did = None, None 47 if start.HasField("aoi_position"): 48 oid = f"{start.aoi_position.aoi_id}-start" 49 elif start.HasField("lane_position"): 50 lane = self._map_convertor._lanes[start.lane_position.lane_id] 51 if lane.parent_id not in self._map_convertor._road2nodes: 52 raise ValueError(f"lane {lane.id} has invalid parent node") 53 # choose the successor node as oid 54 _, oid = self._map_convertor._road2nodes[lane.parent_id] 55 else: 56 raise ValueError("start position is invalid") 57 58 if end.HasField("aoi_position"): 59 did = f"{end.aoi_position.aoi_id}-end" 60 elif end.HasField("lane_position"): 61 lane = self._map_convertor._lanes[end.lane_position.lane_id] 62 if lane.parent_id not in self._map_convertor._road2nodes: 63 raise ValueError(f"lane {lane.id} has invalid parent node") 64 # choose the predecessor node as did 65 did, _ = self._map_convertor._road2nodes[lane.parent_id] 66 else: 67 raise ValueError("end position is invalid") 68 69 return oid, did 70 71 def _check_connection(self, start_road_id: int, end_road_id: int): 72 """ 73 check if two road segments are connected 74 75 Args: 76 - start_road_id: int, start road segment id 77 - end_road_id: int, end road segment id 78 79 Return: 80 - bool, True if two road segments are connected, False otherwise 81 """ 82 return (start_road_id, end_road_id) in self._map_convertor._connected_road_pairs 83 84 def run( 85 self, 86 persons: Persons, 87 time_interval: int = 60, 88 reset_routes: bool = False, 89 column_gen_num: int = 10, 90 column_update_num: int = 10, 91 ): 92 """ 93 run static traffic assignment on GMNS map with persons. 94 trips of persons will be sliced into time intervals. 95 static traffic assignment will be run for each time interval. 96 route results will be saved into persons. 97 98 EXPERIMENTAL: the method is only for trip with deterministic departure time. Other cases will be skipped. 99 100 Args: 101 - persons: Persons, persons with trips 102 - time_interval: int, time interval (minutes) for static traffic assignment slice. Try to set time_interval larger than trip's travel time. 103 - reset_routes: bool, reset routes of persons before running static traffic assignment 104 - column_gen_num: int, number of column generation iterations for static traffic assignment 105 - column_update_num: int, number of column update iterations for static traffic assignment 106 107 Return: 108 - Persons, persons with route results 109 - dict, statistics of static traffic assignment 110 """ 111 112 # step 1. convert map to GMNS map 113 self._map_convertor.save(self._work_dir) 114 115 # step 2. get all persons' driving trips with deterministic departure time 116 from_trips = [] # (pi, si, ti, departure_time, start, end) 117 for pi, p in enumerate(persons.persons): 118 departure_time = None 119 now = cast(Position, p.home) 120 for si, s in enumerate(p.schedules): 121 s = cast(Schedule, s) 122 if s.HasField("departure_time"): 123 departure_time = s.departure_time 124 for ti, t in enumerate(s.trips): 125 end = cast(Position, t.end) 126 if t.HasField("departure_time"): 127 departure_time = t.departure_time 128 if ( 129 departure_time is not None 130 and t.mode == TripMode.TRIP_MODE_DRIVE_ONLY 131 and (reset_routes or len(t.routes) == 0) 132 and s.loop_count == 1 133 ): 134 t.ClearField("routes") 135 from_trips.append((pi, si, ti, departure_time, now, end)) 136 departure_time = None 137 now = end 138 from_trips.sort(key=lambda x: x[3]) 139 140 from_trips_i = 0 141 success_cnt = 0 142 total_volumes = 0 143 valid_volumes = 0 144 disjointed_cnt = 0 145 while from_trips_i < len(from_trips): 146 # choose first slice 147 demands = defaultdict(int) # (origin, destination) -> count 148 start_t = from_trips[from_trips_i][3] 149 end_t = start_t + time_interval * 60 150 this_from_trips = [] 151 while from_trips_i < len(from_trips): 152 pi, si, ti, departure_time, start, end = from_trips[from_trips_i] 153 if departure_time >= end_t: 154 break 155 this_from_trips.append((pi, si, ti, departure_time, start, end)) 156 oid, did = self._get_od(start, end) 157 demands[(oid, did)] += 1 158 from_trips_i += 1 159 od_df = pd.DataFrame( 160 [ 161 { 162 "o_zone_id": o, 163 "d_zone_id": d, 164 "volume": volume, 165 } 166 for (o, d), volume in demands.items() 167 ] 168 ) 169 od_df.sort_values(["o_zone_id", "d_zone_id"], inplace=True) 170 # write to demand.csv 171 od_df.to_csv(os.path.join(self._work_dir, "demand.csv"), index=False) 172 173 # step 3: run traffic assignment 174 network = pg.read_network( 175 input_dir=self._work_dir, length_unit="km", speed_unit="kph" 176 ) 177 pg.load_demand(network, input_dir=self._work_dir, demand_period_str="AM") 178 pg.perform_column_generation(column_gen_num, column_update_num, network) 179 pg.output_columns(network, output_geometry=False, output_dir=self._work_dir) 180 181 # step 4: get route results 182 agent_df = pd.read_csv( 183 os.path.join(self._work_dir, "agent.csv"), index_col=None 184 ) 185 # (o_zone_id, d_zone_id) -> [{volume, node_sequence, link_sequence}] 186 agent_pairs: Dict[Tuple[str, str], List[Dict[str, Any]]] = defaultdict(list) 187 for _, row in agent_df.iterrows(): 188 volume = int(row["volume"]) 189 total_volumes += volume 190 link_sequence = cast(List[str], row["link_sequence"].split(";")) 191 # check path is valid and convert to road_ids for route .pb format 192 start_with_aoi = link_sequence[0].find("start") != -1 193 end_with_aoi = link_sequence[-1].find("end") != -1 194 road_ids = [] 195 if start_with_aoi: 196 # {aoiid}-start-{roadid}-{junctionid} 197 road_id = link_sequence[0].split("-")[2] 198 road_ids.append(int(road_id)) 199 link_sequence = link_sequence[1:] 200 if end_with_aoi: 201 # replace the last link with the road id 202 road_id = link_sequence[-1].split("-")[2] 203 link_sequence[-1] = road_id 204 road_ids.extend([int(link) for link in link_sequence]) 205 # check connection 206 bad = False 207 for i in range(1, len(road_ids)): 208 if not self._check_connection(road_ids[i - 1], road_ids[i]): 209 bad = True 210 break 211 if bad: 212 disjointed_cnt += volume 213 continue 214 valid_volumes += volume 215 agent_pairs[(row["o_zone_id"], row["d_zone_id"])].append( 216 { 217 "volume": volume, 218 "road_ids": road_ids, 219 "start_with_aoi": start_with_aoi, 220 "end_with_aoi": end_with_aoi, 221 } 222 ) 223 # assign route results to persons 224 for pi, si, ti, departure_time, start, end in this_from_trips: 225 oid, did = self._get_od(start, end) 226 if (oid, did) not in agent_pairs: 227 continue 228 t = cast(Trip, persons.persons[pi].schedules[si].trips[ti]) 229 result = agent_pairs[(oid, did)][0] 230 road_ids: List[int] = copy(result["road_ids"]) 231 if not result["start_with_aoi"]: 232 start_lane = self._map_convertor._lanes[start.lane_position.lane_id] 233 road_ids.insert(0, start_lane.parent_id) 234 # check connection 235 if not self._check_connection(start_lane.id, road_ids[1]): 236 disjointed_cnt += 1 237 continue 238 if not result["end_with_aoi"]: 239 end_lane = self._map_convertor._lanes[end.lane_position.lane_id] 240 road_ids.append(end_lane.parent_id) 241 # check connection 242 if not self._check_connection(road_ids[-2], end_lane.id): 243 disjointed_cnt += 1 244 continue 245 assert len(road_ids) >= 2 246 assert len(t.routes) == 0, f"routes should be empty, but got {t.routes}" 247 t.routes.append( 248 Journey( 249 type=JourneyType.JOURNEY_TYPE_DRIVING, 250 driving=DrivingJourneyBody(road_ids=road_ids), 251 ) 252 ) 253 success_cnt += 1 254 # update volume 255 result["volume"] -= 1 256 if result["volume"] <= 0: 257 agent_pairs[(oid, did)].pop(0) 258 if len(agent_pairs[(oid, did)]) == 0: 259 agent_pairs.pop((oid, did)) 260 261 return persons, { 262 "trip_cnt": len(from_trips), 263 "total_volumes": total_volumes, 264 "valid_volumes": valid_volumes, 265 "successful_cnt": success_cnt, 266 "disjointed_cnt": disjointed_cnt, 267 }
run static traffic assignment on GMNS map with persons
def
run( self, persons: city.person.v2.person_pb2.Persons, time_interval: int = 60, reset_routes: bool = False, column_gen_num: int = 10, column_update_num: int = 10):
84 def run( 85 self, 86 persons: Persons, 87 time_interval: int = 60, 88 reset_routes: bool = False, 89 column_gen_num: int = 10, 90 column_update_num: int = 10, 91 ): 92 """ 93 run static traffic assignment on GMNS map with persons. 94 trips of persons will be sliced into time intervals. 95 static traffic assignment will be run for each time interval. 96 route results will be saved into persons. 97 98 EXPERIMENTAL: the method is only for trip with deterministic departure time. Other cases will be skipped. 99 100 Args: 101 - persons: Persons, persons with trips 102 - time_interval: int, time interval (minutes) for static traffic assignment slice. Try to set time_interval larger than trip's travel time. 103 - reset_routes: bool, reset routes of persons before running static traffic assignment 104 - column_gen_num: int, number of column generation iterations for static traffic assignment 105 - column_update_num: int, number of column update iterations for static traffic assignment 106 107 Return: 108 - Persons, persons with route results 109 - dict, statistics of static traffic assignment 110 """ 111 112 # step 1. convert map to GMNS map 113 self._map_convertor.save(self._work_dir) 114 115 # step 2. get all persons' driving trips with deterministic departure time 116 from_trips = [] # (pi, si, ti, departure_time, start, end) 117 for pi, p in enumerate(persons.persons): 118 departure_time = None 119 now = cast(Position, p.home) 120 for si, s in enumerate(p.schedules): 121 s = cast(Schedule, s) 122 if s.HasField("departure_time"): 123 departure_time = s.departure_time 124 for ti, t in enumerate(s.trips): 125 end = cast(Position, t.end) 126 if t.HasField("departure_time"): 127 departure_time = t.departure_time 128 if ( 129 departure_time is not None 130 and t.mode == TripMode.TRIP_MODE_DRIVE_ONLY 131 and (reset_routes or len(t.routes) == 0) 132 and s.loop_count == 1 133 ): 134 t.ClearField("routes") 135 from_trips.append((pi, si, ti, departure_time, now, end)) 136 departure_time = None 137 now = end 138 from_trips.sort(key=lambda x: x[3]) 139 140 from_trips_i = 0 141 success_cnt = 0 142 total_volumes = 0 143 valid_volumes = 0 144 disjointed_cnt = 0 145 while from_trips_i < len(from_trips): 146 # choose first slice 147 demands = defaultdict(int) # (origin, destination) -> count 148 start_t = from_trips[from_trips_i][3] 149 end_t = start_t + time_interval * 60 150 this_from_trips = [] 151 while from_trips_i < len(from_trips): 152 pi, si, ti, departure_time, start, end = from_trips[from_trips_i] 153 if departure_time >= end_t: 154 break 155 this_from_trips.append((pi, si, ti, departure_time, start, end)) 156 oid, did = self._get_od(start, end) 157 demands[(oid, did)] += 1 158 from_trips_i += 1 159 od_df = pd.DataFrame( 160 [ 161 { 162 "o_zone_id": o, 163 "d_zone_id": d, 164 "volume": volume, 165 } 166 for (o, d), volume in demands.items() 167 ] 168 ) 169 od_df.sort_values(["o_zone_id", "d_zone_id"], inplace=True) 170 # write to demand.csv 171 od_df.to_csv(os.path.join(self._work_dir, "demand.csv"), index=False) 172 173 # step 3: run traffic assignment 174 network = pg.read_network( 175 input_dir=self._work_dir, length_unit="km", speed_unit="kph" 176 ) 177 pg.load_demand(network, input_dir=self._work_dir, demand_period_str="AM") 178 pg.perform_column_generation(column_gen_num, column_update_num, network) 179 pg.output_columns(network, output_geometry=False, output_dir=self._work_dir) 180 181 # step 4: get route results 182 agent_df = pd.read_csv( 183 os.path.join(self._work_dir, "agent.csv"), index_col=None 184 ) 185 # (o_zone_id, d_zone_id) -> [{volume, node_sequence, link_sequence}] 186 agent_pairs: Dict[Tuple[str, str], List[Dict[str, Any]]] = defaultdict(list) 187 for _, row in agent_df.iterrows(): 188 volume = int(row["volume"]) 189 total_volumes += volume 190 link_sequence = cast(List[str], row["link_sequence"].split(";")) 191 # check path is valid and convert to road_ids for route .pb format 192 start_with_aoi = link_sequence[0].find("start") != -1 193 end_with_aoi = link_sequence[-1].find("end") != -1 194 road_ids = [] 195 if start_with_aoi: 196 # {aoiid}-start-{roadid}-{junctionid} 197 road_id = link_sequence[0].split("-")[2] 198 road_ids.append(int(road_id)) 199 link_sequence = link_sequence[1:] 200 if end_with_aoi: 201 # replace the last link with the road id 202 road_id = link_sequence[-1].split("-")[2] 203 link_sequence[-1] = road_id 204 road_ids.extend([int(link) for link in link_sequence]) 205 # check connection 206 bad = False 207 for i in range(1, len(road_ids)): 208 if not self._check_connection(road_ids[i - 1], road_ids[i]): 209 bad = True 210 break 211 if bad: 212 disjointed_cnt += volume 213 continue 214 valid_volumes += volume 215 agent_pairs[(row["o_zone_id"], row["d_zone_id"])].append( 216 { 217 "volume": volume, 218 "road_ids": road_ids, 219 "start_with_aoi": start_with_aoi, 220 "end_with_aoi": end_with_aoi, 221 } 222 ) 223 # assign route results to persons 224 for pi, si, ti, departure_time, start, end in this_from_trips: 225 oid, did = self._get_od(start, end) 226 if (oid, did) not in agent_pairs: 227 continue 228 t = cast(Trip, persons.persons[pi].schedules[si].trips[ti]) 229 result = agent_pairs[(oid, did)][0] 230 road_ids: List[int] = copy(result["road_ids"]) 231 if not result["start_with_aoi"]: 232 start_lane = self._map_convertor._lanes[start.lane_position.lane_id] 233 road_ids.insert(0, start_lane.parent_id) 234 # check connection 235 if not self._check_connection(start_lane.id, road_ids[1]): 236 disjointed_cnt += 1 237 continue 238 if not result["end_with_aoi"]: 239 end_lane = self._map_convertor._lanes[end.lane_position.lane_id] 240 road_ids.append(end_lane.parent_id) 241 # check connection 242 if not self._check_connection(road_ids[-2], end_lane.id): 243 disjointed_cnt += 1 244 continue 245 assert len(road_ids) >= 2 246 assert len(t.routes) == 0, f"routes should be empty, but got {t.routes}" 247 t.routes.append( 248 Journey( 249 type=JourneyType.JOURNEY_TYPE_DRIVING, 250 driving=DrivingJourneyBody(road_ids=road_ids), 251 ) 252 ) 253 success_cnt += 1 254 # update volume 255 result["volume"] -= 1 256 if result["volume"] <= 0: 257 agent_pairs[(oid, did)].pop(0) 258 if len(agent_pairs[(oid, did)]) == 0: 259 agent_pairs.pop((oid, did)) 260 261 return persons, { 262 "trip_cnt": len(from_trips), 263 "total_volumes": total_volumes, 264 "valid_volumes": valid_volumes, 265 "successful_cnt": success_cnt, 266 "disjointed_cnt": disjointed_cnt, 267 }
run static traffic assignment on GMNS map with persons. trips of persons will be sliced into time intervals. static traffic assignment will be run for each time interval. route results will be saved into persons.
EXPERIMENTAL: the method is only for trip with deterministic departure time. Other cases will be skipped.
Args:
- persons: Persons, persons with trips
- time_interval: int, time interval (minutes) for static traffic assignment slice. Try to set time_interval larger than trip's travel time.
- reset_routes: bool, reset routes of persons before running static traffic assignment
- column_gen_num: int, number of column generation iterations for static traffic assignment
- column_update_num: int, number of column update iterations for static traffic assignment
Return:
- Persons, persons with route results
- dict, statistics of static traffic assignment