读取shp文件中的地块数据后求地块(复杂多边形)最大内切圆圆心坐标及半径的Voronoi图算法

        地块是一些超复杂的多边形,其中一些多边形是简单多边形,有些多边形是包含内部空洞的复杂多边形,而有些多边形是两三个复杂多边形(可能包含内部空洞)的“复合体”。

具有内部空洞的复杂多边形(边界高亮)

两三个复杂多边形(可能包含内部空洞)的“复合体”(边界高亮)

        读取地块数据并使用Voronoi图方法顺序计算shapefile文件中每一个复杂多边形最大内切圆的圆心坐标和半径,并添加新字段将圆心坐标和半径写入shapefile文件中

1.导入相关库

# 导入NumPy库,用于数值计算
import numpy as np
# 导入Polygon、MultiPolygon和Point类,用于处理几何对象
from shapely.geometry import Polygon, MultiPolygon, Point
# 导入Voronoi类,用于生成Voronoi图
from scipy.spatial import Voronoi
# 导入geopandas库,用于处理地理空间数据
import geopandas as gpd
# 导入matplotlib.pyplot用于绘图
import matplotlib.pyplot as plt
# 从pyproj模块中导入Transformer类,Transformer类则用于坐标变换
from pyproj import Transformer

2.实现基于多边形特性的自适应采样策略

       包括对于没有空洞的多边形采用较少的采样点,而对于有空洞的多边形使用更多的采样点,同时根据边界的长度调整采样点的密度

def sample_points_on_edge(geometry, num_points=300):
    # 检查几何对象是否有效,如果无效则尝试修复
    if not geometry.is_valid:
        geometry = geometry.buffer(0)
    sampled_points = []  # 初始化采样点列表
    # 判断几何对象是否为多边形
    if isinstance(geometry, Polygon):
        # 获取多边形的外边界和所有内边界(空洞)
        exteriors = [geometry.exterior] + list(geometry.interiors)
        # 根据是否有内边界(空洞)调整采样点总数
        num_points_adjusted = num_points if len(geometry.interiors) > 0 else int(num_points * 0.75)
        # 计算所有边界的总长度
        total_length = sum([ext.length for ext in exteriors])
        # 对每个边界进行处理
        for ext in exteriors:
            length = ext.length  # 计算边界的长度
            # 根据边界长度和调整后的采样点数计算此边界的采样点数
            # 根据每个边界(外边界和内边界)的长度比例分配采样点数,以实现在边界较长的部分采用更密集的采样,在边界较短的部分采用较稀疏的采样
            edge_num_points = int(num_points_adjusted * (length / total_length))
            # 在边界上根据计算出的采样间隔插值采样点
            distances = np.linspace(0, length, edge_num_points)
            sampled_points += [ext.interpolate(distance) for distance in distances]
    return sampled_points

3.计算多边形的最大内切圆的圆心坐标

def find_largest_inscribed_circle_center(polygon, num_points=300):
    # 对多边形的外边界和所有内边界(空洞)上的点进行采样
    sampled_points = sample_points_on_edge(polygon, num_points)
    # 移除采样点中的重复点,保留唯一点集
    points = np.unique(np.array([[point.x, point.y] for point in sampled_points]), axis=0)
    
    # 打印点集,用于调试(看看具体是哪些点)
    # print(points)

    # 绘制多边形和采样点(可视化出来观察看看采样点是密集还是稀疏)
    plt.figure(figsize=(8, 8))
    for exterior in [polygon.exterior] + list(polygon.interiors):
        plt.plot(*exterior.xy, color='blue')  # 绘制多边形边界
    plt.scatter(points[:, 0], points[:, 1], color='red')  # 绘制采样点

    # 确保点集中有足够的唯一点来构建Voronoi图(至少需要3个不同的点)
    if len(points) < 3:
        raise ValueError("Not enough unique points to construct a Voronoi diagram.")

    # 使用点集生成Voronoi图
    vor = Voronoi(points)

    # 绘制Voronoi图
    voronoi_plot_2d(vor, show_vertices=False, line_colors='orange', line_width=2, point_size=2)
    plt.title("Polygon and Sampled Points with Voronoi Diagram")
    
    # 过滤掉位于多边形外部和内部空洞中的Voronoi顶点
    valid_vertices = [Point(vertex) for vertex in vor.vertices if polygon.contains(Point(vertex))]

    # 初始化最大半径和对应的圆心变量
    largest_radius = 0
    largest_center = None
    # 遍历所有有效的Voronoi顶点
    for vertex in valid_vertices:
        # 计算顶点到多边形边界的最短距离
        distance = polygon.boundary.distance(vertex)
        # 更新最大半径和对应的圆心
        if distance > largest_radius:
            largest_radius = distance
            largest_center = vertex

    return largest_center, largest_radius

4.定义坐标转换函数

        从CGCS2000_3_Degree_GK_CM_108E转换为WGS84。如果自己不需要可以注释或者删掉

def transform_to_wgs84(x, y):
    # 初始化转换器,从CGCS2000(EPSG:4545)到WGS84
    transformer = Transformer.from_crs("epsg:4545", "epsg:4326", always_xy=True)
    # 执行转换
    x2, y2 = transformer.transform(x, y)
    return x2, y2

5.处理shp文件并更新最大内切圆的圆心坐标

def process_shp_file(shp_path, updated_shp_path):
    # 使用gpd.read_file(shp_path)读取Shapefile文件并将其转换为GeoDataFrame对象,这使得后续对地理数据的处理更加方便
    gdf = gpd.read_file(shp_path)
    # 在这里我只是想统计shp文件中没有处理好的地块(即复合地块)的数量以及记录和输出其ID
    multi_polygon_count = 0  # 记录MultiPolygon的数量
    multi_polygon_ids = []  # 记录MultiPolygon的ID
    centers = []  # 初始化圆心坐标列表
    radii = []  # 初始化半径列表
    # 遍历shp文件中的每个几何对象
    for index, geom in enumerate(gdf.geometry):
        if isinstance(geom, MultiPolygon):
            multi_polygon_count += 1  # 更新MultiPolygon的数量
            multi_polygon_ids.append(index)  # 记录MultiPolygon的ID
            # 初始化临时存储最大圆的变量
            max_radius = 0
            max_center = None
            # 对MultiPolygon中的每个Polygon进行处理
            for polygon in geom.geoms:
                # 根据是否有空洞选择采样点数量
                num_points = 300 if len(polygon.interiors) > 0 else int(300 * 0.75)
                center, radius = find_largest_inscribed_circle_center(polygon, num_points)
                if radius > max_radius:
                    max_radius = radius
                    max_center = center
            # 在找到最大内切圆的圆心后,立即转换坐标(可将其写成一个函数调用)
            if max_center is not None:
                # 转换坐标
                center_x_transformed, center_y_transformed = transform_to_wgs84(max_center.x, max_center.y)
                # 更新圆心为转换后的坐标
                max_center = Point(center_x_transformed, center_y_transformed)
            centers.append(max_center)
            radii.append(max_radius)
        elif isinstance(geom, Polygon):
            # 如果多边形有内部空洞,则采样点数量为300,否则为225,以适应多边形的特性
            num_points = 300 if len(geom.interiors) > 0 else int(300 * 0.75)
            # num_points = 300 if len(geom.interiors) > 0 else 225
            center, radius = find_largest_inscribed_circle_center(geom, num_points)
            # 在找到最大内切圆的圆心后,立即转换坐标
            if center is not None:
                # 转换坐标
                center_x_transformed, center_y_transformed = transform_to_wgs84(center.x, center.y)
                # 更新圆心为转换后的坐标
                center = Point(center_x_transformed, center_y_transformed)
            centers.append(center)
            radii.append(radius)
        else:
            # 对于非Polygon/MultiPolygon类型,简单地为这些几何对象分配了空的圆心和0半径
            centers.append(None)
            radii.append(0)

    # 将计算得到的圆心坐标和半径添加到GeoDataFrame的新字段中,确保新字段与GeoDataFrame的行数匹配
    gdf['center_x'] = [center.x if center else None for center in centers]
    gdf['center_y'] = [center.y if center else None for center in centers]
    gdf['radius'] = radii

    # 使用gdf.to_file(updated_shp_path)将更新后的GeoDataFrame保存为新的Shapefile文件
    # 指定编码为UTF-8以正确处理中文(GBK完全不行,Big5有些中文不显示)
    gdf.to_file(updated_shp_path, encoding='UTF-8')
    # 返回multipolygon的数量和ID
    return multi_polygon_count, multi_polygon_ids

6.使用matplotlib绘制具有内部空洞的复杂多边形

        用于绘制具有外边界和内部空洞的多边形。此函数首先绘制多边形的外边界,然后遍历并绘制每个内部空洞的边界。内部空洞使用虚线样式以便于区分

def draw_polygon_with_holes(geom, ax):
    # 通过geom.exterior.xy获取外边界的x和y坐标,使用ax.plot(x, y, 'b')在给定的轴(ax)上绘制外边界
    x, y = geom.exterior.xy
    # ax.fill(x, y, alpha=0.5, fc='lightblue', ec='black')
    ax.plot(x, y, 'b')
    # 通过遍历geom.interiors获取每个内部空洞的坐标,并使用ax.plot(x, y, 'b', linestyle='--')以虚线样式绘制内部空洞的边界
    for interior in geom.interiors:
        x, y = interior.xy
        ax.plot(x, y, 'b', linestyle='--')  # 使用虚线样式绘制内部空洞的边界

7.绘制最大内切圆,并在matplotlib图形界面中标记圆心

        使用plt.Circle创建一个圆形对象,其中心位于参数center指定的位置,半径等于参数radius指定的值。然后,使用ax.add_artist(circle)将这个圆形添加到图形界面的指定轴上

def draw_largest_inscribed_circle(ax, center, radius):
    circle = plt.Circle((center.x, center.y), radius, color='r', fill=False)
    ax.add_artist(circle)
    # 在圆心位置绘制一个红色的圆点
    ax.plot(center.x, center.y, 'ro')  # 红色圆点表示圆心

8.处理和绘制GeoDataFrame中指定范围内的几何对象

        在这里是想绘制shp文件中指定范围内的一些复杂多边形,将其可视化出来看看结果(最大内切圆和圆心)是否精确

def process_and_draw_polygons(gdf):
    # 通过enumerate(gdf.geometry[29:40])遍历GeoDataFrame中的第30至40个几何对象(索引从0开始)
    for index, geom in enumerate(gdf.geometry[29:40]):  # 仅处理第30至40个几何对象
        fig, ax = plt.subplots()
        # 对于每个几何对象,根据其类型(MultiPolygon或Polygon),使用draw_polygon_with_holes函数绘制多边形的外边界和内部空洞,
        # 然后使用find_largest_inscribed_circle_center函数计算最大内切圆的圆心和半径,并使用draw_largest_inscribed_circle函数进行绘制
        if isinstance(geom, MultiPolygon):
            for polygon in geom.geoms:
                draw_polygon_with_holes(polygon, ax)
                center, radius = find_largest_inscribed_circle_center(polygon)
                draw_largest_inscribed_circle(ax, center, radius)
        elif isinstance(geom, Polygon):
            draw_polygon_with_holes(geom, ax)
            center, radius = find_largest_inscribed_circle_center(geom)
            draw_largest_inscribed_circle(ax, center, radius)
        # 为每个几何对象创建的图形设置等比例的视图,并设置标题以表示当前处理的多边形序号和其最大内切圆。最后,使用plt.show()显示绘制的图形
        ax.set_aspect('equal')
        plt.title(f'Polygon {index + 10} with Largest Inscribed Circle')
        plt.show()

9.调用示例,查看效果

# 调用示例,路径需要根据实际情况进行替换
shp_path = 'shp/cut.shp'  # 输入shp文件的路径
updated_shp_path = 'new_shp/cut.shp'  # 输出更新后shp文件的路径
gdf = gpd.read_file(shp_path)
multi_polygon_count, multi_polygon_ids = process_shp_file(shp_path, updated_shp_path)
process_and_draw_polygons(gdf)

# 输出MultiPolygon的数量和对应的ID
print("MultiPolygon count:", multi_polygon_count)
print("MultiPolygon IDs:", multi_polygon_ids)

        因为MultiPolygon的存在,代码复杂了许多。同时,在某些地方在处理完之后需要可视化出来观察效果,不得已增加了一些代码。整个代码运行起来时没有问题的,下面是运行结果:

(这里中间的多边形的内切圆因为圆心画得太大所有看着不像)

        代码有点多,也有点复杂,可能有不足或错误的地方。如果有什么问题可以在评论区留言

以下是使用 arcpy 将 parcels.txt 地块信息和多边形写入 ShapeFile 文件的代码: ```python import arcpy # 设置工作空间和输出 ShapeFile 文件名 arcpy.env.workspace = r"C:\data" output_shapefile = "parcels.shp" # 创建一个空的 ShapeFile 文件 arcpy.CreateFeatureclass_management(arcpy.env.workspace, output_shapefile, "POLYGON") # 添加字段:地块 ID 和地块面积 arcpy.AddField_management(output_shapefile, "parcel_id", "LONG") arcpy.AddField_management(output_shapefile, "area", "DOUBLE") # 打开 parcels.txt 文件并遍历每一行 with open("parcels.txt", "r") as f: # 读取第一行,即表头,跳过 f.readline() # 遍历每一行 for line in f: # 分割每一行,并将信息存储到变量 fields = line.strip().split(",") parcel_id = int(fields[0]) area = float(fields[1]) # 从第三个元素开始获取所有坐标点 coords = [float(x) for x in fields[2:]] # 将坐标点转换为 arcpy.Point 对象 points = [] for i in range(0, len(coords), 2): points.append(arcpy.Point(coords[i], coords[i+1])) # 创建一个多边形对象,并将其添加到 ShapeFile 文件 polygon = arcpy.Polygon(arcpy.Array(points)) cursor = arcpy.da.InsertCursor(output_shapefile, ["SHAPE@", "parcel_id", "area"]) cursor.insertRow([polygon, parcel_id, area]) del cursor ``` 这个代码将创建一个名为 "parcels.shp" 的 ShapeFile 文件,并从 "parcels.txt" 文件读取地块信息和多边形坐标点。每个多边形对象都将添加到 ShapeFile 文件,并与其相应的地块 ID 和面积一起写入。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值