mosstool.trip.sumo

SUMO Route Converting Tools

1"""
2SUMO Route Converting Tools
3"""
4from .route import RouteConverter
5
6__all__ = ["RouteConverter"]
class RouteConverter:
  18class RouteConverter:
  19    def __init__(
  20        self,
  21        converted_map: Map,
  22        sumo_id_mappings: dict,
  23        route_path: str,
  24        additional_path: Optional[str] = None,
  25        seed: Optional[int] = 0,
  26    ):
  27        """
  28        Args:
  29        - converted_map: The converted map from SUMO network.
  30        - sumo_id_mappings: The mapping from SUMO id to the unique id in the converted map.
  31        - route_path: The path to the SUMO route file.
  32        - additional_path: The path to the additional file containing bus stops, charging stations, and parking areas.
  33        - seed: The random seed.
  34        """
  35        if seed is not None:
  36            random.seed(seed)
  37        self._additional_path = additional_path  # additional file with "busStop","chargingStation","parkingArea"
  38        self._id2uid = sumo_id_mappings
  39        m = converted_map
  40        logging.info("Reading converted map")
  41        self._lanes = {}
  42        self._juncs = {}
  43        self._roads = {}
  44        for l in m.lanes:
  45            lid = l.id
  46            pres = l.predecessors
  47            sucs = l.successors
  48            self._lanes[lid] = {
  49                "type": l.type,
  50                "geo": LineString([[p.x, p.y] for p in l.center_line.nodes]),
  51                "in_lids": [p.id for p in pres],
  52                "out_lids": [s.id for s in sucs],
  53                "parent_id": l.parent_id,
  54                "length": l.length,
  55            }
  56        for j in m.junctions:
  57            jid = j.id
  58            self._juncs[jid] = {
  59                "lane_ids": j.lane_ids,
  60            }
  61        for r in m.roads:
  62            rid = r.id
  63            self._roads[rid] = {
  64                "lane_ids": r.lane_ids,
  65                "length": self._lanes[r.lane_ids[len(r.lane_ids) // 2]]["length"],
  66                "driving_lane_ids": [
  67                    lid
  68                    for lid in r.lane_ids
  69                    if self._lanes[lid]["type"] == mapv2.LANE_TYPE_DRIVING
  70                ],
  71                "walking_lane_ids": [
  72                    lid
  73                    for lid in r.lane_ids
  74                    if self._lanes[lid]["type"] == mapv2.LANE_TYPE_WALKING
  75                ],
  76            }
  77        logging.info(f"Reading route from {route_path}")
  78        dom_tree = parse(route_path)
  79        # get the root node
  80        root_node = dom_tree.documentElement
  81        # read SUMO .route.xml
  82        self._vtype = (
  83            {}
  84        )  # vehicle_id -> (agent_attribute,vehicle_attribute,pedestrian_attribute,bike_attribute,agent_type,)
  85        self._routes = root_node.getElementsByTagName("route")
  86        self._trips = root_node.getElementsByTagName("trip")
  87        self._flows = root_node.getElementsByTagName("flow")
  88        self._intervals = root_node.getElementsByTagName(
  89            "interval"
  90        )  # interval contains multiple `flows`
  91        self._vehicles = root_node.getElementsByTagName("vehicle")
  92        # output data
  93        self._output_agents = []
  94        for v in root_node.getElementsByTagName("vType"):
  95            vid = v.getAttribute("id")
  96            max_acc = (
  97                np.float64(v.getAttribute("accel")) if v.hasAttribute("accel") else 3.0
  98            )
  99            max_dec = (
 100                -np.float64(v.getAttribute("decel"))
 101                if v.hasAttribute("decel")
 102                else -4.5
 103            )
 104            length = (
 105                np.float64(v.getAttribute("length"))
 106                if v.hasAttribute("length")
 107                else 5.0
 108            )
 109            max_speed = (
 110                np.float64(v.getAttribute("maxSpeed"))
 111                if v.hasAttribute("maxSpeed")
 112                else 41.6666666667
 113            )
 114            width = (
 115                np.float64(v.getAttribute("width")) if v.hasAttribute("width") else 2.0
 116            )
 117            min_gap = (
 118                np.float64(v.getAttribute("minGap"))
 119                if v.hasAttribute("minGap")
 120                else 1.0
 121            )
 122            v_class = v.getAttribute("vClass") if v.hasAttribute("vClass") else ""
 123            if v_class == "pedestrian":
 124                agent_type = "AGENT_TYPE_PERSON"
 125            elif v_class == "bus":
 126                agent_type = "AGENT_TYPE_BUS"
 127            elif v_class == "bicycle":
 128                agent_type = "AGENT_TYPE_BIKE"
 129            else:
 130                agent_type = "AGENT_TYPE_PRIVATE_CAR"
 131
 132            usual_acc = 2.0
 133            usual_dec = -4.5
 134            LC_length = 2 * length
 135            # TODO: add other car-following-model
 136            # https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#car-following_models
 137            # model_name = v.getAttribute("carFollowModel") if v.hasAttribute("carFollowModel") else "IDM"
 138            self._vtype[vid] = (
 139                {},
 140                {
 141                    "length": length,
 142                    "width": width,
 143                    "max_speed": max_speed,
 144                    "max_acceleration": max_acc,
 145                    "max_braking_acceleration": max_dec,
 146                    "usual_acceleration": usual_acc,
 147                    "usual_braking_acceleration": usual_dec,
 148                    "lane_change_length": LC_length,
 149                    "min_gap": min_gap,
 150                },
 151                {
 152                    "speed": 1.34,
 153                },
 154                {
 155                    "speed": 5.0,
 156                },
 157                agent_type,
 158            )
 159        self._additional_stops = {}
 160        if self._additional_path:
 161            add_dom_tree = parse(self._additional_path)
 162            add_root_node = add_dom_tree.documentElement
 163            for stop_type in ["busStop", "chargingStation", "parkingArea"]:
 164                for stop in add_root_node.getElementsByTagName(stop_type):
 165                    stop_id = stop.getAttribute("id")
 166                    stop_name = (
 167                        stop.getAttribute("name") if stop.hasAttribute("name") else ""
 168                    )
 169                    if stop.getAttribute("lane") in self._id2uid.keys():
 170                        stop_lid = self._id2uid[stop.getAttribute("lane")]
 171                        stop_lane = self._lanes[stop_lid]
 172                        start_pos = (
 173                            np.float64(stop.getAttribute("startPos"))
 174                            if stop.hasAttribute("startPos")
 175                            else 0.1 * stop_lane["length"]
 176                        )
 177                        if start_pos < 0:
 178                            start_pos += stop_lane["length"]
 179                        end_pos = (
 180                            np.float64(stop.getAttribute("endPos"))
 181                            if stop.hasAttribute("endPos")
 182                            else 0.9 * stop_lane["length"]
 183                        )
 184                        if end_pos < 0:
 185                            end_pos += stop_lane["length"]
 186                        stop_s = (end_pos + start_pos) / 2
 187                        stop_s = np.clip(
 188                            stop_s, 0.1 * stop_lane["length"], 0.9 * stop_lane["length"]
 189                        )
 190                        veh_stop = {
 191                            "lane_position": {
 192                                "lane_id": stop_lid,
 193                                "s": stop_s,
 194                            }
 195                        }
 196                        parent_rid = stop_lane["parent_id"]
 197                        self._additional_stops[stop_id] = (veh_stop, parent_rid)
 198                    else:
 199                        logging.warning(f"Invalid stop {stop_id} {stop_name}!")
 200
 201    def _convert_time(self, time_str: str) -> np.float64:
 202        if ":" not in time_str:
 203            return np.float64(time_str)
 204        converted_time = np.float64(0)
 205        times = time_str.split(":")
 206        t_factor = 1.0
 207        for t in times[::-1][:3]:
 208            converted_time += t_factor * np.float64(t)
 209            t_factor *= 60
 210        return converted_time
 211
 212    def _convert_route_trips(
 213        self, edges: list, repeat: int, cycle_time: np.float64, rid2stop: dict
 214    ):
 215        route_trips = []  # Road ids separated by stop
 216        last_stop_rid = None  # Ensure road ids contain the start and end points
 217        for n_repeat in range(repeat + 1):
 218            road_ids = []
 219            for eid in edges:
 220                if not eid in self._id2uid.keys():  # Indicates junction lane
 221                    continue
 222                else:
 223                    if last_stop_rid:
 224                        road_ids.append(last_stop_rid)
 225                        last_stop_rid = None
 226                    rid = self._id2uid[eid]
 227                    road_ids.append(rid)
 228                    if rid in rid2stop.keys():
 229                        stop = rid2stop[rid]
 230                        if stop["until"]:
 231                            stop["until"] += n_repeat * cycle_time
 232                        route_trips.append(
 233                            {
 234                                "road_ids": road_ids,
 235                                "stop": stop,
 236                            }
 237                        )
 238                        last_stop_rid = rid
 239                        road_ids = []
 240            if road_ids:
 241                route_trips.append(
 242                    {
 243                        "road_ids": road_ids,
 244                        "stop": None,
 245                    }
 246                )
 247        return route_trips
 248
 249    def _process_route_trips(
 250        self,
 251        t: minidom.Element,
 252        route_trips: list,
 253        trip_id: int,
 254        pre_veh_end: dict,
 255        TRIP_MODE: int,
 256        ROAD_LANE_TYPE: Union[Literal["walking_lane_ids"], Literal["driving_lane_ids"]],
 257        SPEED: float,
 258        departure: np.float64,
 259        trip_type: Union[Literal["trip"], Literal["flow"], Literal["vehicle"]] = "flow",
 260    ):
 261        schedules = []
 262        for i, route_trip in enumerate(route_trips):
 263            road_ids = route_trip["road_ids"]
 264            stop = route_trip["stop"]
 265            from_rid = road_ids[0]
 266            from_road = self._roads[from_rid]
 267            to_rid = road_ids[-1]
 268            to_road = self._roads[to_rid]
 269            if not stop:
 270                veh_end = self._get_trip_position(
 271                    t,
 272                    trip_id,
 273                    to_road,
 274                    to_rid,
 275                    ROAD_LANE_TYPE,
 276                    trip_type=trip_type,
 277                    attribute="arrivalLane",
 278                )
 279            else:
 280                veh_end = stop["veh_end"]
 281            if TRIP_MODE in {
 282                tripv2.TRIP_MODE_WALK_ONLY,
 283                tripv2.TRIP_MODE_BIKE_WALK,
 284                tripv2.TRIP_MODE_BUS_WALK,
 285            }:
 286                pre_lid = pre_veh_end["lane_position"]["lane_id"]
 287                pre_geo = self._lanes[pre_lid]["geo"]
 288                cur_lid = veh_end["lane_position"]["lane_id"]
 289                cur_geo = self._lanes[cur_lid]["geo"]
 290                estimate_distance = np.sqrt(2) * cur_geo.distance(pre_geo)
 291                eta = max(estimate_distance / SPEED, 5)
 292                schedules.append(
 293                    {
 294                        "trips": [
 295                            {
 296                                "mode": TRIP_MODE,
 297                                "end": veh_end,
 298                                "activity": "other",
 299                            }
 300                        ],
 301                        "departure_time": departure,
 302                        "loop_count": 1,
 303                    }
 304                )
 305                pre_veh_end = veh_end
 306            else:
 307                route_len = 0
 308                for rid in road_ids:
 309                    route_len += self._roads[rid]["length"]
 310                eta = max(route_len / SPEED, 5)
 311                journey = {
 312                    "type": routingv2.JOURNEY_TYPE_DRIVING,
 313                    "driving": {
 314                        "road_ids": road_ids,
 315                        "eta": eta,
 316                    },
 317                }
 318                schedules.append(
 319                    {
 320                        "trips": [
 321                            {
 322                                "mode": TRIP_MODE,
 323                                "end": veh_end,
 324                                "activity": "other",
 325                                "routes": [journey],
 326                            }
 327                        ],
 328                        "departure_time": departure,
 329                        "loop_count": 1,
 330                    }
 331                )
 332                pre_veh_end = veh_end
 333            if stop:
 334                duration = stop["duration"]
 335                until = stop["until"]
 336                if duration:
 337                    departure += duration
 338                elif until:
 339                    departure = max(departure + eta, until)
 340            else:
 341                departure += eta
 342        return schedules
 343
 344    def _convert_trips_with_route(
 345        self,
 346        t: minidom.Element,
 347        departure_times: list[np.float64],
 348        TRIP_MODE: int,
 349        ROAD_LANE_TYPE: Union[Literal["walking_lane_ids"], Literal["driving_lane_ids"]],
 350        SPEED: float,
 351        trip_id: int,
 352        trip_type: Union[Literal["trip"], Literal["flow"], Literal["vehicle"]] = "flow",
 353    ):
 354        if t.hasAttribute("route"):
 355            route_id = t.getAttribute("route")
 356            troute = self.route_dict[route_id]
 357        else:
 358            troute = t.getElementsByTagName("route")[0]
 359        for departure in departure_times:
 360            edges = troute.getAttribute("edges").split(" ")
 361            repeat = (
 362                int(troute.getAttribute("repeat"))
 363                if troute.hasAttribute("repeat")
 364                else 0
 365            )
 366            vstops = troute.getElementsByTagName("stop")
 367            rid2stop = self._convert_stops(
 368                all_stops=list(t.getElementsByTagName("stop")) + list(vstops),
 369                trip_id=trip_id,
 370                trip_type=trip_type,
 371            )
 372            cycle_time = (
 373                self._convert_time(troute.getAttribute("cycleTime"))
 374                if troute.hasAttribute("cycleTime")
 375                else np.float64(0)
 376            )
 377            route_trips = self._convert_route_trips(edges, repeat, cycle_time, rid2stop)
 378
 379            if not route_trips:
 380                logging.warning(f"Bad route at {trip_type} {trip_id}")
 381                continue
 382            # processing route_trips
 383            self._route_trips_to_person(
 384                route_trips,
 385                t,
 386                trip_id,
 387                ROAD_LANE_TYPE,
 388                trip_type,
 389                TRIP_MODE,
 390                SPEED,
 391                departure,
 392            )
 393
 394    def _convert_flows_with_from_to(
 395        self,
 396        f: minidom.Element,
 397        departure_times: list[np.float64],
 398        flow_id: int,
 399        ROAD_LANE_TYPE: Union[Literal["walking_lane_ids"], Literal["driving_lane_ids"]],
 400        TRIP_MODE: int,
 401    ):
 402        from_eid = f.getAttribute("from")
 403        from_rid = self._id2uid[from_eid]
 404        from_road = self._roads[from_rid]
 405        to_eid = f.getAttribute("to")
 406        to_rid = self._id2uid[to_eid]
 407        to_road = self._roads[to_rid]
 408        for departure in departure_times:
 409            flow_home = self._get_trip_position(
 410                f,
 411                flow_id,
 412                from_road,
 413                from_rid,
 414                ROAD_LANE_TYPE,
 415                trip_type="flow",
 416                attribute="departLane",
 417            )
 418            if not flow_home:
 419                break
 420            flow_end = self._get_trip_position(
 421                f,
 422                flow_id,
 423                to_road,
 424                to_rid,
 425                ROAD_LANE_TYPE,
 426                trip_type="flow",
 427                attribute="arrivalLane",
 428            )
 429            if not flow_end:
 430                break
 431            schedules = [
 432                {
 433                    "trips": [
 434                        {
 435                            "mode": TRIP_MODE,
 436                            "end": flow_end,
 437                            "activity": "other",
 438                        }
 439                    ],
 440                    "departure_time": departure,
 441                    "loop_count": 1,
 442                }
 443            ]
 444            self._output_agents.append(
 445                {
 446                    "id": self.agent_uid,
 447                    "home": flow_home,
 448                    "attribute": self.agent_attribute,
 449                    "vehicle_attribute": self.vehicle_attribute,
 450                    "pedestrian_attribute": self.pedestrian_attribute,
 451                    "bike_attribute": self.bike_attribute,
 452                    "schedules": schedules,
 453                }
 454            )
 455            self.agent_uid += 1
 456
 457    def _convert_stops(
 458        self,
 459        all_stops: list,
 460        trip_id: int,
 461        trip_type: Union[Literal["trip"], Literal["flow"], Literal["vehicle"]] = "flow",
 462    ):
 463        rid2stop = {}
 464        for s in all_stops:
 465            if any(
 466                [
 467                    s.hasAttribute(stop_type)
 468                    for stop_type in [
 469                        "containerStop",
 470                    ]
 471                ]
 472            ):
 473                logging.warning(f"Unsupported stop type at {trip_type} {trip_id}")
 474                continue
 475            if s.hasAttribute("busStop"):
 476                stop_id = s.getAttribute("busStop")
 477                if stop_id in self._additional_stops.keys():
 478                    (veh_stop, parent_rid) = self._additional_stops[stop_id]
 479                else:
 480                    logging.warning(f"Invalid busStop {stop_id}")
 481                    continue
 482            elif s.hasAttribute("chargingStation"):
 483                stop_id = s.getAttribute("chargingStation")
 484                if stop_id in self._additional_stops.keys():
 485                    (veh_stop, parent_rid) = self._additional_stops[stop_id]
 486                else:
 487                    logging.warning(f"Invalid chargingStation {stop_id}")
 488                    continue
 489            elif s.hasAttribute("parkingArea"):
 490                stop_id = s.getAttribute("parkingArea")
 491                if stop_id in self._additional_stops.keys():
 492                    (veh_stop, parent_rid) = self._additional_stops[stop_id]
 493                else:
 494                    logging.warning(f"Invalid parkingArea {stop_id}")
 495                    continue
 496            elif s.getAttribute("lane") in self._id2uid.keys():
 497                stop_lid = self._id2uid[s.getAttribute("lane")]
 498                stop_lane = self._lanes[stop_lid]
 499                start_pos = (
 500                    np.float64(s.getAttribute("startPos"))
 501                    if s.hasAttribute("startPos")
 502                    else 0.1 * stop_lane["length"]
 503                )
 504                if start_pos < 0:
 505                    start_pos += stop_lane["length"]
 506                end_pos = (
 507                    np.float64(s.getAttribute("endPos"))
 508                    if s.hasAttribute("endPos")
 509                    else 0.9 * stop_lane["length"]
 510                )
 511                if end_pos < 0:
 512                    end_pos += stop_lane["length"]
 513                stop_s = (end_pos + start_pos) / 2
 514                stop_s = np.clip(
 515                    stop_s, 0.1 * stop_lane["length"], 0.9 * stop_lane["length"]
 516                )
 517                veh_stop = {
 518                    "lane_position": {
 519                        "lane_id": stop_lid,
 520                        "s": stop_s,
 521                    }
 522                }
 523                parent_rid = stop_lane["parent_id"]
 524            else:
 525                logging.warning(f"Unsupported stop type at {trip_type} {trip_id}")
 526                continue
 527            duration = (
 528                self._convert_time(s.getAttribute("duration"))
 529                if s.hasAttribute("duration")
 530                else None
 531            )
 532            until = (
 533                self._convert_time(s.getAttribute("until"))
 534                if s.hasAttribute("until")
 535                else None
 536            )
 537            if not duration and not until:
 538                continue
 539            else:
 540                rid2stop[parent_rid] = {
 541                    "veh_end": veh_stop,
 542                    "duration": duration,
 543                    "until": until,
 544                }
 545        return rid2stop
 546
 547    def _get_trip_position(
 548        self,
 549        t: minidom.Element,
 550        trip_id: int,
 551        road: dict,
 552        road_id: int,
 553        ROAD_LANE_TYPE: Union[Literal["walking_lane_ids"], Literal["driving_lane_ids"]],
 554        trip_type: Union[Literal["trip"], Literal["flow"], Literal["vehicle"]],
 555        attribute: Union[Literal["departLane"], Literal["arrivalLane"]],
 556    ):
 557        lid = None
 558        res_pos = {}
 559        if t.hasAttribute(attribute):
 560            attribute_id = t.getAttribute(attribute)
 561            if attribute_id in self._id2uid.keys():
 562                lid = self._id2uid[attribute_id]
 563        if not lid or lid not in road[ROAD_LANE_TYPE]:
 564            lid = random.choice(road[ROAD_LANE_TYPE])
 565            if not road[ROAD_LANE_TYPE]:
 566                logging.warning(
 567                    f"Wrong Lane Type {ROAD_LANE_TYPE} at {road_id} at {trip_type} {trip_id}"
 568                )
 569                lid = None
 570        s_proj = random.uniform(0.1, 0.9) * self._lanes[lid]["length"]
 571        if lid is not None:
 572            res_pos = {
 573                "lane_position": {
 574                    "lane_id": lid,
 575                    "s": s_proj,
 576                }
 577            }
 578        return res_pos
 579
 580    def _process_agent_type(self):
 581        agent_type = self.agent_type
 582        if agent_type == "AGENT_TYPE_PERSON":
 583            TRIP_MODE = tripv2.TRIP_MODE_WALK_ONLY
 584            SPEED = random.uniform(0.3, 0.8) * 2
 585            ROAD_LANE_TYPE = "walking_lane_ids"
 586        elif agent_type == "AGENT_TYPE_BIKE":
 587            TRIP_MODE = tripv2.TRIP_MODE_BIKE_WALK
 588            SPEED = random.uniform(3, 6)
 589            ROAD_LANE_TYPE = "walking_lane_ids"
 590        elif agent_type == "AGENT_TYPE_PRIVATE_CAR":
 591            TRIP_MODE = tripv2.TRIP_MODE_DRIVE_ONLY
 592            SPEED = random.uniform(0.3, 0.8) * 50 / 3.6
 593            ROAD_LANE_TYPE = "driving_lane_ids"
 594        else:
 595            TRIP_MODE = tripv2.TRIP_MODE_DRIVE_ONLY
 596            SPEED = random.uniform(0.3, 0.8) * 50 / 3.6
 597            ROAD_LANE_TYPE = "driving_lane_ids"
 598        return (TRIP_MODE, SPEED, ROAD_LANE_TYPE)
 599
 600    def _route_trips_to_person(
 601        self,
 602        route_trips: list,
 603        t: minidom.Element,
 604        trip_id: int,
 605        ROAD_LANE_TYPE: Union[Literal["walking_lane_ids"], Literal["driving_lane_ids"]],
 606        trip_type: Union[Literal["trip"], Literal["flow"], Literal["vehicle"]],
 607        TRIP_MODE: int,
 608        SPEED: float,
 609        departure: np.float64,
 610    ):
 611        home_rid = route_trips[0]["road_ids"][0]
 612        for i in range(len(route_trips) - 1):
 613            cur_route = route_trips[i]
 614            next_route = route_trips[i + 1]
 615            if not cur_route["road_ids"][-1] == next_route["road_ids"][0]:
 616                assert (
 617                    home_rid == next_route["road_ids"][0]
 618                )  # only process when `repeat` is valid
 619                cur_route["road_ids"].append(home_rid)
 620
 621        home_road = self._roads[home_rid]
 622        veh_home = self._get_trip_position(
 623            t,
 624            trip_id,
 625            home_road,
 626            home_rid,
 627            ROAD_LANE_TYPE,
 628            trip_type,
 629            attribute="departLane",
 630        )
 631        if not veh_home:
 632            return
 633        pre_veh_end = veh_home
 634
 635        schedules = self._process_route_trips(
 636            t,
 637            route_trips,
 638            trip_id,
 639            pre_veh_end,
 640            TRIP_MODE,
 641            ROAD_LANE_TYPE,
 642            SPEED,
 643            departure,
 644            trip_type,
 645        )
 646        self._output_agents.append(
 647            {
 648                "id": self.agent_uid,
 649                "home": veh_home,
 650                "attribute": self.agent_attribute,
 651                "vehicle_attribute": self.vehicle_attribute,
 652                "pedestrian_attribute": self.pedestrian_attribute,
 653                "bike_attribute": self.bike_attribute,
 654                "schedules": schedules,
 655            }
 656        )
 657        self.agent_uid += 1
 658
 659    def convert_route(self):
 660        self.agent_uid = 0
 661        DEFAULT_AGENT_ATTRIBUTE = {}
 662        DEFAULT_VEHICLE_ATTRIBUTE = {
 663            "length": 5,
 664            "width": 2,
 665            "max_speed": 41.6666666667,
 666            "max_acceleration": 3,
 667            "max_braking_acceleration": -10,
 668            "usual_acceleration": 2,
 669            "usual_braking_acceleration": -4.5,
 670            "lane_change_length": 10,
 671            "min_gap": 1,
 672        }
 673        DEFAULT_PEDESTRIAN_ATTRIBUTE = {"speed": 1.34}
 674        DEFAULT_BIKE_ATTRIBUTE = {"speed": 5}
 675        DEFAULT_AGENT_TYPE = "AGENT_TYPE_PRIVATE_CAR"
 676
 677        # Route contains the edges that all vehicles pass through, that is, the complete trajectory
 678        # Route can be defined separately from vehicle or under vehicle, so additional judgment is required.
 679        self.route_dict = {}
 680        for r in self._routes:
 681            route_id = r.getAttribute("id")
 682            self.route_dict[route_id] = r
 683        if self._trips:
 684            logging.info("Converting trips")
 685        for t in self._trips:
 686            if t.hasAttribute("type"):
 687                trip_type = t.getAttribute("type")
 688                (
 689                    self.agent_attribute,
 690                    self.vehicle_attribute,
 691                    self.pedestrian_attribute,
 692                    self.bike_attribute,
 693                    self.agent_type,
 694                ) = self._vtype[trip_type]
 695            else:
 696                (
 697                    self.agent_attribute,
 698                    self.vehicle_attribute,
 699                    self.pedestrian_attribute,
 700                    self.bike_attribute,
 701                    self.agent_type,
 702                ) = (
 703                    DEFAULT_AGENT_ATTRIBUTE,
 704                    DEFAULT_VEHICLE_ATTRIBUTE,
 705                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 706                    DEFAULT_BIKE_ATTRIBUTE,
 707                    DEFAULT_AGENT_TYPE,
 708                )
 709            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 710            trip_id = t.getAttribute("id")
 711            departure = self._convert_time(t.getAttribute("depart"))
 712            if not t.hasAttribute("from") or not t.hasAttribute("to"):
 713                # no from and to means this item has route
 714                if t.hasAttribute("route"):
 715                    route_id = t.getAttribute("route")
 716                    troute = self.route_dict[route_id]
 717                else:
 718                    troute = t.getElementsByTagName("route")[0]
 719                edges = troute.getAttribute("edges").split(" ")
 720                repeat = (
 721                    int(troute.getAttribute("repeat"))
 722                    if troute.hasAttribute("repeat")
 723                    else 0
 724                )
 725                vstops = troute.getElementsByTagName("stop")
 726                rid2stop = self._convert_stops(
 727                    all_stops=list(t.getElementsByTagName("stop")) + list(vstops),
 728                    trip_id=trip_id,
 729                    trip_type="trip",
 730                )
 731
 732                cycle_time = (
 733                    self._convert_time(troute.getAttribute("cycleTime"))
 734                    if troute.hasAttribute("cycleTime")
 735                    else np.float64(0)
 736                )
 737                route_trips = self._convert_route_trips(
 738                    edges, repeat, cycle_time, rid2stop
 739                )
 740
 741                if not route_trips:
 742                    logging.warning(f"Bad route at trip {trip_id}")
 743                    continue
 744                # process route_trips
 745                self._route_trips_to_person(
 746                    route_trips,
 747                    t,
 748                    trip_id,
 749                    ROAD_LANE_TYPE,
 750                    "trip",
 751                    TRIP_MODE,
 752                    SPEED,
 753                    departure,
 754                )
 755            else:
 756                from_eid = t.getAttribute("from")
 757                via_eids = (
 758                    t.getAttribute("via").split(" ") if t.hasAttribute("via") else []
 759                )
 760                to_eid = t.getAttribute("to")
 761                from_rid = self._id2uid[from_eid]
 762                from_road = self._roads[from_rid]
 763                to_rid = self._id2uid[to_eid]
 764                to_road = self._roads[to_rid]
 765                trip_home = self._get_trip_position(
 766                    t,
 767                    trip_id,
 768                    from_road,
 769                    from_rid,
 770                    ROAD_LANE_TYPE,
 771                    trip_type="trip",
 772                    attribute="departLane",
 773                )
 774                if not trip_home:
 775                    continue
 776                trip_end = self._get_trip_position(
 777                    t,
 778                    trip_id,
 779                    to_road,
 780                    to_rid,
 781                    ROAD_LANE_TYPE,
 782                    trip_type="trip",
 783                    attribute="arrivalLane",
 784                )
 785                if not trip_end:
 786                    continue
 787                via_eids.append(to_eid)
 788                via_rids = []
 789                for eid in via_eids:
 790                    if eid in self._id2uid.keys():
 791                        via_rids.append(self._id2uid[eid])
 792                schedules = []
 793                via_ends = [{} for _ in range(len(via_rids))]
 794                via_ends[-1] = trip_end
 795                pre_via_end = trip_home
 796                for i, rid in enumerate(via_rids):
 797                    via_end = via_ends[i]
 798                    if not via_end:
 799                        via_road = self._roads[rid]
 800                        if not via_road[ROAD_LANE_TYPE]:
 801                            continue
 802                        via_lid = random.choice(via_road[ROAD_LANE_TYPE])
 803                        via_s = (
 804                            random.uniform(0.1, 0.9) * self._lanes[via_lid]["length"]
 805                        )
 806                        via_end = {
 807                            "lane_position": {
 808                                "lane_id": via_lid,
 809                                "s": via_s,
 810                            }
 811                        }
 812                    pre_lid = pre_via_end["lane_position"]["lane_id"]
 813                    pre_geo = self._lanes[pre_lid]["geo"]
 814                    cur_lid = via_end["lane_position"]["lane_id"]
 815                    cur_geo = self._lanes[cur_lid]["geo"]
 816                    estimate_distance = np.sqrt(2) * cur_geo.distance(pre_geo)
 817                    eta = max(estimate_distance / SPEED, 5)
 818                    schedules.append(
 819                        {
 820                            "trips": [
 821                                {
 822                                    "mode": TRIP_MODE,
 823                                    "end": via_end,
 824                                    "activity": "other",
 825                                }
 826                            ],
 827                            "departure_time": departure,
 828                            "loop_count": 1,
 829                        }
 830                    )
 831                    departure += eta
 832                    pre_via_end = via_end
 833                self._output_agents.append(
 834                    {
 835                        "id": self.agent_uid,
 836                        "home": trip_home,
 837                        "attribute": self.agent_attribute,
 838                        "vehicle_attribute": self.vehicle_attribute,
 839                        "pedestrian_attribute": self.pedestrian_attribute,
 840                        "bike_attribute": self.bike_attribute,
 841                        "schedules": schedules,
 842                    }
 843                )
 844                self.agent_uid += 1
 845
 846        def get_flow_departure_times(
 847            f: minidom.Element, begin_time: np.float64, end_time: np.float64
 848        ) -> list[np.float64]:
 849            departure_times = []
 850            if f.hasAttribute("number"):
 851                number = int(f.getAttribute("number"))
 852                departure_times = list(
 853                    np.linspace(begin, end, number).astype(np.float64)
 854                )
 855            elif f.hasAttribute("period"):
 856                period = self._convert_time(f.getAttribute("period"))
 857                number = int((end - begin) / period)
 858                departure_times = list(
 859                    np.linspace(begin, end, number).astype(np.float64)
 860                )
 861            elif f.hasAttribute("vehsPerHour"):
 862                vehs_per_hour = int(f.getAttribute("vehsPerHour"))
 863                number = int(vehs_per_hour * (end_time - begin_time) / 3600)
 864                departure_times = list(
 865                    np.linspace(begin, end, number).astype(np.float64)
 866                )
 867            elif f.hasAttribute("probability"):
 868                prob = np.float64(f.getAttribute("probability"))
 869                for i in range(int(end - begin) + 1):
 870                    if random.random() < prob:
 871                        departure_times.append(np.float64(i + begin))
 872            return departure_times
 873
 874        if self._flows or self._intervals:
 875            logging.info("Converting flows")
 876        for f in self._flows:
 877            flow_id = f.getAttribute("id")
 878            begin = self._convert_time(f.getAttribute("begin"))
 879            end = self._convert_time(f.getAttribute("end"))
 880            departure_times = get_flow_departure_times(f, begin, end)
 881            if len(departure_times) < 1:
 882                logging.warning(f"Incomplete flow {flow_id} at vehicle num!")
 883                continue
 884            if f.hasAttribute("type"):
 885                flow_type = f.getAttribute("type")
 886                (
 887                    self.agent_attribute,
 888                    self.vehicle_attribute,
 889                    self.pedestrian_attribute,
 890                    self.bike_attribute,
 891                    self.agent_type,
 892                ) = self._vtype[flow_type]
 893            else:
 894                (
 895                    self.agent_attribute,
 896                    self.vehicle_attribute,
 897                    self.pedestrian_attribute,
 898                    self.bike_attribute,
 899                    self.agent_type,
 900                ) = (
 901                    DEFAULT_AGENT_ATTRIBUTE,
 902                    DEFAULT_VEHICLE_ATTRIBUTE,
 903                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 904                    DEFAULT_BIKE_ATTRIBUTE,
 905                    DEFAULT_AGENT_TYPE,
 906                )
 907            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 908            if not f.hasAttribute("from") or not f.hasAttribute("to"):
 909                # no from and to means this item has route
 910                self._convert_trips_with_route(
 911                    f,
 912                    departure_times,
 913                    TRIP_MODE,
 914                    ROAD_LANE_TYPE,
 915                    SPEED,
 916                    trip_id=flow_id,
 917                    trip_type="flow",
 918                )
 919            else:
 920                self._convert_flows_with_from_to(
 921                    f, departure_times, flow_id, ROAD_LANE_TYPE, TRIP_MODE
 922                )
 923        for i in self._intervals:
 924            begin = self._convert_time(i.getAttribute("begin"))
 925            end = self._convert_time(i.getAttribute("end"))
 926            for f in i.getElementsByTagName("flow"):
 927                flow_id = f.getAttribute("id")
 928                departure_times = get_flow_departure_times(f, begin, end)
 929                if len(departure_times) < 1:
 930                    logging.warning(f"Incomplete flow {flow_id} at vehicle num!")
 931                    continue
 932                if f.hasAttribute("type"):
 933                    flow_type = f.getAttribute("type")
 934                    (
 935                        self.agent_attribute,
 936                        self.vehicle_attribute,
 937                        self.pedestrian_attribute,
 938                        self.bike_attribute,
 939                        self.agent_type,
 940                    ) = self._vtype[flow_type]
 941                else:
 942                    (
 943                        self.agent_attribute,
 944                        self.vehicle_attribute,
 945                        self.pedestrian_attribute,
 946                        self.bike_attribute,
 947                        self.agent_type,
 948                    ) = (
 949                        DEFAULT_AGENT_ATTRIBUTE,
 950                        DEFAULT_VEHICLE_ATTRIBUTE,
 951                        DEFAULT_PEDESTRIAN_ATTRIBUTE,
 952                        DEFAULT_BIKE_ATTRIBUTE,
 953                        DEFAULT_AGENT_TYPE,
 954                    )
 955                (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 956                if not f.hasAttribute("from") or not f.hasAttribute("to"):
 957                    # no from and to means this item has route
 958                    self._convert_trips_with_route(
 959                        f,
 960                        departure_times,
 961                        TRIP_MODE,
 962                        ROAD_LANE_TYPE,
 963                        SPEED,
 964                        flow_id,
 965                        trip_type="flow",
 966                    )
 967                else:
 968                    self._convert_flows_with_from_to(
 969                        f, departure_times, flow_id, ROAD_LANE_TYPE, TRIP_MODE
 970                    )
 971        if self._vehicles:
 972            logging.info("Converting routes")
 973        for v in self._vehicles:
 974            veh_id = v.getAttribute("id")
 975            if v.hasAttribute("type"):
 976                vehicle_type = v.getAttribute("type")
 977                (
 978                    self.agent_attribute,
 979                    self.vehicle_attribute,
 980                    self.pedestrian_attribute,
 981                    self.bike_attribute,
 982                    self.agent_type,
 983                ) = self._vtype[vehicle_type]
 984            else:
 985                (
 986                    self.agent_attribute,
 987                    self.vehicle_attribute,
 988                    self.pedestrian_attribute,
 989                    self.bike_attribute,
 990                    self.agent_type,
 991                ) = (
 992                    DEFAULT_AGENT_ATTRIBUTE,
 993                    DEFAULT_VEHICLE_ATTRIBUTE,
 994                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 995                    DEFAULT_BIKE_ATTRIBUTE,
 996                    DEFAULT_AGENT_TYPE,
 997                )
 998            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 999            if v.hasAttribute("route"):
1000                route_id = v.getAttribute("route")
1001                vroute = self.route_dict[route_id]
1002            else:
1003                vroute = v.getElementsByTagName("route")[0]
1004            departure = self._convert_time(v.getAttribute("depart"))
1005            edges = vroute.getAttribute("edges").split(" ")
1006            repeat = (
1007                int(vroute.getAttribute("repeat"))
1008                if vroute.hasAttribute("repeat")
1009                else 0
1010            )
1011            vstops = vroute.getElementsByTagName("stop")
1012            rid2stop = self._convert_stops(
1013                all_stops=list(vstops), trip_id=veh_id, trip_type="vehicle"
1014            )
1015            cycle_time = (
1016                self._convert_time(vroute.getAttribute("cycleTime"))
1017                if vroute.hasAttribute("cycleTime")
1018                else np.float64(0)
1019            )
1020            route_trips = self._convert_route_trips(edges, repeat, cycle_time, rid2stop)
1021            if not route_trips:
1022                logging.warning(f"Bad route at vehicle {veh_id}")
1023                continue
1024            # process route_trips
1025            self._route_trips_to_person(
1026                route_trips,
1027                v,
1028                veh_id,
1029                ROAD_LANE_TYPE,
1030                "vehicle",
1031                TRIP_MODE,
1032                SPEED,
1033                departure,
1034            )
1035
1036        return {"persons": self._output_agents}
RouteConverter( converted_map: city.map.v2.map_pb2.Map, sumo_id_mappings: dict, route_path: str, additional_path: Optional[str] = None, seed: Optional[int] = 0)
 19    def __init__(
 20        self,
 21        converted_map: Map,
 22        sumo_id_mappings: dict,
 23        route_path: str,
 24        additional_path: Optional[str] = None,
 25        seed: Optional[int] = 0,
 26    ):
 27        """
 28        Args:
 29        - converted_map: The converted map from SUMO network.
 30        - sumo_id_mappings: The mapping from SUMO id to the unique id in the converted map.
 31        - route_path: The path to the SUMO route file.
 32        - additional_path: The path to the additional file containing bus stops, charging stations, and parking areas.
 33        - seed: The random seed.
 34        """
 35        if seed is not None:
 36            random.seed(seed)
 37        self._additional_path = additional_path  # additional file with "busStop","chargingStation","parkingArea"
 38        self._id2uid = sumo_id_mappings
 39        m = converted_map
 40        logging.info("Reading converted map")
 41        self._lanes = {}
 42        self._juncs = {}
 43        self._roads = {}
 44        for l in m.lanes:
 45            lid = l.id
 46            pres = l.predecessors
 47            sucs = l.successors
 48            self._lanes[lid] = {
 49                "type": l.type,
 50                "geo": LineString([[p.x, p.y] for p in l.center_line.nodes]),
 51                "in_lids": [p.id for p in pres],
 52                "out_lids": [s.id for s in sucs],
 53                "parent_id": l.parent_id,
 54                "length": l.length,
 55            }
 56        for j in m.junctions:
 57            jid = j.id
 58            self._juncs[jid] = {
 59                "lane_ids": j.lane_ids,
 60            }
 61        for r in m.roads:
 62            rid = r.id
 63            self._roads[rid] = {
 64                "lane_ids": r.lane_ids,
 65                "length": self._lanes[r.lane_ids[len(r.lane_ids) // 2]]["length"],
 66                "driving_lane_ids": [
 67                    lid
 68                    for lid in r.lane_ids
 69                    if self._lanes[lid]["type"] == mapv2.LANE_TYPE_DRIVING
 70                ],
 71                "walking_lane_ids": [
 72                    lid
 73                    for lid in r.lane_ids
 74                    if self._lanes[lid]["type"] == mapv2.LANE_TYPE_WALKING
 75                ],
 76            }
 77        logging.info(f"Reading route from {route_path}")
 78        dom_tree = parse(route_path)
 79        # get the root node
 80        root_node = dom_tree.documentElement
 81        # read SUMO .route.xml
 82        self._vtype = (
 83            {}
 84        )  # vehicle_id -> (agent_attribute,vehicle_attribute,pedestrian_attribute,bike_attribute,agent_type,)
 85        self._routes = root_node.getElementsByTagName("route")
 86        self._trips = root_node.getElementsByTagName("trip")
 87        self._flows = root_node.getElementsByTagName("flow")
 88        self._intervals = root_node.getElementsByTagName(
 89            "interval"
 90        )  # interval contains multiple `flows`
 91        self._vehicles = root_node.getElementsByTagName("vehicle")
 92        # output data
 93        self._output_agents = []
 94        for v in root_node.getElementsByTagName("vType"):
 95            vid = v.getAttribute("id")
 96            max_acc = (
 97                np.float64(v.getAttribute("accel")) if v.hasAttribute("accel") else 3.0
 98            )
 99            max_dec = (
100                -np.float64(v.getAttribute("decel"))
101                if v.hasAttribute("decel")
102                else -4.5
103            )
104            length = (
105                np.float64(v.getAttribute("length"))
106                if v.hasAttribute("length")
107                else 5.0
108            )
109            max_speed = (
110                np.float64(v.getAttribute("maxSpeed"))
111                if v.hasAttribute("maxSpeed")
112                else 41.6666666667
113            )
114            width = (
115                np.float64(v.getAttribute("width")) if v.hasAttribute("width") else 2.0
116            )
117            min_gap = (
118                np.float64(v.getAttribute("minGap"))
119                if v.hasAttribute("minGap")
120                else 1.0
121            )
122            v_class = v.getAttribute("vClass") if v.hasAttribute("vClass") else ""
123            if v_class == "pedestrian":
124                agent_type = "AGENT_TYPE_PERSON"
125            elif v_class == "bus":
126                agent_type = "AGENT_TYPE_BUS"
127            elif v_class == "bicycle":
128                agent_type = "AGENT_TYPE_BIKE"
129            else:
130                agent_type = "AGENT_TYPE_PRIVATE_CAR"
131
132            usual_acc = 2.0
133            usual_dec = -4.5
134            LC_length = 2 * length
135            # TODO: add other car-following-model
136            # https://sumo.dlr.de/docs/Definition_of_Vehicles%2C_Vehicle_Types%2C_and_Routes.html#car-following_models
137            # model_name = v.getAttribute("carFollowModel") if v.hasAttribute("carFollowModel") else "IDM"
138            self._vtype[vid] = (
139                {},
140                {
141                    "length": length,
142                    "width": width,
143                    "max_speed": max_speed,
144                    "max_acceleration": max_acc,
145                    "max_braking_acceleration": max_dec,
146                    "usual_acceleration": usual_acc,
147                    "usual_braking_acceleration": usual_dec,
148                    "lane_change_length": LC_length,
149                    "min_gap": min_gap,
150                },
151                {
152                    "speed": 1.34,
153                },
154                {
155                    "speed": 5.0,
156                },
157                agent_type,
158            )
159        self._additional_stops = {}
160        if self._additional_path:
161            add_dom_tree = parse(self._additional_path)
162            add_root_node = add_dom_tree.documentElement
163            for stop_type in ["busStop", "chargingStation", "parkingArea"]:
164                for stop in add_root_node.getElementsByTagName(stop_type):
165                    stop_id = stop.getAttribute("id")
166                    stop_name = (
167                        stop.getAttribute("name") if stop.hasAttribute("name") else ""
168                    )
169                    if stop.getAttribute("lane") in self._id2uid.keys():
170                        stop_lid = self._id2uid[stop.getAttribute("lane")]
171                        stop_lane = self._lanes[stop_lid]
172                        start_pos = (
173                            np.float64(stop.getAttribute("startPos"))
174                            if stop.hasAttribute("startPos")
175                            else 0.1 * stop_lane["length"]
176                        )
177                        if start_pos < 0:
178                            start_pos += stop_lane["length"]
179                        end_pos = (
180                            np.float64(stop.getAttribute("endPos"))
181                            if stop.hasAttribute("endPos")
182                            else 0.9 * stop_lane["length"]
183                        )
184                        if end_pos < 0:
185                            end_pos += stop_lane["length"]
186                        stop_s = (end_pos + start_pos) / 2
187                        stop_s = np.clip(
188                            stop_s, 0.1 * stop_lane["length"], 0.9 * stop_lane["length"]
189                        )
190                        veh_stop = {
191                            "lane_position": {
192                                "lane_id": stop_lid,
193                                "s": stop_s,
194                            }
195                        }
196                        parent_rid = stop_lane["parent_id"]
197                        self._additional_stops[stop_id] = (veh_stop, parent_rid)
198                    else:
199                        logging.warning(f"Invalid stop {stop_id} {stop_name}!")

Args:

  • converted_map: The converted map from SUMO network.
  • sumo_id_mappings: The mapping from SUMO id to the unique id in the converted map.
  • route_path: The path to the SUMO route file.
  • additional_path: The path to the additional file containing bus stops, charging stations, and parking areas.
  • seed: The random seed.
def convert_route(self):
 659    def convert_route(self):
 660        self.agent_uid = 0
 661        DEFAULT_AGENT_ATTRIBUTE = {}
 662        DEFAULT_VEHICLE_ATTRIBUTE = {
 663            "length": 5,
 664            "width": 2,
 665            "max_speed": 41.6666666667,
 666            "max_acceleration": 3,
 667            "max_braking_acceleration": -10,
 668            "usual_acceleration": 2,
 669            "usual_braking_acceleration": -4.5,
 670            "lane_change_length": 10,
 671            "min_gap": 1,
 672        }
 673        DEFAULT_PEDESTRIAN_ATTRIBUTE = {"speed": 1.34}
 674        DEFAULT_BIKE_ATTRIBUTE = {"speed": 5}
 675        DEFAULT_AGENT_TYPE = "AGENT_TYPE_PRIVATE_CAR"
 676
 677        # Route contains the edges that all vehicles pass through, that is, the complete trajectory
 678        # Route can be defined separately from vehicle or under vehicle, so additional judgment is required.
 679        self.route_dict = {}
 680        for r in self._routes:
 681            route_id = r.getAttribute("id")
 682            self.route_dict[route_id] = r
 683        if self._trips:
 684            logging.info("Converting trips")
 685        for t in self._trips:
 686            if t.hasAttribute("type"):
 687                trip_type = t.getAttribute("type")
 688                (
 689                    self.agent_attribute,
 690                    self.vehicle_attribute,
 691                    self.pedestrian_attribute,
 692                    self.bike_attribute,
 693                    self.agent_type,
 694                ) = self._vtype[trip_type]
 695            else:
 696                (
 697                    self.agent_attribute,
 698                    self.vehicle_attribute,
 699                    self.pedestrian_attribute,
 700                    self.bike_attribute,
 701                    self.agent_type,
 702                ) = (
 703                    DEFAULT_AGENT_ATTRIBUTE,
 704                    DEFAULT_VEHICLE_ATTRIBUTE,
 705                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 706                    DEFAULT_BIKE_ATTRIBUTE,
 707                    DEFAULT_AGENT_TYPE,
 708                )
 709            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 710            trip_id = t.getAttribute("id")
 711            departure = self._convert_time(t.getAttribute("depart"))
 712            if not t.hasAttribute("from") or not t.hasAttribute("to"):
 713                # no from and to means this item has route
 714                if t.hasAttribute("route"):
 715                    route_id = t.getAttribute("route")
 716                    troute = self.route_dict[route_id]
 717                else:
 718                    troute = t.getElementsByTagName("route")[0]
 719                edges = troute.getAttribute("edges").split(" ")
 720                repeat = (
 721                    int(troute.getAttribute("repeat"))
 722                    if troute.hasAttribute("repeat")
 723                    else 0
 724                )
 725                vstops = troute.getElementsByTagName("stop")
 726                rid2stop = self._convert_stops(
 727                    all_stops=list(t.getElementsByTagName("stop")) + list(vstops),
 728                    trip_id=trip_id,
 729                    trip_type="trip",
 730                )
 731
 732                cycle_time = (
 733                    self._convert_time(troute.getAttribute("cycleTime"))
 734                    if troute.hasAttribute("cycleTime")
 735                    else np.float64(0)
 736                )
 737                route_trips = self._convert_route_trips(
 738                    edges, repeat, cycle_time, rid2stop
 739                )
 740
 741                if not route_trips:
 742                    logging.warning(f"Bad route at trip {trip_id}")
 743                    continue
 744                # process route_trips
 745                self._route_trips_to_person(
 746                    route_trips,
 747                    t,
 748                    trip_id,
 749                    ROAD_LANE_TYPE,
 750                    "trip",
 751                    TRIP_MODE,
 752                    SPEED,
 753                    departure,
 754                )
 755            else:
 756                from_eid = t.getAttribute("from")
 757                via_eids = (
 758                    t.getAttribute("via").split(" ") if t.hasAttribute("via") else []
 759                )
 760                to_eid = t.getAttribute("to")
 761                from_rid = self._id2uid[from_eid]
 762                from_road = self._roads[from_rid]
 763                to_rid = self._id2uid[to_eid]
 764                to_road = self._roads[to_rid]
 765                trip_home = self._get_trip_position(
 766                    t,
 767                    trip_id,
 768                    from_road,
 769                    from_rid,
 770                    ROAD_LANE_TYPE,
 771                    trip_type="trip",
 772                    attribute="departLane",
 773                )
 774                if not trip_home:
 775                    continue
 776                trip_end = self._get_trip_position(
 777                    t,
 778                    trip_id,
 779                    to_road,
 780                    to_rid,
 781                    ROAD_LANE_TYPE,
 782                    trip_type="trip",
 783                    attribute="arrivalLane",
 784                )
 785                if not trip_end:
 786                    continue
 787                via_eids.append(to_eid)
 788                via_rids = []
 789                for eid in via_eids:
 790                    if eid in self._id2uid.keys():
 791                        via_rids.append(self._id2uid[eid])
 792                schedules = []
 793                via_ends = [{} for _ in range(len(via_rids))]
 794                via_ends[-1] = trip_end
 795                pre_via_end = trip_home
 796                for i, rid in enumerate(via_rids):
 797                    via_end = via_ends[i]
 798                    if not via_end:
 799                        via_road = self._roads[rid]
 800                        if not via_road[ROAD_LANE_TYPE]:
 801                            continue
 802                        via_lid = random.choice(via_road[ROAD_LANE_TYPE])
 803                        via_s = (
 804                            random.uniform(0.1, 0.9) * self._lanes[via_lid]["length"]
 805                        )
 806                        via_end = {
 807                            "lane_position": {
 808                                "lane_id": via_lid,
 809                                "s": via_s,
 810                            }
 811                        }
 812                    pre_lid = pre_via_end["lane_position"]["lane_id"]
 813                    pre_geo = self._lanes[pre_lid]["geo"]
 814                    cur_lid = via_end["lane_position"]["lane_id"]
 815                    cur_geo = self._lanes[cur_lid]["geo"]
 816                    estimate_distance = np.sqrt(2) * cur_geo.distance(pre_geo)
 817                    eta = max(estimate_distance / SPEED, 5)
 818                    schedules.append(
 819                        {
 820                            "trips": [
 821                                {
 822                                    "mode": TRIP_MODE,
 823                                    "end": via_end,
 824                                    "activity": "other",
 825                                }
 826                            ],
 827                            "departure_time": departure,
 828                            "loop_count": 1,
 829                        }
 830                    )
 831                    departure += eta
 832                    pre_via_end = via_end
 833                self._output_agents.append(
 834                    {
 835                        "id": self.agent_uid,
 836                        "home": trip_home,
 837                        "attribute": self.agent_attribute,
 838                        "vehicle_attribute": self.vehicle_attribute,
 839                        "pedestrian_attribute": self.pedestrian_attribute,
 840                        "bike_attribute": self.bike_attribute,
 841                        "schedules": schedules,
 842                    }
 843                )
 844                self.agent_uid += 1
 845
 846        def get_flow_departure_times(
 847            f: minidom.Element, begin_time: np.float64, end_time: np.float64
 848        ) -> list[np.float64]:
 849            departure_times = []
 850            if f.hasAttribute("number"):
 851                number = int(f.getAttribute("number"))
 852                departure_times = list(
 853                    np.linspace(begin, end, number).astype(np.float64)
 854                )
 855            elif f.hasAttribute("period"):
 856                period = self._convert_time(f.getAttribute("period"))
 857                number = int((end - begin) / period)
 858                departure_times = list(
 859                    np.linspace(begin, end, number).astype(np.float64)
 860                )
 861            elif f.hasAttribute("vehsPerHour"):
 862                vehs_per_hour = int(f.getAttribute("vehsPerHour"))
 863                number = int(vehs_per_hour * (end_time - begin_time) / 3600)
 864                departure_times = list(
 865                    np.linspace(begin, end, number).astype(np.float64)
 866                )
 867            elif f.hasAttribute("probability"):
 868                prob = np.float64(f.getAttribute("probability"))
 869                for i in range(int(end - begin) + 1):
 870                    if random.random() < prob:
 871                        departure_times.append(np.float64(i + begin))
 872            return departure_times
 873
 874        if self._flows or self._intervals:
 875            logging.info("Converting flows")
 876        for f in self._flows:
 877            flow_id = f.getAttribute("id")
 878            begin = self._convert_time(f.getAttribute("begin"))
 879            end = self._convert_time(f.getAttribute("end"))
 880            departure_times = get_flow_departure_times(f, begin, end)
 881            if len(departure_times) < 1:
 882                logging.warning(f"Incomplete flow {flow_id} at vehicle num!")
 883                continue
 884            if f.hasAttribute("type"):
 885                flow_type = f.getAttribute("type")
 886                (
 887                    self.agent_attribute,
 888                    self.vehicle_attribute,
 889                    self.pedestrian_attribute,
 890                    self.bike_attribute,
 891                    self.agent_type,
 892                ) = self._vtype[flow_type]
 893            else:
 894                (
 895                    self.agent_attribute,
 896                    self.vehicle_attribute,
 897                    self.pedestrian_attribute,
 898                    self.bike_attribute,
 899                    self.agent_type,
 900                ) = (
 901                    DEFAULT_AGENT_ATTRIBUTE,
 902                    DEFAULT_VEHICLE_ATTRIBUTE,
 903                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 904                    DEFAULT_BIKE_ATTRIBUTE,
 905                    DEFAULT_AGENT_TYPE,
 906                )
 907            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 908            if not f.hasAttribute("from") or not f.hasAttribute("to"):
 909                # no from and to means this item has route
 910                self._convert_trips_with_route(
 911                    f,
 912                    departure_times,
 913                    TRIP_MODE,
 914                    ROAD_LANE_TYPE,
 915                    SPEED,
 916                    trip_id=flow_id,
 917                    trip_type="flow",
 918                )
 919            else:
 920                self._convert_flows_with_from_to(
 921                    f, departure_times, flow_id, ROAD_LANE_TYPE, TRIP_MODE
 922                )
 923        for i in self._intervals:
 924            begin = self._convert_time(i.getAttribute("begin"))
 925            end = self._convert_time(i.getAttribute("end"))
 926            for f in i.getElementsByTagName("flow"):
 927                flow_id = f.getAttribute("id")
 928                departure_times = get_flow_departure_times(f, begin, end)
 929                if len(departure_times) < 1:
 930                    logging.warning(f"Incomplete flow {flow_id} at vehicle num!")
 931                    continue
 932                if f.hasAttribute("type"):
 933                    flow_type = f.getAttribute("type")
 934                    (
 935                        self.agent_attribute,
 936                        self.vehicle_attribute,
 937                        self.pedestrian_attribute,
 938                        self.bike_attribute,
 939                        self.agent_type,
 940                    ) = self._vtype[flow_type]
 941                else:
 942                    (
 943                        self.agent_attribute,
 944                        self.vehicle_attribute,
 945                        self.pedestrian_attribute,
 946                        self.bike_attribute,
 947                        self.agent_type,
 948                    ) = (
 949                        DEFAULT_AGENT_ATTRIBUTE,
 950                        DEFAULT_VEHICLE_ATTRIBUTE,
 951                        DEFAULT_PEDESTRIAN_ATTRIBUTE,
 952                        DEFAULT_BIKE_ATTRIBUTE,
 953                        DEFAULT_AGENT_TYPE,
 954                    )
 955                (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 956                if not f.hasAttribute("from") or not f.hasAttribute("to"):
 957                    # no from and to means this item has route
 958                    self._convert_trips_with_route(
 959                        f,
 960                        departure_times,
 961                        TRIP_MODE,
 962                        ROAD_LANE_TYPE,
 963                        SPEED,
 964                        flow_id,
 965                        trip_type="flow",
 966                    )
 967                else:
 968                    self._convert_flows_with_from_to(
 969                        f, departure_times, flow_id, ROAD_LANE_TYPE, TRIP_MODE
 970                    )
 971        if self._vehicles:
 972            logging.info("Converting routes")
 973        for v in self._vehicles:
 974            veh_id = v.getAttribute("id")
 975            if v.hasAttribute("type"):
 976                vehicle_type = v.getAttribute("type")
 977                (
 978                    self.agent_attribute,
 979                    self.vehicle_attribute,
 980                    self.pedestrian_attribute,
 981                    self.bike_attribute,
 982                    self.agent_type,
 983                ) = self._vtype[vehicle_type]
 984            else:
 985                (
 986                    self.agent_attribute,
 987                    self.vehicle_attribute,
 988                    self.pedestrian_attribute,
 989                    self.bike_attribute,
 990                    self.agent_type,
 991                ) = (
 992                    DEFAULT_AGENT_ATTRIBUTE,
 993                    DEFAULT_VEHICLE_ATTRIBUTE,
 994                    DEFAULT_PEDESTRIAN_ATTRIBUTE,
 995                    DEFAULT_BIKE_ATTRIBUTE,
 996                    DEFAULT_AGENT_TYPE,
 997                )
 998            (TRIP_MODE, SPEED, ROAD_LANE_TYPE) = self._process_agent_type()
 999            if v.hasAttribute("route"):
1000                route_id = v.getAttribute("route")
1001                vroute = self.route_dict[route_id]
1002            else:
1003                vroute = v.getElementsByTagName("route")[0]
1004            departure = self._convert_time(v.getAttribute("depart"))
1005            edges = vroute.getAttribute("edges").split(" ")
1006            repeat = (
1007                int(vroute.getAttribute("repeat"))
1008                if vroute.hasAttribute("repeat")
1009                else 0
1010            )
1011            vstops = vroute.getElementsByTagName("stop")
1012            rid2stop = self._convert_stops(
1013                all_stops=list(vstops), trip_id=veh_id, trip_type="vehicle"
1014            )
1015            cycle_time = (
1016                self._convert_time(vroute.getAttribute("cycleTime"))
1017                if vroute.hasAttribute("cycleTime")
1018                else np.float64(0)
1019            )
1020            route_trips = self._convert_route_trips(edges, repeat, cycle_time, rid2stop)
1021            if not route_trips:
1022                logging.warning(f"Bad route at vehicle {veh_id}")
1023                continue
1024            # process route_trips
1025            self._route_trips_to_person(
1026                route_trips,
1027                v,
1028                veh_id,
1029                ROAD_LANE_TYPE,
1030                "vehicle",
1031                TRIP_MODE,
1032                SPEED,
1033                departure,
1034            )
1035
1036        return {"persons": self._output_agents}