You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以向量化方式在多边形内生成随机位置与轨迹

多边形内Numpy向量化生成随机位置与轨迹

问题背景

已实现正方形边界下的向量化随机轨迹生成(代码如下),现需适配自定义多边形边界(示例多边形坐标:[(100, 100), (80, 130), (90, 130), (90, 140), (70, 140), (150, 200), (120, 150), (100, 100)]),要求全程采用Numpy向量化操作保证计算效率。

原正方形实现代码:

import numpy as np
# coordinates of square
square_coords = [-255, -255, 256, 256] # [xMin, yMin, xMax, yMax]
# convert to np.array
square_boundaries = np.array([(square_coords[0], square_coords[2]), (square_coords[1], square_coords[3])])

# specify speeds with corresponding probabilities of each speed
ue_speed = [3, 4, 8, 25] 
ue_speed_prob = [0.4, 0.2, 0.3, 0.1] # should sum up to 1

steps = 50
time_interval = 10
nwalks = 1

# calcultation without boundaries
v = np.random.choice(ue_speed, size=steps, p=ue_speed_prob)
R = np.expand_dims((v * time_interval), axis=-1)
theta = 2 * np.pi * np.random.rand(nwalks, steps)
xy = np.dstack((np.cos(theta), np.sin(theta))) * R
trajectory_no_boundaries = np.hstack((np.zeros((nwalks, 1, 2)), np.cumsum(xy, axis=1)))

# generete random placement inside of boundaries
start = (np.random.randint(square_coords[0], square_coords[2]), np.random.randint(square_coords[1], square_coords[3]))
# generate random trajectory inside of boundaries
size = np.diff(square_boundaries, axis=1).ravel()
trajectory = np.abs((trajectory_no_boundaries[0] + start - square_boundaries[:, 0] + size) % (2 * size) - size) + square_boundaries[:, 0]

解决方案

针对多边形场景,核心分为随机起点生成和带边界约束的轨迹修正两部分,尽量采用Numpy向量化操作减少循环开销,结合Shapely做几何辅助判断。

1. 多边形内随机起点的向量化生成

直接遍历判断点是否在多边形内效率低,先通过多边形包围盒过滤大部分点,再用射线法向量化判断剩余点是否在多边形内:

import numpy as np
from shapely.geometry import Polygon, Point

# 定义目标多边形
polygon_coords = [(100, 100), (80, 130), (90, 130), (90, 140), (70, 140), (150, 200), (120, 150), (100, 100)]
polygon = Polygon(polygon_coords)
poly_np = np.array(polygon_coords[:-1])  # 转为Numpy数组,去掉闭合点

# 向量化射线法判断点是否在多边形内
def points_in_polygon(points, poly):
    x, y = points[:, 0], points[:, 1]
    n = len(poly)
    inside = np.zeros(len(points), dtype=bool)
    
    for i in range(n):
        j = (i + 1) % n
        xi, yi = poly[i]
        xj, yj = poly[j]
        
        # 判断点是否在边的y范围内
        cond1 = (yi > y) != (yj > y)
        # 计算射线与边的交点x坐标
        x_intersect = ((y - yi) * (xj - xi)) / (yj - yi) + xi
        cond2 = x < x_intersect
        inside[cond1 & cond2] = ~inside[cond1 & cond2]
    
    return inside

# 生成随机起点:先在包围盒内生成大量点,再筛选出多边形内的点
min_x, min_y, max_x, max_y = polygon.bounds
# 多生成候选点保证足够的有效起点
candidate_points = np.random.uniform([min_x, min_y], [max_x, max_y], size=(nwalks * 5, 2))
mask = points_in_polygon(candidate_points, poly_np)
start_points = candidate_points[mask][:nwalks]

2. 带多边形边界约束的轨迹生成

多边形无法像正方形那样用镜像反射公式直接处理,采用逐帧修正策略:先生成无约束轨迹,再对每一步超出多边形的点,计算与边界的碰撞点并修正运动方向,尽量用Numpy批量处理:

from shapely.geometry import LineString

# 原无约束轨迹生成逻辑(复用原有代码)
ue_speed = [3, 4, 8, 25] 
ue_speed_prob = [0.4, 0.2, 0.3, 0.1]
steps = 50
time_interval = 10
nwalks = 1

v = np.random.choice(ue_speed, size=(nwalks, steps), p=ue_speed_prob)
R = v * time_interval
theta = 2 * np.pi * np.random.rand(nwalks, steps)
xy = np.stack([np.cos(theta) * R, np.sin(theta) * R], axis=-1)
# 基于随机起点生成无约束轨迹
trajectory_no_boundaries = start_points[:, np.newaxis, :] + np.cumsum(xy, axis=1)
# 插入初始起点
trajectory_no_boundaries = np.concatenate([start_points[:, np.newaxis, :], trajectory_no_boundaries], axis=1)[:, :-1, :]

# 轨迹边界修正:对每一步超出多边形的点进行碰撞修正
trajectory = trajectory_no_boundaries.copy()
for step in range(1, steps+1):
    # 获取当前步所有点
    current_points = trajectory[:, step, :]
    # 判断是否在多边形内
    in_poly = np.array([polygon.contains(Point(p)) for p in current_points])
    # 处理超出边界的点
    out_idx = np.where(~in_poly)[0]
    if len(out_idx) == 0:
        continue
    
    for idx in out_idx:
        # 获取上一步位置和当前步位置
        prev_p = trajectory[idx, step-1, :]
        curr_p = current_points[idx]
        # 计算线段与多边形的交点(碰撞点)
        line = LineString([prev_p, curr_p])
        intersection = polygon.boundary.intersection(line)
        if intersection.is_empty:
            # 无交点时回退到上一步,重新生成方向
            theta[idx, step-1] = 2 * np.pi * np.random.rand()
            new_xy = np.array([np.cos(theta[idx, step-1]), np.sin(theta[idx, step-1])]) * R[idx, step-1]
            trajectory[idx, step, :] = trajectory[idx, step-1, :] + new_xy
        else:
            # 用碰撞点作为当前步位置,修正方向(镜像反射)
            intersect_p = np.array([intersection.x, intersection.y])
            trajectory[idx, step, :] = intersect_p
            # 计算反射方向:基于边界法向量
            edge = get_nearest_edge(intersect_p, polygon.boundary)
            edge_vec = np.array([edge.coords[1][0]-edge.coords[0][0], edge.coords[1][1]-edge.coords[0][1]])
            norm_vec = np.array([-edge_vec[1], edge_vec[0]])  # 法向量
            norm_vec = norm_vec / np.linalg.norm(norm_vec)
            dir_vec = curr_p - prev_p
            dir_vec = dir_vec / np.linalg.norm(dir_vec)
            # 反射向量公式
            reflect_vec = dir_vec - 2 * np.dot(dir_vec, norm_vec) * norm_vec
            # 更新下一步的方向
            if step < steps:
                theta[idx, step] = np.arctan2(reflect_vec[1], reflect_vec[0])

# 辅助函数:获取点到边界最近的边
def get_nearest_edge(point, boundary):
    min_dist = float('inf')
    nearest_edge = None
    for i in range(len(boundary.coords)-1):
        edge = LineString([boundary.coords[i], boundary.coords[i+1]])
        dist = edge.distance(Point(point))
        if dist < min_dist:
            min_dist = dist
            nearest_edge = edge
    return nearest_edge

说明

  • 随机起点生成采用包围盒预过滤+向量化射线法,比逐个调用Shapely.contains效率提升数倍;
  • 轨迹修正部分对超出边界的点单独处理,尽可能复用Numpy批量判断,平衡效率与实现复杂度;
  • 如果需要支持大量轨迹(nwalks很大),可以进一步优化碰撞修正的循环逻辑,比如用Numpy向量化处理边的距离计算。

内容的提问来源于stack exchange,提问作者illuminato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 03:35:20