使用Arcpy自定义曲线线段替换为线段的实现

图片

一、曲线线段修复

        在GIS中,曲线一般有两种表示方式:一类是由多个直线段(多段线)组成的曲线,另一类是由贝塞尔曲线、圆弧和椭圆弧等数学曲线表示的曲线。

  1. 多段线(LineString): 这种曲线是由一系列相邻的直线段连接而成。在GIS中,多段线通常用于近似曲线或折线特征,如道路、河流等。多段线是由一系列节点(顶点)连接而成,可以通过这些节点来近似实际的曲线。

  2. 贝塞尔曲线、圆弧和椭圆弧: 这些曲线是由数学公式生成的光滑曲线,其路径由控制点、半径等参数决定。在GIS中,这些曲线通常用于绘制更为光滑和精确的曲线,如图形设计、CAD制图、地图标注等。它们可以更准确地模拟自然曲线,提供更高的图形质量。

在GIS中,多段线和贝塞尔曲线、圆弧、椭圆弧等曲线都有各自的应用场景和优势。

1.1 多段线(LineString)的应用

  1. 折线地物表示: 多段线通常用于近似表示折线状的地物,如河流、道路等。由一系列直线段组成,适用于模拟具有直线特征的地物。

  2. 轨迹记录: 在移动物体轨迹的记录中,多段线可以用于表示物体在不同时间点之间的运动路径。

  3. 地图标注: 多段线可以用于绘制地图上的标注线,例如连接地图元素之间的注释线。

1.2 贝塞尔曲线、圆弧和椭圆弧的应用

  1. 曲线地物表示: 贝塞尔曲线、圆弧和椭圆弧等数学曲线可以用于更准确地表示自然界中的光滑曲线地物,如环岛、山脊的轮廓等。

  2. 图形设计: 在GIS中,用于图形设计的曲线类型可以提供更高的图形质量,用于绘制符号、标签等,使地图更具艺术性。

  3. CAD绘图: 在CAD(计算机辅助设计)软件中,贝塞尔曲线、圆弧和椭圆弧常用于精确绘制建筑、工程图纸等。

1.3 将曲线增密为多段线的原因

  1. 数据存储和传输: 曲线的数学表示可能需要更多的数据存储和传输资源,因为曲线的定义通常需要更多的参数以准确描述其形状。这可能包括曲线的方程、曲线上的控制点等。与之相比,直线或多段线的数学表示可能更简单,只需存储端点坐标即可。

  2. 分析和处理效率: 曲线的分析和处理可能相对复杂,而多段线的分析通常更为高效。在某些GIS分析任务中,将曲线增密为多段线可能更容易实现和处理。

  3. 符号化和显示: 在某些情况下,曲线的图形表示可能无法很好地适应显示设备或符号化需求。增密为多段线可以更好地适应不同的显示要求。

二、如何将曲线线段(贝塞尔、圆弧和椭圆弧)替换为线段?

2.1 使用ArcGIS解决

          使用ArcGIS提供的地理处理工具“增密”,通过指定“增密方法”为“偏移”,实现对曲线线段替换为线段。工具直接修改源要素图层,没有其他输出。

图片

    增密工具参数示意如下:

图片

        实际上,因为没有输出,这种“做好事不留名”的做法,在一些场景下是不友好的。比如,我不希望直接在源数据上进行更改。

从数据质检需求和需要保持源数据不变的原则下,当我们需要对曲线线段(贝塞尔、圆弧和椭圆弧)进行查找和修复时,需要单独输出曲线线段的记录和最后修复后的结果。

2.2 使用Arcpy自定义曲线线段替换为线段的实现

        “曲线线段修复”工具将曲线线段(贝塞尔、圆弧和椭圆弧)替换为线段。

图片

工具概述

        (1)将输入要素中存在曲线线段(贝塞尔、圆弧和椭圆弧)的要素输出;

        (2)修复输入要素中的曲线线段(贝塞尔、圆弧和椭圆弧),并输出到指定目录。

        (3)对线要素(多部件)的曲线部分进行增密处理,不破坏原有要素类的其他属性,如字段属性,属性域关系等。

        (4)默认“最大偏移偏差(输出线段与原始线段之间的最大距离)”为0.1米;

功能流程

        工具执行界面如下图所示:

图片

        工具运行结果展示如下图:

图片

工具参数介绍如下:

图片

工具输出:

        在“输出路径”下,输出与输入同名的要素类,并将被转换为折线的曲线部分输出,图层名为“curve_repair”。

        所有输出的要素类都存放在输出目录中的scratch.gdb中。若scratch.gdb不存在,则自动创建,若已存在,不会覆盖其中已有的要素。

注意事项:

        无。

图片

        实现代码如下:

import math
import os
from collections import defaultdict
from itertools import groupby
from operator import itemgetter
import arcpy
from arcgis.geometry import Geometry
from pyxmy.feature.database import Row, create_feature
from pyxmy.feature.field import TextField, FieldTable


arcpy.env.overwriteOutput = True


# 距离长度转测地线长度
def plane_to_geodesic(p_length, v_angle=0, lat=0):
    # cell 分辨率大小 lat 要素维度
    # 地球半径 r
    r = 6378137

    # 地球周长 R
    R = 2 * math.pi * r

    # 赤道上一度所对应的地表距离    degToMeter = math.pi * r / 180.0
    # 在经线上,变化1纬度是多少距离(对应直角坐标系y轴)
    # 北半球的纬度是0° - 90°,北半球的弧长是 R / 4 ,纬度变化1°,对应的距离 L = R / (90 * 4)
    L = R / (90 * 4)
    y = p_length / L

    # 在纬线上,变化1经度是多少距离(对应直角坐标系x轴)
    # 只要能够得到指定纬度θ所切的圆的半径 r' ,就可以得到在这个纬度θ上所切的圆的周长:R' = 2πr' ,纬度θ上变化 1经度的距离 L = R' / 360
    # 在纬度θ所切圆的周长 R' = 2πr' =  R * cosθ

    R_lat = R * math.cos(math.pi / 180 * lat)
    # 纬度θ的纬线上变化 1经度的距离 L = R' / 360

    degToMeter = R_lat / 360.0
    x = p_length / degToMeter

    degree_angle = (v_angle * math.pi) / 180

    g_length = math.sqrt((x * math.cos(degree_angle)) ** 2 + (y * math.sin(degree_angle)) ** 2)

    return g_length


# 指定要素,创建空要素类模板
def create_fc_template(fc_path, out_path):
    arcpy.env.scratchWorkspace = out_path
    gdb_path = arcpy.env.scratchGDB

    desc = arcpy.Describe(fc_path)

    catalog_path = desc.catalogPath

    name = desc.name.split(".")[0]
    spatial_ref = desc.spatialReference
    geometry_type = desc.shapeType

    has_m = "DISABLED"
    has_z = "DISABLED"

    arcpy.CreateFeatureclass_management(gdb_path, name, geometry_type, catalog_path,
                                        has_m, has_z, spatial_ref)

    return os.path.join(gdb_path, name)


# 解析曲线坐标,将曲线段的起始坐标索引找出,用于后续曲线增密后替换
def parse_curve(curve_coords):
    rs = defaultdict(list)
    curvePaths = curve_coords['curvePaths']
    for n, part in enumerate(curvePaths):
        for i, p in enumerate(part):
            if isinstance(p, dict):
                rs[n].append(i)

    for key in rs:
        values = rs[key]
        groups = []
        for k, g in groupby(enumerate(values), lambda i_x: i_x[0] - i_x[1]):
            groups.append(list(map(itemgetter(1), g)))
        rs[key] = groups
    return rs


# 计算两个坐标值之间的距离
def distance(p1, p2):
    x1, y1 = p1
    x2, y2 = p2
    return (x2 - x1) ** 2 + (y2 - y1) ** 2


# 找出p在ls中,与ls中最接近的点位
def min_index(p, ls):
    distances = [distance(p, point) for point in ls]
    min_index = distances.index(min(distances))
    return min_index


def repair_curve(fc_polyline, out_path):
    # 遍历每条线,并查询空间索引以获取在指定距离范围内的点
    new_fc = create_fc_template(fc_polyline, out_path)
    rows = arcpy.InsertCursor(new_fc)
    desc = arcpy.Describe(fc_polyline)
    OID = desc.OIDFieldName
    spatial_ref = desc.spatialReference
    cursor = arcpy.UpdateCursor(fc_polyline)
    row = cursor.next()

    spa_type = spatial_ref.type

    if spa_type == "Geographic":
        g_radius = plane_to_geodesic(1)
    else:
        g_radius = 1

    records = []
    arcpy.env.scratchWorkspace = out_path
    gdb_path = arcpy.env.scratchGDB
    while row:
        geo_row = row.getValue('SHAPE')
        line_id = row.getValue(OID)
        if geo_row.hasCurves:  # 几何具有曲线段
            curve_coords = Geometry(geo_row)
            rs = parse_curve(curve_coords)
            # 可以沿线要素添加折点,将曲线线段(贝塞尔、圆弧和椭圆弧)替换为线段
            densify_row = geo_row.densify('DISTANCE', distance=g_radius, deviation=g_radius/10)
            densify_curve_coords = Geometry(densify_row)
            # 只对曲线线段进行增密处理,即替换为线段
            for part, values in rs.items():  # part为部件索引,values为部件内曲线线段点连续索引集合
                part_coords = densify_curve_coords['paths'][part]
                curve_part_coords = curve_coords['curvePaths'][part]
                current_index = 0
                for value in values:
                    start_i = value[0] + current_index
                    end_i = value[-1] + current_index
                    try:
                        if start_i - 1 >= 0:
                            start_p = curve_part_coords[start_i - 1]
                        else:
                            start_p = curve_part_coords[start_i]
                    except IndexError:
                        start_p = curve_part_coords[start_i]

                    try:
                        if end_i + 1 < len(Geometry(geo_row).get_part(part)):
                            end_p = curve_part_coords[end_i + 1]
                        else:
                            end_p = curve_part_coords[end_i]
                    except IndexError:
                        end_p = curve_part_coords[end_i]

                    # 将曲线线段(贝塞尔、圆弧和椭圆弧)替换为线段
                    rp_start_i = curve_part_coords.index(start_p)
                    rp_end_i = curve_part_coords.index(end_p)
                    if isinstance(start_p, dict):
                        first_key = next(iter(start_p))
                        start_p = start_p[first_key][0]
                    if isinstance(end_p, dict):
                        first_key = next(iter(end_p))
                        end_p = end_p[first_key][0]

                    new_start_i = min_index(start_p, part_coords)
                    new_end_i = min_index(end_p, part_coords)
                    # 曲线增密部分的坐标串
                    replace_curve_coords = part_coords[new_start_i:new_end_i + 1]

                    curve_coords['curvePaths'][part][rp_start_i:rp_end_i+1] = replace_curve_coords
                    # 将曲线替换为圆弧的部分输出
                    f_info = arcpy.Polyline(arcpy.Array([arcpy.Point(*coords) for coords in replace_curve_coords]), spatial_reference=spatial_ref)
                    f_row = Row([line_id], 'POLYLINE', geometry=f_info, attr_name=['IN_FID'])
                    records.append(f_row)
                    current_index += (new_end_i - new_start_i) - (rp_end_i - rp_start_i)

            # 构建新的线段
            for feature in curve_coords['curvePaths']:
                new_part = arcpy.Polyline(
                    arcpy.Array([arcpy.Point(*coords) for coords in feature]), spatial_reference=spatial_ref)

                row.setValue("SHAPE", new_part)
                rows.insertRow(row)

        else:
            rows.insertRow(row)

        row = cursor.next()

    del row
    del rows

    tf = TextField(name='IN_FID', alias='输入ID', length=10, null_able=False)
    out_fc = os.path.join(gdb_path, "{}".format("curve_repair"))
    cursor = create_feature(feature_name=out_fc, field=FieldTable(tf), geometry_type='POLYLINE', srs=spatial_ref)
    cursor.insert(records)


if __name__ == "__main__":
    fc_line = r"E:\6.测试代码\Default.gdb\aa"
    out_path = r'E:\6.测试代码'
    repair_curve(fc_line, out_path)

图片

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

craybb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值