def _closest_neighbour_in_grid4_directions( current_city_idx: int, city_positions: IntVector2DArray) -> List[int]: """ Finds the closest city in each direction of the current city Parameters ---------- current_city_idx: int Index of current city city_positions: IntVector2DArray Vector containing the coordinates of all cities Returns ------- Returns indices of closest neighbour in every direction NESW """ city_distances = [] closest_neighbour: List[int] = [None for i in range(4)] # compute distance to all other cities for city_idx in range(len(city_positions)): city_distances.append( Vec2dOperations.get_manhattan_distance( city_positions[current_city_idx], city_positions[city_idx])) sorted_neighbours = np.argsort(city_distances) for neighbour in sorted_neighbours[1:]: # do not include city itself direction_to_neighbour = direction_to_point( city_positions[current_city_idx], city_positions[neighbour]) if closest_neighbour[direction_to_neighbour] is None: closest_neighbour[direction_to_neighbour] = neighbour # early return once all 4 directions have a closest neighbour if None not in closest_neighbour: return closest_neighbour return closest_neighbour
def _generate_city_connection_points( city_positions: IntVector2DArray, city_radius: int, vector_field: IntVector2DArray, rails_between_cities: int, rails_in_city: int = 2, np_random: RandomState = None ) -> (List[List[List[IntVector2D]]], List[List[List[IntVector2D]]], List[ np.ndarray], List[Grid4TransitionsEnum]): """ Generate the city connection points. Internal connection points are used to generate the parallel paths within the city. External connection points are used to connect different cities together Parameters ---------- city_positions: IntVector2DArray Vector that contains all the positions of the cities city_radius: int Radius of each city. Cities are squares with edge length 2 * city_radius + 1 vector_field: IntVector2DArray Vectorfield of the size of the environment. It is used to generate preferred orienations for each cell. Each cell contains the prefered orientation of cells. If no prefered orientation is present it is set to -1 rails_between_cities: int Number of rails that connect out from the city rails_in_city: int Number of rails within the city Returns ------- inner_connection_points: List of List of length number of cities Contains all the inner connection points for each boarder of each city. [North_Points, East_Poinst, South_Points, West_Points] outer_connection_points: List of List of length number of cities Contains all the outer connection points for each boarder of the city. [North_Points, East_Poinst, South_Points, West_Points] city_orientations: List of length number of cities Contains all the orientations of cities. This is then used to orient agents according to the rails city_cells: List List containing the coordinates of all the cells that belong to a city. This is used by other algorithms to avoid drawing inter-city-rails through cities. """ inner_connection_points: List[List[List[IntVector2D]]] = [] outer_connection_points: List[List[List[IntVector2D]]] = [] city_orientations: List[Grid4TransitionsEnum] = [] city_cells: IntVector2DArray = [] for city_position in city_positions: # Chose the directions where close cities are situated neighb_dist = [] for neighbour_city in city_positions: neighb_dist.append( Vec2dOperations.get_manhattan_distance( city_position, neighbour_city)) closest_neighb_idx = argsort(neighb_dist) # Store the directions to these neighbours and orient city to face closest neighbour connection_sides_idx = [] idx = 1 if grid_mode: current_closest_direction = np_random.randint(4) else: current_closest_direction = direction_to_point( city_position, city_positions[closest_neighb_idx[idx]]) connection_sides_idx.append(current_closest_direction) connection_sides_idx.append((current_closest_direction + 2) % 4) city_orientations.append(current_closest_direction) city_cells.extend( _get_cells_in_city(city_position, city_radius, city_orientations[-1], vector_field)) # set the number of tracks within a city, at least 2 tracks per city connections_per_direction = np.zeros(4, dtype=int) nr_of_connection_points = np_random.randint(2, rails_in_city + 1) for idx in connection_sides_idx: connections_per_direction[idx] = nr_of_connection_points connection_points_coordinates_inner: List[List[IntVector2D]] = [ [] for i in range(4) ] connection_points_coordinates_outer: List[List[IntVector2D]] = [ [] for i in range(4) ] number_of_out_rails = np_random.randint( 1, min(rails_between_cities, nr_of_connection_points) + 1) start_idx = int( (nr_of_connection_points - number_of_out_rails) / 2) for direction in range(4): connection_slots = np.arange( nr_of_connection_points) - start_idx # Offset the rails away from the center of the city offset_distances = np.arange(nr_of_connection_points) - int( nr_of_connection_points / 2) # The clipping helps ofsetting one side more than the other to avoid switches at same locations # The magic number plus one is added such that all points have at least one offset inner_point_offset = np.abs(offset_distances) + np.clip( offset_distances, 0, 1) + 1 for connection_idx in range( connections_per_direction[direction]): if direction == 0: tmp_coordinates = (city_position[0] - city_radius + inner_point_offset[connection_idx], city_position[1] + connection_slots[connection_idx]) out_tmp_coordinates = ( city_position[0] - city_radius, city_position[1] + connection_slots[connection_idx]) if direction == 1: tmp_coordinates = (city_position[0] + connection_slots[connection_idx], city_position[1] + city_radius - inner_point_offset[connection_idx]) out_tmp_coordinates = ( city_position[0] + connection_slots[connection_idx], city_position[1] + city_radius) if direction == 2: tmp_coordinates = (city_position[0] + city_radius - inner_point_offset[connection_idx], city_position[1] + connection_slots[connection_idx]) out_tmp_coordinates = ( city_position[0] + city_radius, city_position[1] + connection_slots[connection_idx]) if direction == 3: tmp_coordinates = (city_position[0] + connection_slots[connection_idx], city_position[1] - city_radius + inner_point_offset[connection_idx]) out_tmp_coordinates = ( city_position[0] + connection_slots[connection_idx], city_position[1] - city_radius) connection_points_coordinates_inner[direction].append( tmp_coordinates) if connection_idx in range(start_idx, start_idx + number_of_out_rails): connection_points_coordinates_outer[direction].append( out_tmp_coordinates) inner_connection_points.append(connection_points_coordinates_inner) outer_connection_points.append(connection_points_coordinates_outer) return inner_connection_points, outer_connection_points, city_orientations, city_cells
def _connect_cities( city_positions: IntVector2DArray, connection_points: List[List[List[IntVector2D]]], city_cells: IntVector2DArray, rail_trans: RailEnvTransitions, grid_map: RailEnvTransitions) -> List[IntVector2DArray]: """ Connects cities together through rails. Each city connects from its outgoing connection points to the closest cities. This guarantees that all connection points are used. Parameters ---------- city_positions: IntVector2DArray All coordinates of the cities connection_points: List[List[List[IntVector2D]]] List of coordinates of all outer connection points city_cells: IntVector2DArray Coordinates of all the cells contained in any city. This is used to avoid drawing rails through existing cities. rail_trans: RailEnvTransitions Railway transition objects grid_map: RailEnvTransitions The grid map containing the rails. Used to draw new rails Returns ------- Returns a list of all the cells (Coordinates) that belong to a rail path. This can be used to access railway cells later. """ all_paths: List[IntVector2DArray] = [] connect_cities = [] connected_points = [] grid4_directions = [ Grid4TransitionsEnum.NORTH, Grid4TransitionsEnum.EAST, Grid4TransitionsEnum.SOUTH, Grid4TransitionsEnum.WEST ] for current_city_idx in np.arange(len(city_positions)): closest_neighbours = _closest_neighbour_in_grid4_directions( current_city_idx, city_positions) for out_direction in grid4_directions: neighbour_idx = get_closest_neighbour_for_direction( closest_neighbours, out_direction) if set((current_city_idx, neighbour_idx)) in connect_cities: continue for city_out_connection_point in connection_points[ current_city_idx][out_direction]: city_out_connection_dir = out_direction min_connection_dist = np.inf for direction in grid4_directions: current_points = connection_points[neighbour_idx][ direction] for tmp_in_connection_point in current_points: tmp_dist = Vec2dOperations.get_manhattan_distance( city_out_connection_point, tmp_in_connection_point) if tmp_dist < min_connection_dist: min_connection_dist = tmp_dist neighbour_connection_point = tmp_in_connection_point neighbour_connection_dir = direction if set((*city_out_connection_point, *neighbour_connection_point)) in connected_points: continue lines = _align_start_end(city_out_connection_point, neighbour_connection_point,\ city_out_connection_dir, neighbour_connection_dir, grid_map, rail_trans, city_cells) if len(city_positions) == 2: new_line = connect_points(lines, grid_map, rail_trans) else: new_line = connect_rail_in_grid_map( grid_map, city_out_connection_point, neighbour_connection_point, rail_trans, flip_start_node_trans=False, flip_end_node_trans=False, respect_transition_validity=False, avoid_rail=True, forbidden_cells=city_cells) all_paths.extend(new_line) connect_cities.append( set((current_city_idx, neighbour_idx))) connected_points.append( set((*city_out_connection_point, *neighbour_connection_point))) return all_paths
def test_vec2d_manhattan_distance(): node_a = (3, -7) node_0 = (0, 0) assert Vec2d.get_manhattan_distance(node_a, node_0) == 3 + 7