如何使用 Python、Numpy 和 Shapely 生成落在多边形内的随机和非重复点?

How to generate random and non duplicate points that fall within a polygon using Python, Numpy, and Shapely?

我一直在使用 Python、Shapely、Numpy 和 Geopandas 生成点密度图。点密度地图点根据种族有不同的颜色,因此可以了解整体城市人口统计数据。

我一直在使用一些类似于函数 found in this answer 的代码来生成落在多边形内的点。该函数采用一个多边形和一个整数(不同种族的人数)并运行一个 while 循环以创建落在该多边形内的随机点。它为此使用 Numpy uniform

这是地理数据框的样子

这是我 运行 创建点的代码:

pts_per_person = 5
  epsg = 4326 
  seed = 10

  list_of_point_categories = []
  for field in ['white_pop','black_pop','hispanic_pop', 'asian_pop', 'amerindian_pop',  'other_race_pop',   'two_or_more_races_pop']:
    
    ps = gpd.GeoDataFrame(gen_points_in_gdf_polys(geometry = gdf['geometry'], values=gdf[field],
                                points_per_value = pts_per_person, seed=seed))
    ps['ethnicity'] = field
    ps['year'] = i

    list_of_point_categories.append(ps)

  all_points=gpd.GeoDataFrame(pd.concat(list_of_point_categories))

  all_points=all_points.reset_index(drop=True)

函数如下:

def gen_random_points_poly(poly, num_points, seed = None):
    """
    Returns a list of N randomly generated points within a polygon. 
    """
    
    min_x, min_y, max_x, max_y = poly.bounds
    points = []
    i=0
    
    while len(points) < num_points:
        s=RandomState(seed+i) if seed else RandomState(seed)
        random_point = Point([s.uniform(min_x, max_x), s.uniform(min_y, max_y)])
        if random_point.within(poly):
            points.append(random_point)
        i+=1
    return points


def gen_points_in_gdf_polys(geometry, values, points_per_value = None, seed = None):
    """
    Take a GeoSeries of Polygons along with a Series of values and returns randomly generated points within
    these polygons. Optionally takes a "points_per_value" integer which indicates the number of points that 
    should be generated for each 1 value.
    """
    if points_per_value:
        new_values = (values/points_per_value).astype(int)
    else:
        new_values = values

    
    new_values = new_values[new_values>0]
    #print(new_values.size)
    
    if(new_values.size > 0):
        g = gpd.GeoDataFrame(data = {'vals':new_values}, geometry = geometry)
        
        a = g.apply(lambda row: tuple(gen_random_points_poly(row['geometry'], row['vals'], seed)),1)
        b = gpd.GeoSeries(a.apply(pd.Series).stack(), crs = geometry.crs)
        b.name='geometry'
      
        return b

    

但我发现我最终得到了每个种族的多个重复点。纬度和经度值完全相同。

重复的点相互堆叠。我不得不将 s.uniform 行更改为 random_point = Point([s.uniform(min_x, max_x) + round(random.uniform(.0001, .001),10), s.uniform(min_y, max_y) + round(random.uniform(.0001, .001),10)]) 以使其真正随机。这具有更随机地分散点数的预期效果,没有重复。

但是这感觉有点不对劲,就像我没有正确使用 .uniform 一样。这是在多边形内创建随机点的正确方法吗?

问题似乎出在 gen_random_points_poly() 函数中 while 循环的第一行。这一行写着:

 s=RandomState(seed+i) if seed else RandomState(seed)

这会在循环的每次迭代中初始化随机数生成器。初始化取决于 seed 的值(固定的)和 i 的值(在循环的每次迭代中递增)。实际上,如果多次调用 gen_random_points_poly() 函数,那么每次生成的点序列都将完全相同。这就是为什么不同种族产生的点数集合是完全一样的。

如果您想获得可重现的结果,您可以在 gen_random_points_poly() 函数之外创建一次 RandomState 对象。或者,您可以在每次调用此函数时提供不同的种子。在后一种情况下,在 while 循环之前只创建一次 RandomState 对象比在每次迭代中重复创建它更有效。