【ArcGIS技巧】根据地块、界址点图层生成界址线
"农经权二轮延包我已经写的差不多了,需要的一些生成四至、分割地块的功能也分享了替代的插件。前面刚分享完界址点的生成,今天分享界址线的生成,有需要的自取,至此,基本可以用这些功能完成出成果工作。"
1、工具使用
在使用前,先确保有地块(DK.shp)要素图层,且包含字段“DKBM”、“ZJRXM”;界址点(JZD.shp)要素图层(含字段"DKBM", "JZDH")。
插件因为生成界址线比较复杂,而且生成JZX.shp图层包含了:"DKBM"、“QZJDH”、“ZJZDH”、“JZXSM”,特别是“JZXSM”字段比较复杂,所以生成的时间稍微长些,请注意等待。
下载后记得加载py文件,不会的参考:【ArcGIS技巧】分享个判断是否基本农田的工具。
2、脚本逻辑
脚本的生成逻辑大致分为三步:1、先利用地块要素图层生成只要两个点的直线,生成DKBM、PLDWQLR字段;2、根据生成DKBM、PLDWQLR字段合并直线,因为前面界址点生成简化了面,所以要确保每个界址线的起止点都在界址线图层中,再根据界址点去截断界址线;3、根据界址点的JZDH以及界址线的长度与方位等,生成“QZJDH”、“ZJZDH”、“JZXSM”字段内容。
值得注意的是:工具的py文件,在pycharm等解析器中写完后,需要用文本文件编辑器打开,另存文件覆盖原来的.py文件,编码选ANSI格式,只有这样,其中一些中文什么的才会执行时不报错。
3、具体代码
工具的具体代码如下:
# -*- coding: utf-8 -*-
import arcpy
import os
from collections import defaultdict
import tempfile
import math
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
arcpy.env.workspace = u"C:\\data"
arcpy.env.overwriteOutput = True
arcpy.env.qualifiedFieldNames = Falsedk_shp = u"C:\\data\\DK.shp"
jzd_path = u"C:\\data\\JZD.shp"
output_folder = u"C:\\data\\new"# 中间文件路径
temp_folder = tempfile.mkdtemp()
jzx1_path = os.path.join(temp_folder, u"JZX1.shp")
dissolve_path = os.path.join(temp_folder, u"dissolved.shp")
rounded_points = os.path.join(temp_folder, u"rounded_points.shp")def create_jzx():# 验证必须字段存在required_fields = ["DKBM", "ZJRXM"]actual_fields = [f.name for f in arcpy.ListFields(dk_shp)]missing = [f for f in required_fields if f not in actual_fields]if missing:raise arcpy.ExecuteError(u"缺失必要字段:{}".format("/".join(missing)))# ====================== 创建输出要素 ======================sr = arcpy.Describe(dk_shp).spatialReferencearcpy.CreateFeatureclass_management(out_path=temp_folder,out_name=os.path.basename(jzx1_path),geometry_type="POLYLINE",spatial_reference=sr)# 添加字段arcpy.AddField_management(jzx1_path, "DKBM", "TEXT", field_length=100)arcpy.AddField_management(jzx1_path, "PLDWZJR", "TEXT", field_length=255)# ====================== 构建DKBM-ZJRXM映射 ======================dk_zjr_map = {}with arcpy.da.SearchCursor(dk_shp, ["DKBM", "ZJRXM"]) as cursor:for row in cursor:dk_zjr_map[row[0]] = row[1]# ====================== 主处理逻辑 ======================line_dict = defaultdict(set)arcpy.AddMessage(u"正在采集边界线段...")# 第一次遍历:收集所有边界线段with arcpy.da.SearchCursor(dk_shp, ["OID@", "SHAPE@", "DKBM"]) as cursor:for row in cursor:geom = row[1]dkbm = row[2]# 分解多边形边界线for part in geom.getPart():for i in range(len(part) - 1):pt1 = part[i]pt2 = part[i + 1]# 标准化线段方向(避免反向重复)if (pt1.X, pt1.Y) > (pt2.X, pt2.Y):pt1, pt2 = pt2, pt1# 生成唯一标识(精确到毫米)line_key = (round(pt1.X, 3), round(pt1.Y, 3),round(pt2.X, 3), round(pt2.Y, 3))line_dict[line_key].add(dkbm)# ====================== 写入结果 ======================arcpy.AddMessage(u"正在生成界址线...")with arcpy.da.InsertCursor(jzx1_path, ["SHAPE@", "DKBM", "PLDWZJR"]) as i_cursor:# 第二次遍历:处理相邻地块逻辑for line_key, dkbms in line_dict.iteritems():pt1 = arcpy.Point(line_key[0], line_key[1])pt2 = arcpy.Point(line_key[2], line_key[3])line = arcpy.Polyline(arcpy.Array([pt1, pt2]), sr)# 处理DKBM字段(按字母排序)sorted_dkbms = sorted(dkbms, key=lambda x: str(x))dkbm_str = "/".join(sorted_dkbms)# 处理PLDWZJR字段if len(sorted_dkbms) == 1:zjrxm = u"无"else:# 取"/"后面所有地块的ZJRXMzjrxm_list = []for dk in sorted_dkbms[1:]: # 从第二个开始if dk in dk_zjr_map:zjrxm_list.append(dk_zjr_map[dk])zjrxm = "/".join(zjrxm_list) if zjrxm_list else u"无"i_cursor.insertRow([line, dkbm_str, zjrxm])arcpy.AddMessage(u"处理完成!输出路径:{}".format(jzx1_path))create_jzx()try:# ===== 阶段1:合并界址线 =====arcpy.Dissolve_management(in_features=jzx1_path,out_feature_class=dissolve_path,dissolve_field=["DKBM", "PLDWZJR"],multi_part="SINGLE_PART")# ===== 阶段2:创建精确坐标点 =====arcpy.CreateFeatureclass_management(temp_folder,os.path.basename(rounded_points),"POINT",spatial_reference=arcpy.Describe(jzd_path).spatialReference)# 插入舍入坐标点with arcpy.da.SearchCursor(jzd_path, ["SHAPE@XY"]) as sc, \arcpy.da.InsertCursor(rounded_points, ["SHAPE@XY"]) as ic:for row in sc:x, y = row[0]ic.insertRow([(round(x, 3), round(y, 3))])# ===== 阶段3:批量拆分线段 =====# 创建内存图层arcpy.MakeFeatureLayer_management(dissolve_path, "lines_lyr")arcpy.MakeFeatureLayer_management(rounded_points, "points_lyr")# 执行拆分操作split_result = arcpy.management.SplitLineAtPoint(in_features="lines_lyr",point_features="points_lyr",out_feature_class=os.path.join(output_folder, "JZX"),search_radius="0.001 Meters")
finally:# 清理临时数据for item in [dissolve_path, rounded_points]:if arcpy.Exists(item):arcpy.Delete_management(item)def calculate_direction(angle):"""根据方位角计算方向字典"""if 337.5 <= angle or angle < 22.5:return "东"elif 22.5 <= angle < 67.5:return "东南"elif 67.5 <= angle < 112.5:return "南"elif 112.5 <= angle < 157.5:return "西南"elif 157.5 <= angle < 202.5:return "西"elif 202.5 <= angle < 247.5:return "西北"elif 247.5 <= angle < 292.5:return "北"elif 292.5 <= angle < 337.5:return "东北"def get_angle(start_point, end_point):"""计算两点之间的方位角"""dx = end_point.X - start_point.Xdy = end_point.Y - start_point.Yangle = math.degrees(math.atan2(dy, dx))return (angle + 360) % 360# 创建字段
split_lines = os.path.join(output_folder, "JZX.shp")
for field in ["QZJDH", "ZJZDH", "JZXSM"]:if not arcpy.ListFields(split_lines, field):arcpy.AddField_management(split_lines, field, "TEXT", field_length=100)# 构建界址点索引
jzd_dict = defaultdict(dict)
with arcpy.da.SearchCursor(jzd_path, ["DKBM", "JZDH", "SHAPE@"]) as cursor:for row in cursor:key = (row[0], row[1])jzd_dict[key] = row[2].centroid # 获取质心坐标# ========== 处理界址线 ==========
with arcpy.da.UpdateCursor(split_lines, ["DKBM", "PLDWZJR", "SHAPE@", "QZJDH", "ZJZDH", "JZXSM"]) as cursor:for row in cursor:dkbm_line = row[0]pldwzjr = row[1] if row[1] else "无"geometry = row[2]# ===== 步骤1:调整线段方向 =====# 获取首尾点坐标first_point = geometry.firstPointlast_point = geometry.lastPoint# 查找对应的界址点号found_start = Nonefound_end = None# 建立一个DKBM与ZJDH的键值对found_sartdkbm = Nonefound_enddkbm = Nonefor (dkbm_part, jzdh), point in jzd_dict.items():set1 = set(dkbm_line.split("/"))set2 = set(dkbm_part.split("/"))if not set1.isdisjoint(set2):if (abs(point.X - first_point.X) < 0.001 andabs(point.Y - first_point.Y) < 0.001):found_start = jzdhfound_sartdkbm=dkbm_partif (abs(point.X - last_point.X) < 0.001 andabs(point.Y - last_point.Y) < 0.001):found_end = jzdhfound_enddkbm=dkbm_partline_dkbm=dkbm_line.split("/")list_qjzdh=[]list_zjzdh = []for i in line_dkbm:list_jzdh1=found_start.split("/")list_dkbm1=found_sartdkbm.split("/")for j in range(len(list_dkbm1)):if i==list_dkbm1[j]:list_qjzdh.append(int(list_jzdh1[j]))list_jzdh2 = found_end.split("/")list_dkbm2 = found_enddkbm.split("/")for j in range(len(list_dkbm2)):if i == list_dkbm2[j]:list_zjzdh.append(int(list_jzdh2[j]))def reverse_polyline(geom):array = arcpy.Array()part = geom.getPart(0) # 获取线段的点集合reversed_part = [pnt for pnt in part][::-1] # 反转坐标点顺序for pnt in reversed_part:array.add(pnt)return arcpy.Polyline(array) # 重建反转后的线段# 判断是否需要反转if found_start and found_end and sum(list_qjzdh) > sum(list_zjzdh):geometry = reverse_polyline(geometry)# ===== 步骤2:生成起止点号 =====# 生成字段值str_qjzdh=''if len(list_qjzdh)==1:str_qjzdh =str_qjzdh + 'J'+str(list_qjzdh[0])else:str_qjzdh = str_qjzdh + 'J' + str(list_qjzdh[0])+u'/J'+str(list_qjzdh[1])str_zjzdh=''if len(list_zjzdh)==1:str_zjzdh =str_zjzdh + 'J'+str(list_zjzdh[0])else:str_zjzdh = str_zjzdh + 'J' + str(list_zjzdh[0])+u'/J'+str(list_zjzdh[1])row[3] =str_qjzdhrow[4] = str_zjzdh# ===== 步骤3:生成描述字段 =====# 计算长度length = round(geometry.length, 2)# 计算方向start_pt = geometry.firstPointend_pt = geometry.lastPointangle = get_angle(start_pt, end_pt)direction = calculate_direction(angle)# 构建描述desc = "{}-{}:由{}界址点沿{}宗地分界线往{}方向长度{}m至{};".format(row[3].split("/")[0], row[4].split("/")[0], row[3].split("/")[0],pldwzjr if pldwzjr != "无" else "",direction,length,row[4].split("/")[0])row[5] = desccursor.updateRow(row)print(u"处理完成!")