Source code for irsim.world.map.obstacle_map
from typing import Any
import numpy as np
import shapely
from shapely.geometry import MultiLineString, Point
from shapely.strtree import STRtree
from irsim.world.object_base import ObjectBase
# Grid-based collision detection constants
# Occupancy threshold: grid values > this are considered obstacles (0-100 range)
OCCUPANCY_THRESHOLD = 50
# Offset to convert grid index to cell center (0.5 = center of cell)
CELL_CENTER_OFFSET = 0.5
# Collision radius as fraction of cell size (0.5 = half cell width)
COLLISION_RADIUS_FACTOR = 0.5
[docs]
class ObstacleMap(ObjectBase):
def __init__(
self,
shape: dict | None = None,
color: str = "k",
static: bool = True,
grid_map: np.ndarray | None = None,
grid_reso: np.ndarray | None = None,
world_offset: list[float] | None = None,
**kwargs: Any,
) -> None:
"""Create an obstacle map object from a set of line segments.
Args:
shape (dict | None): Map shape configuration with keys like
``{"name": "map", "reso": float, "points": array}``.
color (str): Display color. Default "k".
static (bool): Whether the object is static. Default True.
grid_map (np.ndarray | None): Grid map array for fast collision detection.
grid_reso (np.ndarray | None): Resolution [x_reso, y_reso] of the grid.
world_offset (list | None): World offset [x, y].
**kwargs: Forwarded to ``ObjectBase`` constructor.
"""
if shape is None:
shape = {"name": "map", "reso": "0.1", "points": None}
super().__init__(
shape=shape,
role="obstacle",
color=color,
static=static,
**kwargs,
)
self.linestrings = list(self.geometry.geoms)
self.geometry_tree = STRtree(self.linestrings)
# Grid-based collision detection data
self.grid_map = grid_map
self.grid_reso = (
grid_reso if grid_reso is not None else np.array([[1.0], [1.0]])
)
self.world_offset = world_offset if world_offset is not None else [0.0, 0.0]
[docs]
def check_grid_collision(self, geometry) -> bool:
"""Check collision using grid array lookup.
Uses a two-phase approach:
1. Quick bounding box check for early rejection
2. Precise check: verify if geometry actually intersects occupied cells
Args:
geometry: Shapely geometry object to check collision for.
Returns:
bool: True if collision detected, False otherwise.
"""
if self.grid_map is None:
return False
# Get bounding box of the geometry
minx, miny, maxx, maxy = geometry.bounds
# Convert world coordinates to grid indices
x_reso = self.grid_reso[0, 0]
y_reso = self.grid_reso[1, 0]
offset_x, offset_y = self.world_offset
# Calculate grid indices (clamp to valid range)
i_min = max(0, int((minx - offset_x) / x_reso))
i_max = min(self.grid_map.shape[0] - 1, int((maxx - offset_x) / x_reso))
j_min = max(0, int((miny - offset_y) / y_reso))
j_max = min(self.grid_map.shape[1] - 1, int((maxy - offset_y) / y_reso))
if i_min > i_max or j_min > j_max:
return False
# Check each occupied cell in bounding box region
collision_radius = max(x_reso, y_reso) * COLLISION_RADIUS_FACTOR
for i in range(i_min, i_max + 1):
for j in range(j_min, j_max + 1):
if self.grid_map[i, j] > OCCUPANCY_THRESHOLD:
# Get cell center in world coordinates
cell_x = offset_x + (i + CELL_CENTER_OFFSET) * x_reso
cell_y = offset_y + (j + CELL_CENTER_OFFSET) * y_reso
cell_center = Point(cell_x, cell_y)
# Check if geometry is within or exactly at the collision radius of the cell center
if geometry.distance(cell_center) <= collision_radius:
return True
return False
[docs]
def is_collision(self, geometry) -> bool:
"""Check collision against grid (if present) and map geometry."""
if self.grid_map is not None and self.check_grid_collision(geometry):
return True
candidate_indices = self.geometry_tree.query(geometry)
filtered_lines = [self.linestrings[i] for i in candidate_indices]
if not filtered_lines:
return False
filtered_multi_line = MultiLineString(filtered_lines)
return shapely.intersects(geometry, filtered_multi_line)