【ArcMap】按属性表复制字段并上移一段距离

image

将属性表中 LXMC 为 名称2 的字段复制并上移20米,在py编辑器中插入以下代码:

# -*- coding: utf-8 -*-
import arcpy
import os
import sys

# 重新加载sys并设置默认编码
reload(sys)
sys.setdefaultencoding('utf-8')

# 设置环境
arcpy.env.overwriteOutput = True

try:
    # 获取当前地图文档
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = arcpy.mapping.ListDataFrames(mxd)[0]
    
    arcpy.AddMessage("脚本开始执行...")
    
    # 查找线图层
    target_layer = None
    for layer in arcpy.mapping.ListLayers(mxd):
        if layer.isFeatureLayer:
            desc = arcpy.Describe(layer)
            if desc.shapeType == "Polyline":
                target_layer = layer
                break
    
    if target_layer is None:
        arcpy.AddError("未找到线图层")
        sys.exit()
    
    arcpy.AddMessage("找到图层: " + target_layer.name)
    
    # 检查LXMC字段是否存在
    field_names = [field.name for field in arcpy.ListFields(target_layer)]
    if "LXMC" not in field_names:
        arcpy.AddError("图层中不存在 LXMC 字段")
        sys.exit()
    
    # 先选中LXMC为"名称2"的要素
    where_clause = "LXMC = '名称2'"
    arcpy.SelectLayerByAttribute_management(target_layer, "NEW_SELECTION", where_clause)
    
    # 检查是否有选中的要素
    desc = arcpy.Describe(target_layer)
    if not desc.FIDSet:
        arcpy.AddError("没有找到LXMC为'名称2'的要素")
        sys.exit()
    
    # 获取选中要素的数量
    result = arcpy.GetCount_management(target_layer)
    selected_count = int(result.getOutput(0))
    arcpy.AddMessage("选中的要素数量: " + str(selected_count))
    
    # 获取数据源路径
    data_source = desc.catalogPath
    arcpy.AddMessage("数据源: " + data_source)
    
    # 获取空间参考
    spatial_ref = desc.spatialReference
    arcpy.AddMessage("坐标系: " + spatial_ref.name)
    
    # 获取选中要素的OID
    selected_oids = []
    with arcpy.da.SearchCursor(target_layer, ["OID@"]) as cursor:
        for row in cursor:
            selected_oids.append(row[0])
    
    arcpy.AddMessage("选中的要素OID: " + str(selected_oids))
    
    # 创建查询条件
    oid_field = arcpy.Describe(target_layer).OIDFieldName
    where_clause = "{} IN ({})".format(arcpy.AddFieldDelimiters(data_source, oid_field), 
                                      ",".join(str(oid) for oid in selected_oids))
    
    # 获取字段列表
    field_names = []
    for field in arcpy.ListFields(target_layer):
        if field.type not in ["OID", "Geometry"]:
            field_names.append(field.name)
    
    arcpy.AddMessage("将复制的字段: " + str(field_names))
    
    # 确定移动距离 - 修改为20米
    if spatial_ref.type == "Geographic":
        y_offset = 0.00018  # 大约20米 (1度≈111公里,20米≈0.00018度)
    else:
        y_offset = 20.0  # 20米
    
    arcpy.AddMessage("向上移动距离: " + str(y_offset) + ("" if spatial_ref.type == "Geographic" else ""))
    
    # 开始编辑会话
    edit = arcpy.da.Editor(os.path.dirname(data_source))
    edit.startEditing(False, True)
    edit.startOperation()
    
    try:
        # 使用插入游标将移动后的要素添加到同一图层
        with arcpy.da.InsertCursor(data_source, ["SHAPE@"] + field_names) as insert_cursor:
            # 使用搜索游标读取选中的要素
            with arcpy.da.SearchCursor(data_source, ["SHAPE@"] + field_names, where_clause) as search_cursor:
                moved_count = 0
                for row in search_cursor:
                    shape = row[0]
                    attributes = row[1:]
                    
                    if shape:
                        # 创建一个新的数组来存储移动后的点
                        new_parts = arcpy.Array()
                        
                        # 遍历原始几何的每个部分
                        for part in shape:
                            new_part = arcpy.Array()
                            
                            # 遍历每个点
                            for point in part:
                                if point:
                                    # 创建新点,Y坐标增加偏移量
                                    new_point = arcpy.Point(point.X, point.Y + y_offset)
                                    new_part.add(new_point)
                            
                            new_parts.add(new_part)
                        
                        # 创建新的Polyline几何
                        moved_shape = arcpy.Polyline(new_parts, spatial_ref)
                        
                        # 插入移动后的要素到同一图层
                        insert_cursor.insertRow([moved_shape] + list(attributes))
                        moved_count += 1
                        arcpy.AddMessage("已复制并移动要素 " + str(moved_count))
        
        # 提交编辑
        edit.stopOperation()
        edit.stopEditing(True)
        
        arcpy.AddMessage("成功复制并移动 " + str(moved_count) + " 个要素到原始图层")
        
        # 刷新视图
        arcpy.RefreshActiveView()
        arcpy.RefreshTOC()
        
        # 清除选择集,显示所有要素
        target_layer.setSelectionSet("NEW", [])
        
        arcpy.AddMessage("操作完成! LXMC为'名称2'的要素已复制并向上移动20米")
        arcpy.AddMessage("原始要素保持不变,新要素已向上移动20米")
        arcpy.AddMessage("现在图层中有 " + str(selected_count + moved_count) + " 个要素")
        arcpy.AddMessage("请使用测量工具验证两条线之间的距离是否为20米")
        arcpy.AddMessage("注意:由于20米距离很小,可能需要放大查看")
        
    except Exception as e:
        # 中止编辑
        edit.stopOperation()
        edit.stopEditing(False)
        raise e

except Exception as e:
    arcpy.AddError("错误: " + str(e))
    import traceback
    error_msg = traceback.format_exc()
    arcpy.AddError(error_msg)

回车后:

image

 进阶版:适用GD

# -*- coding: utf-8 -*-
import arcpy
import os
import sys
import math

# 重新加载sys并设置默认编码
reload(sys)
sys.setdefaultencoding('utf-8')

# 设置环境
arcpy.env.overwriteOutput = True

def calculate_segment_offset_direction(start_point, end_point, distance=10):
    """
    计算单个线段的偏移方向
    """
    dx = end_point.X - start_point.X
    dy = end_point.Y - start_point.Y
    
    # 计算线段长度
    length = math.sqrt(dx*dx + dy*dy)
    if length == 0:
        return 0, distance
    
    # 归一化方向向量
    dx /= length
    dy /= length
    
    # 计算垂直向量(左侧偏移)
    offset_dx = -dy * distance
    offset_dy = dx * distance
    
    return offset_dx, offset_dy

def offset_polyline_segments(line_geometry, distance=10, spatial_ref=None):
    """
    对多段线进行逐段偏移,避免S型线相交
    """
    new_parts = arcpy.Array()
    
    for part in line_geometry:
        new_part = arcpy.Array()
        points = []
        
        # 收集所有点
        for point in part:
            if point:
                points.append((point.X, point.Y))
        
        if len(points) < 2:
            continue
            
        # 对每个线段计算偏移方向
        offset_points = []
        
        # 处理第一个点
        first_offset_dx, first_offset_dy = calculate_segment_offset_direction(
            arcpy.Point(points[0][0], points[0][1]),
            arcpy.Point(points[1][0], points[1][1]),
            distance
        )
        offset_points.append((
            points[0][0] + first_offset_dx,
            points[0][1] + first_offset_dy
        ))
        
        # 处理中间点 - 使用前后线段方向的平均值
        for i in range(1, len(points)-1):
            # 前一线段的方向
            prev_dx, prev_dy = calculate_segment_offset_direction(
                arcpy.Point(points[i-1][0], points[i-1][1]),
                arcpy.Point(points[i][0], points[i][1]),
                distance
            )
            
            # 后一线段的方向
            next_dx, next_dy = calculate_segment_offset_direction(
                arcpy.Point(points[i][0], points[i][1]),
                arcpy.Point(points[i+1][0], points[i+1][1]),
                distance
            )
            
            # 使用平均值来平滑过渡
            avg_dx = (prev_dx + next_dx) / 2
            avg_dy = (prev_dy + next_dy) / 2
            
            offset_points.append((
                points[i][0] + avg_dx,
                points[i][1] + avg_dy
            ))
        
        # 处理最后一个点
        last_offset_dx, last_offset_dy = calculate_segment_offset_direction(
            arcpy.Point(points[-2][0], points[-2][1]),
            arcpy.Point(points[-1][0], points[-1][1]),
            distance
        )
        offset_points.append((
            points[-1][0] + last_offset_dx,
            points[-1][1] + last_offset_dy
        ))
        
        # 创建新的线段
        for x, y in offset_points:
            new_part.add(arcpy.Point(x, y))
        
        new_parts.add(new_part)
    
    return arcpy.Polyline(new_parts, spatial_ref)

def check_and_adjust_offset(line_geometry, offset_line, original_endpoints, distance=10, spatial_ref=None):
    """
    检查偏移线是否与原始线相交,如果相交则调整
    """
    try:
        # 检查是否相交
        if line_geometry.crosses(offset_line) or line_geometry.overlaps(offset_line):
            arcpy.AddMessage("检测到偏移线与原始线相交,尝试反向偏移")
            
            # 尝试反向偏移
            return offset_polyline_segments(line_geometry, -distance, spatial_ref)
        else:
            return offset_line
    except:
        # 如果几何检查失败,使用距离检查
        try:
            if line_geometry.distanceTo(offset_line) < distance * 0.5:
                arcpy.AddMessage("偏移距离过小,尝试反向偏移")
                return offset_polyline_segments(line_geometry, -distance, spatial_ref)
            else:
                return offset_line
        except:
            return offset_line

def smooth_polyline(polyline, tolerance=0.1, spatial_ref=None):
    """
    对多段线进行平滑处理,减少尖锐转角
    """
    new_parts = arcpy.Array()
    
    for part in polyline:
        if part.count < 3:
            new_parts.add(part)
            continue
            
        new_part = arcpy.Array()
        points = []
        
        # 收集所有点
        for point in part:
            if point:
                points.append((point.X, point.Y))
        
        # 添加第一个点
        new_part.add(arcpy.Point(points[0][0], points[0][1]))
        
        # 对中间点进行平滑
        for i in range(1, len(points)-1):
            prev_x, prev_y = points[i-1]
            curr_x, curr_y = points[i]
            next_x, next_y = points[i+1]
            
            # 计算前后向量的角度
            vec1_angle = math.atan2(curr_y - prev_y, curr_x - prev_x)
            vec2_angle = math.atan2(next_y - curr_y, next_x - curr_x)
            
            # 如果角度变化太大,进行平滑
            angle_diff = abs(vec1_angle - vec2_angle)
            if angle_diff > tolerance:
                # 使用中点
                smooth_x = (prev_x + next_x) / 2
                smooth_y = (prev_y + next_y) / 2
                new_part.add(arcpy.Point(smooth_x, smooth_y))
            else:
                new_part.add(arcpy.Point(curr_x, curr_y))
        
        # 添加最后一个点
        new_part.add(arcpy.Point(points[-1][0], points[-1][1]))
        new_parts.add(new_part)
    
    return arcpy.Polyline(new_parts, spatial_ref)

def connect_endpoints_to_original(offset_line, original_endpoints, search_distance=30):
    """
    将偏移线的端点连接到原始线的对应端点
    """
    new_parts = arcpy.Array()
    
    for part in offset_line:
        new_part = arcpy.Array()
        points = []
        
        # 收集所有点
        for point in part:
            if point:
                points.append((point.X, point.Y))
        
        if len(points) < 2:
            new_parts.add(part)
            continue
        
        # 处理起点 - 连接到最近的原始起点
        start_x, start_y = points[0]
        closest_start = find_closest_original_endpoint(start_x, start_y, original_endpoints, search_distance)
        if closest_start:
            points[0] = closest_start
        
        # 处理终点 - 连接到最近的原始终点
        end_x, end_y = points[-1]
        closest_end = find_closest_original_endpoint(end_x, end_y, original_endpoints, search_distance)
        if closest_end:
            points[-1] = closest_end
        
        # 重新构建线段
        for x, y in points:
            new_part.add(arcpy.Point(x, y))
        
        new_parts.add(new_part)
    
    return arcpy.Polyline(new_parts, offset_line.spatialReference)

def find_closest_original_endpoint(point_x, point_y, original_endpoints, max_distance=30):
    """
    找到距离最近的原始端点
    """
    min_distance = float('inf')
    closest_point = None
    
    for orig_oid, endpoints in original_endpoints.items():
        for endpoint_type, coords in endpoints.items():
            orig_x, orig_y = coords
            distance = math.sqrt((point_x - orig_x)**2 + (point_y - orig_y)**2)
            
            if distance < min_distance and distance < max_distance:
                min_distance = distance
                closest_point = (orig_x, orig_y)
    
    return closest_point

try:
    # 获取当前地图文档
    mxd = arcpy.mapping.MapDocument("CURRENT")
    df = arcpy.mapping.ListDataFrames(mxd)[0]
    
    arcpy.AddMessage("开始处理复杂线形...")
    
    # 查找线图层
    target_layer = None
    for layer in arcpy.mapping.ListLayers(mxd):
        if layer.isFeatureLayer:
            desc = arcpy.Describe(layer)
            if desc.shapeType == "Polyline":
                target_layer = layer
                break
    
    if target_layer is None:
        arcpy.AddError("未找到线图层")
        sys.exit()
    
    arcpy.AddMessage("找到图层: " + target_layer.name)
    
    # 检查字段是否存在
    field_names = [field.name for field in arcpy.ListFields(target_layer)]
    if "JSDJ" not in field_names:
        arcpy.AddError("图层中不存在 JSDJ 字段")
        sys.exit()
    
    if "XCFX" not in field_names:
        arcpy.AddError("图层中不存在 XCFX 字段")
        sys.exit()
    
    # 选中JSDJ为"一级公路"且XCFX为"A"的要素(上行线)
    where_clause = "JSDJ = '一级公路' AND XCFX = 'A'"
    arcpy.SelectLayerByAttribute_management(target_layer, "NEW_SELECTION", where_clause)
    
    # 检查是否有选中的要素
    desc = arcpy.Describe(target_layer)
    if not desc.FIDSet:
        arcpy.AddError("没有找到JSDJ为'一级公路'且XCFX为'A'的要素")
        sys.exit()
    
    # 获取选中要素的数量
    result = arcpy.GetCount_management(target_layer)
    selected_count = int(result.getOutput(0))
    arcpy.AddMessage("选中的上行线要素数量: " + str(selected_count))
    
    # 获取数据源路径
    data_source = desc.catalogPath
    arcpy.AddMessage("数据源: " + data_source)
    
    # 获取空间参考
    spatial_ref = desc.spatialReference
    arcpy.AddMessage("坐标系: " + spatial_ref.name)
    
    # 获取选中要素的OID
    selected_oids = []
    with arcpy.da.SearchCursor(target_layer, ["OID@"]) as cursor:
        for row in cursor:
            selected_oids.append(row[0])
    
    # 创建查询条件
    oid_field = arcpy.Describe(target_layer).OIDFieldName
    where_clause = "{} IN ({})".format(arcpy.AddFieldDelimiters(data_source, oid_field), 
                                      ",".join(str(oid) for oid in selected_oids))
    
    # 获取字段列表
    copy_field_names = []
    for field in arcpy.ListFields(target_layer):
        if field.type not in ["OID", "Geometry"]:
            copy_field_names.append(field.name)
    
    # 确定移动距离
    if spatial_ref.type == "Geographic":
        move_distance = 0.00009  # 大约10米
    else:
        move_distance = 10.0  # 10米
    
    arcpy.AddMessage("偏移距离: " + str(move_distance) + ("" if spatial_ref.type == "Geographic" else ""))
    
    # 开始编辑会话
    edit = arcpy.da.Editor(os.path.dirname(data_source))
    edit.startEditing(False, True)
    edit.startOperation()
    
    try:
        # 存储原始要素的起点和终点
        original_endpoints = {}
        with arcpy.da.SearchCursor(data_source, ["OID@", "SHAPE@"], where_clause) as cursor:
            for row in cursor:
                oid = row[0]
                shape = row[1]
                if shape:
                    first_point = shape.firstPoint
                    last_point = shape.lastPoint
                    original_endpoints[oid] = {
                        'first': (first_point.X, first_point.Y),
                        'last': (last_point.X, last_point.Y)
                    }
        
        # 使用插入游标将偏移后的要素添加到同一图层
        new_features_oids = []
        with arcpy.da.InsertCursor(data_source, ["SHAPE@"] + copy_field_names) as insert_cursor:
            with arcpy.da.SearchCursor(data_source, ["OID@", "SHAPE@"] + copy_field_names, where_clause) as search_cursor:
                moved_count = 0
                for row in search_cursor:
                    oid = row[0]
                    shape = row[1]
                    attributes = row[2:]
                    
                    # 修改XCFX字段的值
                    modified_attributes = list(attributes)
                    if "XCFX" in copy_field_names:
                        xcfx_index = copy_field_names.index("XCFX")
                        modified_attributes[xcfx_index] = "B"
                    
                    if shape:
                        arcpy.AddMessage("处理要素 {}...".format(oid))
                        
                        # 方法1: 逐段偏移
                        offset_shape = offset_polyline_segments(shape, move_distance, spatial_ref)
                        
                        # 检查并调整偏移
                        offset_shape = check_and_adjust_offset(shape, offset_shape, original_endpoints, move_distance, spatial_ref)
                        
                        # 平滑处理
                        offset_shape = smooth_polyline(offset_shape, 0.2, spatial_ref)
                        
                        # 连接端点
                        offset_shape = connect_endpoints_to_original(offset_shape, original_endpoints, move_distance * 3)
                        
                        # 插入偏移后的要素
                        insert_cursor.insertRow([offset_shape] + modified_attributes)
                        moved_count += 1
                        arcpy.AddMessage("成功处理要素 {}".format(oid))
        
        # 获取新创建要素的OID
        new_where_clause = "XCFX = 'B'"
        with arcpy.da.SearchCursor(data_source, ["OID@"], new_where_clause) as cursor:
            for row in cursor:
                new_features_oids.append(row[0])
        
        arcpy.AddMessage("新创建的下行线要素数量: " + str(len(new_features_oids)))
        
        # 提交编辑
        edit.stopOperation()
        edit.stopEditing(True)
        
        arcpy.AddMessage("成功处理 " + str(moved_count) + " 个要素")
        
        # 刷新视图
        arcpy.RefreshActiveView()
        arcpy.RefreshTOC()
        
        # 清除选择集
        target_layer.setSelectionSet("NEW", [])
        
        arcpy.AddMessage("处理完成! 复杂线形已成功复制为下行线")
        arcpy.AddMessage("采用逐段偏移算法,避免S型线相交问题")
        
    except Exception as e:
        # 中止编辑
        edit.stopOperation()
        edit.stopEditing(False)
        raise e

except Exception as e:
    arcpy.AddError("错误: " + str(e))
    import traceback
    error_msg = traceback.format_exc()
    arcpy.AddError(error_msg)

 

posted @ 2025-10-26 21:51  山鬼谣`  阅读(11)  评论(0)    收藏  举报