"农经权二轮延包我已经写的差不多了,需要的一些生成四至、分割地块的功能也分享了替代的插件。前面刚分享完界址点的生成,今天分享界址线的生成,有需要的自取,至此,基本可以用这些功能完成出成果工作。"
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、具体代码
工具的具体代码如下:
python
# -*- 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 = False
dk_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).spatialReference
arcpy.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:
# 取"/"后面所有地块的ZJRXM
zjrxm_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.X
dy = end_point.Y - start_point.Y
angle = 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.firstPoint
last_point = geometry.lastPoint
# 查找对应的界址点号
found_start = None
found_end = None
# 建立一个DKBM与ZJDH的键值对
found_sartdkbm = None
found_enddkbm = None
for (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 and
abs(point.Y - first_point.Y) < 0.001):
found_start = jzdh
found_sartdkbm=dkbm_part
if (abs(point.X - last_point.X) < 0.001 and
abs(point.Y - last_point.Y) < 0.001):
found_end = jzdh
found_enddkbm=dkbm_part
line_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_qjzdh
row[4] = str_zjzdh
# ===== 步骤3:生成描述字段 =====
# 计算长度
length = round(geometry.length, 2)
# 计算方向
start_pt = geometry.firstPoint
end_pt = geometry.lastPoint
angle = 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] = desc
cursor.updateRow(row)
print(u"处理完成!")