From 5ef97a4a925ac2978e0a439fe098a7c6c73a249a Mon Sep 17 00:00:00 2001 From: ll7 Date: Wed, 6 Mar 2024 16:17:23 +0100 Subject: [PATCH] Add docs for MapDefinitionPool class and serialize_map function --- robot_sf/nav/map_config.py | 68 +++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/robot_sf/nav/map_config.py b/robot_sf/nav/map_config.py index 189558d..c68204b 100644 --- a/robot_sf/nav/map_config.py +++ b/robot_sf/nav/map_config.py @@ -274,46 +274,109 @@ def find_route(self, spawn_id: int, goal_id: int) -> Union[GlobalRoute, None]: @dataclass class MapDefinitionPool: + """ + A class to represent a pool of map definitions. + + Attributes + ---------- + maps_folder : str + The directory where the map files are located. + map_defs : List[MapDefinition] + The list of map definitions. + + Methods + ------- + __post_init__(): + Validates and initializes the map_defs attribute. + choose_random_map(): + Returns a random map definition from the pool. + """ + maps_folder: str = os.path.join(os.path.dirname(os.path.dirname(__file__)), "maps") map_defs: List[MapDefinition] = field(default_factory=list) def __post_init__(self): + """ + Validates and initializes the map_defs attribute. + If map_defs is empty, it loads the map definitions from the files in the + maps_folder directory. + Raises a ValueError if the maps_folder directory does not exist or if + map_defs is still empty after loading. + """ + + # If map_defs is empty, load the map definitions from the files if not self.map_defs: + # Check if the maps_folder directory exists if not os.path.exists(self.maps_folder) or not os.path.isdir(self.maps_folder): raise ValueError(f"Map directory '{self.maps_folder}' does not exist!") + # Function to load a JSON file def load_json(path: str) -> dict: with open(path, 'r', encoding='utf-8') as file: return json.load(file) + + # Get the list of map files map_files = [os.path.join(self.maps_folder, f) for f in os.listdir(self.maps_folder)] + + # Load the map definitions from the files self.map_defs = [serialize_map(load_json(f)) for f in map_files] + # If map_defs is still empty, raise an error if not self.map_defs: raise ValueError('Map pool is empty! Please specify some maps!') def choose_random_map(self) -> MapDefinition: + """ + Returns a random map definition from the pool. + + Returns + ------- + MapDefinition + A random map definition. + """ + return random.choice(self.map_defs) def serialize_map(map_structure: dict) -> MapDefinition: + """ + Converts a map structure dictionary into a MapDefinition object. + + Parameters + ---------- + map_structure : dict + The map structure dictionary. + + Returns + ------- + MapDefinition + The MapDefinition object. + """ + + # Extract the x and y margins and calculate the width and height (min_x, max_x), (min_y, max_y) = map_structure['x_margin'], map_structure['y_margin'] width, height = max_x - min_x, max_y - min_y + # Function to normalize a position def norm_pos(pos: Vec2D) -> Vec2D: return (pos[0] - min_x, pos[1] - min_y) + # Normalize the obstacles obstacles = [Obstacle([norm_pos(p) for p in vertices]) for vertices in map_structure['obstacles']] + # Function to normalize a zone def norm_zone(rect: Rect) -> Rect: return (norm_pos(rect[0]), norm_pos(rect[1]), norm_pos(rect[2])) + # Normalize the zones robot_goal_zones = [norm_zone(z) for z in map_structure['robot_goal_zones']] robot_spawn_zones = [norm_zone(z) for z in map_structure['robot_spawn_zones']] ped_goal_zones = [norm_zone(z) for z in map_structure['ped_goal_zones']] ped_spawn_zones = [norm_zone(z) for z in map_structure['ped_spawn_zones']] ped_crowded_zones = [norm_zone(z) for z in map_structure['ped_crowded_zones']] + # Normalize the routes robot_routes = [GlobalRoute(o['spawn_id'], o['goal_id'], [norm_pos(p) for p in o['waypoints']], robot_spawn_zones[o['spawn_id']], robot_goal_zones[o['goal_id']]) for o in map_structure['robot_routes']] @@ -321,21 +384,24 @@ def norm_zone(rect: Rect) -> Rect: ped_spawn_zones[o['spawn_id']], ped_goal_zones[o['goal_id']]) for o in map_structure['ped_routes']] + # Function to reverse a route def reverse_route(route: GlobalRoute) -> GlobalRoute: return GlobalRoute( route.goal_id, route.spawn_id, list(reversed(route.waypoints)), route.goal_zone, route.spawn_zone) - # info: this works because spawn and goal zones are the same + # Reverse the robot routes and add them to the list of routes rev_robot_routes = [reverse_route(r) for r in robot_routes] robot_routes = robot_routes + rev_robot_routes + # Define the map bounds map_bounds = [ (0, width, 0, 0), # bottom (0, width, height, height), # top (0, 0, 0, height), # left (width, width, 0, height)] # right + # Return the MapDefinition object return MapDefinition( width, height, obstacles, robot_spawn_zones, ped_spawn_zones, robot_goal_zones, map_bounds, robot_routes,