ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ArcGIS图斑自动编号:ArcPy实现自上而下从左到右排序

ArcGIS图斑自动编号:ArcPy实现自上而下从左到右排序 简介ArcGIS图斑编号工具是一款面向GIS数据管理、土地规划与城市设计场景的实用开发组件主要用于对矢量图斑按自上而下、从左到右的规则批量生成唯一编号有效解决地块、建筑物等地理对象的标识与排序问题。资源包共39个文件整体大小仅3.52MB包含16个ArcGIS二次开发所需的DLL库、17个XML配置文件、3个可执行程序以及config、manifest等辅助文件可支撑在C#或VB.NET环境中调用ArcObjects接口进行功能集成与自定义扩展。压缩包内已整理好运行组件与程序结构用户拿到后可直接部署也可参考其编号实现逻辑结合中心点坐标、指定排序字段、编号起始值与格式等参数灵活适配不同业务需求。目前已有1608人学习下载适合ArcGIS开发人员、GIS数据管理人员及需要处理空间编号问题的技术学习者参考使用。 搞GIS的人应该都经历过这种场面一版图斑解译完成密密麻麻铺满屏幕接下来就是给每个图斑编号。技术方案上写着“自上而下、从左到右”看起来一句话的事实际动手才知道里面有太多讲究——起止怎么定、跨行怎么断、狭长图斑算哪一行、半路发现漏了图斑要不要全部重来。没有趁手工具的时候对着几千个图斑手工点属性表编号还没编完眼睛先花了。这篇博文要说的就是我自用的一个ArcGIS图斑编号工具专门实现“自上而下、从左到右”的自动编号逻辑基于ArcPy写成不依赖第三方库在ArcGIS Desktop或Pro的Python窗口里直接能跑。做自然资源调查、国土变更调查、林草湿数据整合、确权登记这类项目的同学应该都用得上。我会把排序逻辑、完整代码、边界情况处理都摊开讲最后说说实际项目里踩过的坑。1. 为什么外业调查非要“自上而下、从左到右”编号1.1 编号规则不是风格问题是管理需求图斑编号看着只是给每个多边形排个序号实际上背后是外业核查的工作逻辑。外业人员拿着打印好的图斑图纸进山要按编号逐个找到目标图斑。如果编号顺序和人的视觉习惯、行进路线一致找起来顺很多如果编号是乱的哪怕只是相邻两个图斑序号错位外业在现场就要多花不少时间。自上而下、从左到右的规则正好匹配人眼看图的基本顺序也匹配多数打印图幅的阅读顺序。所以在项目实践中只要技术方案里写了这个编号规则它就不是可选风格而是硬性约束。这个规则的背后还有一个隐含要求编号要稳定、有规律、能对应到实地位置。系统自动生成的FID、OBJECTID虽然唯一但完全看不出空间关系拿到外业根本没法用。这也是为什么需要专门做排序编号而不是简单写个循环给所有图斑赋号。1.2 手动编号的痛点和自动化的切入点手动编号的效率问题不用多说说几个细节第一上千个图斑每个都要点一下属性表输入编号纯体力劳动第二人眼判断“哪一行更高”经常有争议尤其两条行带边缘交错的时候第三编号过程必须一路记着编到哪了一个分心就重号漏号第四外业反馈某个区域编号逻辑不对内业修改往往要重建一片区域的编号牵扯面很大。自动化工具要解决的也就不只是“生成一串数字”而是要给出一个稳定可复现的空间排序判定规则保证任何熟悉规则的人来跑得到的结果一致。这个“可复现”特别重要它意味着一旦外业对编号有疑问内业可以用同一套逻辑重新验证而不是靠操作者主观判断来争论。2. 排序规则拆解直接按质心排序的局限和更稳妥的分组排序法2.1 直接按质心X/Y排序有什么问题先看最朴素的做法读取每个图斑的质心坐标按Y坐标从大到小排再按X坐标从小到大排最后直接赋编号。这个逻辑能覆盖一部分情况但遇到真实图斑经常翻车。问题本质上出在“质心”上。质心是一个几何上的平均位置不代表图斑在视觉上的“归属行”。举个常见的例子一行图斑里突然有一个抻得特别长的图斑头部和同行图斑齐平尾部耷拉到下一行的位置它的质心会被拉低一大截。按全局Y坐标降序排列时这个图斑很可能会被排到下一行去编号结果就不符合人眼认知了。另一个问题更隐蔽全局Y排序完全不考虑“行”的概念。两个本来属于同一行的图斑因为Y方向上有轻微高低差排序后中间像被卡进了一个其它行的小图斑编号顺序就变成“Z字形”乱窜外业核查时非常难受。2.2 先分组再排序更符合人眼习惯的方案针对上面两个问题我采用的方案是“初排—分组—行内排序—组间排序”四步走先按质心Y坐标从大到小对全部图斑做一次初排保证大的顺序方向是从上到下。从上到下遍历把质心Y坐标足够接近的图斑归入同一组视为同一行。组内按质心X坐标从小到大排序保证从左到右。按组的平均质心Y坐标从大到小排列组间顺序组内连续编号。关键在第二步的“足够接近”怎么定义。我用的判断逻辑是比较当前图斑和当前行最后一个图斑的质心Y坐标差值如果差值小于等于容差就归入当前行否则新起一行。用相邻差而不是和行首的差值是为了适应山坡地带图斑质心在一行内缓慢漂移的情况——如果要求和行首保持严格相等一条有坡度的行带会被切成好几截。容差的计算我一般这样处理参考单元尺寸 sqrt(数据整体范围面积 / 图斑总数) 容差 参考单元尺寸 × 0.4这个公式用平均每个图斑占的面积反推出一个参考间距再乘系数作为容差。系数取0.3到0.5之间都行0.4是我的默认值。数据范围面积我用的是所有图斑质心围成范围的宽高乘积坐标单位是米时算出来才有物理意义。如果容差设得太大两行图斑会被合并成一行编号顺序错乱设得太小一行又被拆成多行。建议拿到新数据先试跑一遍看分组结果里每行大概多少个图斑再微调系数。2.3 别把坐标系方向搞反了还有一个容易忽略的细节ArcGIS的坐标系里Y轴向上所以“自上而下”对应Y坐标从大到小“从左到右”对应X坐标从小到大。这个方向约定本身不难但如果数据是地理坐标系经纬度或者经过了坐标偏移Y和X的绝对数值大小就和视觉位置对不上。后面我会专门讲这部分坑这里先记住排序前确认坐标系的轴向和单位比如CGCS2000的投影坐标下Y坐标是北向X坐标是东向。3. 基于ArcPy实现编号工具的核心逻辑与代码3.1 完整代码框架直接上代码。这个脚本我用da.SearchCursor读取质心用da.UpdateCursor写回编号ArcGIS 10.1以后的版本都能跑Pro 3.x环境下测试正常。# -*- coding: utf-8 -*- import arcpy import math # 参数设置 fc r你的要素类路径 bh_field TBBH # 编号字段需要提前创建文本型 prefix # 编号前缀比如 LD row_tolerance_factor 0.4 # 行分组容差系数默认0.4 # # 1. 读取所有图斑质心 features [] with arcpy.da.SearchCursor(fc, [OID, SHAPE]) as cursor: for row in cursor: centroid row[1].centroid features.append([row[0], centroid.X, centroid.Y]) if len(features) 0: raise SystemExit(没有读取到任何图斑请检查要素类路径) # 2. 计算参考容差 xs [f[1] for f in features] ys [f[2] for f in features] width max(xs) - min(xs) height max(ys) - min(ys) avg_cell math.sqrt((width * height) / len(features)) tolerance avg_cell * row_tolerance_factor print(容差设置为: {:.2f}.format(tolerance)) # 3. 按质心Y坐标降序初排 features.sort(keylambda f: f[2], reverseTrue) # 4. 分组依据相邻质心的Y差值 rows [] current_row [features[0]] for i in range(1, len(features)): if abs(features[i][2] - current_row[-1][2]) tolerance: current_row.append(features[i]) else: rows.append(current_row) current_row [features[i]] rows.append(current_row) # 5. 行内按X坐标升序排序 for row in rows: row.sort(keylambda f: f[1]) # 6. 构建 OID - 编号 映射并写回 oid_to_num {} num 1 for row in rows: for f in row: oid_to_num[f[0]] num num 1 with arcpy.da.UpdateCursor(fc, [OID, bh_field]) as cursor: for row in cursor: code oid_to_num.get(row[0], None) if code is None: continue if prefix: row[1] {}-{:04d}.format(prefix, code) else: row[1] {:04d}.format(code) cursor.updateRow(row) print(编号完成共处理 {} 个图斑.format(len(features)))3.2 代码里的几个关键细节第一为什么编号字段用文本型而且补零文本型的好处是以后可以加前缀也可以插编号比如“LD-0031A”而不破坏排序。补零到四位是为了保证字符串按字典序排序和数字排序一致否则“LD-10”会出现在“LD-9”前面外业按编号找图的时候容易懵。位数可以根据项目图斑总量调整如果总数可能过万就补到五位。第二为什么先全部读出来再写回而不是用一个UpdateCursor同时判断因为UpdateCursor遍历时同时开着对同一数据的读取在高版本ArcGIS上容易遇到文件锁或者连接冲突大数据量下还可能拖慢速度。我的习惯是读取阶段用SearchCursor把质心信息全部写入内存等计算完成后单独再开UpdateCursor写回。内存占用不算大几万个图斑也就存几万组三个数。第三如果要只给选中图斑编号怎么办代码里可以直接加where_clause。用Open属性查询的SQL子句比如“OBJECTID IN (SELECT OBJECTID FROM 你的图层名 WHERE 选择条件)”或者事先用arcpy.SelectLayerByAttribute_management选中后再显式传入selection set。这个细节我后面展开说因为很多人在测试工具时在这里翻车。3.3 图斑整体是斜的怎么改进排序上面代码默认图斑分布基本和坐标轴平行。如果项目区域整体斜着长比如一条西北—东南走向的河谷直接按绝对Y排序会把同一条河谷里的图斑拆到不同行编号在图纸上看起来斜七扭八。我的处理方法是先做一个主方向旋转。粗算主方向可以用所有质心点做PCA计算协方差矩阵最大特征值对应的特征向量就是分布主方向。然后把每个质心坐标投影到主方向和垂直主方向的两个轴上在新坐标系里做分组排序最后把编号结果映射回原数据。核心代码不复杂关键公式是dx centroid.X - center_x dy centroid.Y - center_y coord_parallel dx * cos_theta dy * sin_theta coord_perpendicular -dx * sin_theta dy * cos_theta其中theta是主方向与X轴的夹角。旋转后“自上而下”对应coord_perpendicular“从左到右”对应coord_parallel。绝大多数项目用不上这个旋转版但如果区域条带倾斜明显值得做一版备用。4. 工具在真实数据中的表现与边界情况处理4.1 一组模拟数据的编号效果我用模拟数据做过验证生成10行乘10列共100个矩形图斑行间距和列间距都设为10米在第5行故意把一个图斑拉长让它的质心明显下移。工具跑完后编号结果前50个图斑顺序符合左下到右上的行列预期第5行被拉长的图斑虽然质心低但因为和相邻图斑的Y差仍在容差范围内被正确留在本行没有被甩到下一行。这个案例说明分组逻辑对“凸出图斑”是有效的。我还测过一行里图斑数量不等的情况。第3行只有5个图斑第4行有12个工具分别按各自的X顺序编号行间衔接正确。第4行末尾图斑的编号接第5行开头图斑时Y方向跳变明显外业一眼能看出换行了。4.2 各类边界形状的处理观察实际数据里的图斑形状比矩形复杂得多。我梳理了几类常见的边界情况带孔洞的图斑质心可能落在孔洞里但作为排序依据仍然有效因为排序只看位置不看形状。凹多边形图斑质心有可能落在图斑外部但只要位置在整体分布范围内排序结果不受影响。狭长图斑这是最容易出问题的类型。一条几百米长的狭长图斑横跨两三个行带它的质心位置能代表它“主要占据”的行但视觉上它确实延伸到别的行范围。工具只能依据质心归属不可能让一个图斑同时编两个号这是编号规则本身的限制要在项目一开始就跟业务方说明。零星散点状图斑分布极不均匀时容差计算可能失真建议把容差系数调小分组会更贴合人眼。4.3 性能表现用10万个图斑做了压测读取质心加排序加分组加写回整个流程大概耗时40秒左右内存占用在可接受范围。实际项目中一般也就几千到几万个图斑基本秒级完成。性能瓶颈主要在da.UpdateCursor逐行写回如果字段上建了索引还可能略慢但完全不影响日常使用。5. 实际使用中的几个坑与避坑建议5.1 坐标系不统一排序容差失效的根源这是我在项目中被问得最多的问题。如果数据是地理坐标系经纬度质心的X、Y单位是度直接套用“范围面积开根号”公式算出来的参考尺寸会非常小容差也跟着变得异常小导致分组时几乎每个图斑都单独成行编号结果就成了按Y坐标从大到小的纯排序完全没起到分行的作用。解决办法很简单跑工具前统一投影。建议用CGCS2000 / 3-degree Gauss-Kruger zone对应的投影坐标系或者至少用阿尔伯斯等积投影把单位统一成米。工具不检查坐标系这个责任在操作者身上。我一般会在工具箱里加一个前置校验检测到数据是地理坐标系直接报错提示。5.2 字段类型和备份意识字段类型我前面说过用文本型这里再强调一遍原因短整型字段没法存“LD-0031”这种带前缀的值也没法在中间插入“LD-0031A”。一旦项目中途需要补图斑没有扩展能力就很麻烦。备份这个事真的是老生常谈但每次总有人踩。编号工具会覆写整个编号字段一旦跑错想还原只能靠备份。我的标准操作是跑之前复制一份原始要素类到工作空间下的temp目录确认编号无误后再删。如果有必要也可以在脚本里加一个“备份旧编号”的开关把旧值存到另一个字段里做现场留底。5.3 选择集处理的差异ArcMap和Pro里用Python窗口跑脚本和用工具箱跑脚本对选择集的处理逻辑不一样。直接在Python窗口执行脚本da.UpdateCursor默认不加where_clause会把整个要素类都更新掉。很多新人在测试工具时选中了十几个图斑一跑发现全部图斑都被重新编号了就是这个原因。要支持“只编号选中图斑”可以在UpdateCursor里显式传where_clause。简单做法是先用arcpy.SelectLayerByAttribute_management选中目标然后用“OBJECTID IN (SELECT OBJECTID FROM 图层名)”构造查询子句传给游标。注意图层名要写对而且要区分是图层名还是要素类路径这个细节写错就查不到数据。5.4 ArcMap和Pro的版本差异ArcMap 10.x默认用Python 2.7ArcGIS Pro默认用Python 3.x。上面代码本身在两边都能跑但要注意Print语法、字符串编码这些基础差异。另外Pro里对某些数据源的锁定策略比ArcMap严格如果数据在ArcMap里没关闭直接到Pro里跑工具可能报文件被占用。建议统一在同一个版本环境里操作别两个软件来回切换。我整理了一个避坑速查表问题现象常见原因处理建议编号顺序和视觉明显不符数据是地理坐标系容差计算无意义统一转投影坐标系单位米一行图斑被拆成好几段容差系数设得太小调到0.5左右试跑两行图斑被合并成一行容差系数设得太大调到0.2-0.3再试全要素类都被重新编号忽略选择集脚本更新了整个图层加where_clause过滤编号字段无法保存字符字段类型建成了短整型删除字段重建为文本型脚本报文件被占用另一个程序还开着同一数据源关闭ArcMap/Pro中的图层再跑6. 按这个思路继续工具的扩展可能性6.1 批量编号、编号导出Excel、蛇形编号这套排序逻辑可以扩展出好几个实用版本。批量版本可以遍历工作空间下所有要素类用图层名拼音缩写做前缀自动编号处理一整个工程的数据。导出版本可以在编号完成后把图斑编号、面积、地类代码连同一张缩略图导出到Excel做成外业核查表平板上一翻就能定位。蛇形编号则更适合外业S型走线方案奇数行从左到右偶数行从右到左这样外业人员走完一行不用折返直接在终点顺路进入下一行省不少脚程。蛇形编号只需要在行内排序时判断行序号奇偶奇数行X升序偶数行X降序改动很小。6.2 和质量检查工作流结合最近几次项目里我都会在编号工具前加一步数据质量检查。比如用尖锐角检查插件先扫一遍图斑如果有问题图斑编号工具自动暂停等修正后再继续。这个思路的核心是避免“编号完了再发现数据有问题”的返工——图斑编号一旦发了外业回头改编号牵一发动全身最好在源头就拦住。延伸开想还可以把编号结果接入成果质检流程生成编号断号、跳号的检查报告避免外业核查时发现某个编号不存在。实际项目中这类细小的检查项价值不小往往能提前暴露数据问题省下后面一大笔返工的麻烦。最后再分享一点个人体会。我用这个工具跑了无数遍数据最深的感觉是一个看似简单的“编号”需求真正落地时全在边界情况里较劲。工具里的排序逻辑是把“自上而下、从左到右”从一句话翻译成可执行的规则但规则翻译得再好也不可能覆盖每一个特殊图斑。所以实操中我的流程一直是先让工具跑一遍再把编号标注开出来人眼过一遍重点检查每行的首尾衔接和跨行图斑发现问题在Excel里微调后再写回。这个“工具初排、人工复核”的配合模式是目前我在多个项目里验证下来最稳的做法。本文还有配套的精品资源点击获取
返回列表