Source code for simulation.utils.road.road

"""Road class used to define roads as a python class.

A simulated road can be defined through an object of type Road. It contains all sections of
that road as a list.
"""

import importlib
import os
import random
from dataclasses import dataclass, field
from typing import List, Tuple

from simulation.utils.geometry import Pose, Transform
from simulation.utils.road.sections.road_section import RoadSection


[docs]@dataclass class Road: """Container object for roads. A road consists of multiple road sections that are concatenated. The sections attribute contains these sections in the correct order. """ _name: str = field(default=None, init=False) """Name of the road. The name attribute is determined by the name of the file. It is filled in when the road is generated. """ _seed: str = field(default=None, init=False) """Seed used when generating the road. Determined when generating the road. """ use_seed: bool = True """Use a default seed if none is provided. By default the `use_seed` attribute is true and a seed is set before creating \ the road. If `use_seed` is set to False, the seed is invalidated and there will \ be different random values every time the road is created. """ sections: List[RoadSection] = field(default_factory=list) """All sections of the road.""" length: float = 0 """Length of road.""" def __post_init__(self): """Set random seed if specified.""" if not self.use_seed: # Overwrite seed random.seed()
[docs] def append(self, section: RoadSection): """Append a road section. Determine id of the section. Args: section: New road section. """ section.id = len(self.sections) if section.id == 0: section._is_start = True if section.id > 0: # Pass ending of last section as the transformation to next section ending: Tuple[Pose, float] = self.sections[-1].get_ending() section.set_transform(Transform(ending[0], ending[0].orientation)) section.prev_length = self.length self.length = self.length + section.middle_line.length self.sections.append(section)
[docs] def close_loop(self, p_curvature: float = 2): """Append a road section that connects the last section to the beginning. The road's beginning and it's end are connected using a cubic bezier curve. Args: p_curvature: Scale the curvature of the resulting loop. """ # Global position of the end of the road end_pose_global, _ = self.sections[-1].get_ending() # Distance to start / p_curvature approximate_radius = abs(end_pose_global.position) / p_curvature section = RoadSection.fit_ending( end_pose_global, Pose([0, 0], 0), approximate_radius ) self.append(section)
DEFAULT_ROAD_DIR = os.path.join( os.environ.get("KITCAR_REPO_PATH"), "kitcar-gazebo-simulation", "simulation", "models", "env_db", )
[docs]def _get_module(name: str): if not os.path.isabs(name): name = os.path.join(DEFAULT_ROAD_DIR, name) if not name.endswith(".py"): name += ".py" # Remove .py module_name = os.path.basename(name)[:-3] try: spec = importlib.util.spec_from_file_location(module_name, name) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module, name, module_name except AttributeError or ModuleNotFoundError: raise ValueError(f"Road {module_name} not found at {name}.")
[docs]def load(road_name: str, seed: str = "KITCAR") -> Road: """Load road object from file. Args: road_name: Name of the file containing the road definition. seed: Predetermine random values. """ # Set random seed # set it at this point because the module is reloaded afterwards # the above import is just to ensure that the road is in the module cache random.seed(seed) # Ensure that the road is up to date road_module, file_path, road_name = _get_module(road_name) road = road_module.road road._file_path = file_path road._name = road_name road._seed = seed return road_module.road