ARTICLE DETAIL

资讯详情

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

ArcGIS自动化编号工具实战:用ArcPy脚本告别手工编号

ArcGIS自动化编号工具实战:用ArcPy脚本告别手工编号 简介ARCGIS编号小工具面向GIS数据处理人员支持为mdb、gdb、shp三种常见地理数据格式批量添加顺序编号适合在地图制作、要素标识、数据整理等场景中使用可有效降低重复操作带来的工作量。工具基于ArcGIS API构建涉及地图显示、地理数据库操作、几何对象处理等底层能力用户设定编号字段与起始值后即可自动为所选要素写入序列号整个操作过程简单直观。压缩包共74个文件包括32个dll组件、32个xml配置、4个config配置文件、4个exe可执行程序及2个manifest清单整体约7.1MBdll对应ArcGIS核心运行库xml提供模块配置说明exe为程序入口便于直接运行。该工具已有3985人学习下载借助这套精简程序用户无需编写复杂脚本即可完成编号工作同时也能从封装好的模块中了解ArcGIS二次开发常用的库组织方式与调用思路对日常数据入库、图层管理及空间分析前的属性整理都有帮助。 哎做GIS的兄弟姊妹们不知道你们有没有这种体会编号这件事看着简单干起来真要命。尤其是做国土、规划、地籍项目的一个图斑一个图斑地手输编号动辄上千个要素输到后面眼冒金星不是重号就是跳号回头检查简直想死。我之前也深受其害直到动手折腾了一个“ARCGIS编号小工具”才算彻底把这活儿给治了。这篇东西不整虚的就把我这个自动化编号工具的完整思路、踩过的坑、还有可以直接抄作业的代码逻辑分享出来。如果你也天天跟ArcGIS的属性表、字段计算器较劲那这篇文章应该能帮你省下不少头发。1. 编号到底在编什么先搞懂你要解决的痛点很多人一提“编号”第一反应就是“加个字段算个序号”。但真正的项目里编号要是这么简单我也不会专门写个工具了。我梳理了一下大家日常哭着喊着要做的编号基本逃不出这几类纯流水号这是最简单的1、2、3、4排下去。但难点往往在“按分组排号”比如按行政村统计每个村的地块要从1开始重新编。复合编码最常见的是“行政区代码地类代码流水号”比如110105001201这种前半截是固定的后半截是变化的。这个是最耗费眼神的因为前缀长特别容易复制粘贴出错。空间顺序编号不按属性表里的顺序来而是要求按照图形的空间位置排比如从左到右、从上到下或者沿着河流、道路走向从上游到下游来编。这个用Excel辅助就非常难办了。带检查位的编码有些地方要求编制不动产单元号、宗地代码最后一位或者几位是校验码比如按GB/T 7027-2002算出来的纯手工算算一个要半天。针对这些需求我开发这个工具的核心定位就很清晰了不搞花里胡哨的复杂ArcGIS Pro插件界面就用最通用的方式——一个自定义工具箱.tbx加几个Python脚本工具把那些重复性的、容易出错的编号逻辑固化成自动流程。这么干的好处是不挑ArcGIS版本从10.2到Pro都能用而且分发出去同事拿过去双击就能用不用搞复杂的Python环境。有一个反直觉的经验必须先说越是想“通用”的工具越难用。我第一版做了一个能处理所有情况的“万能脚本”结果参数设置界面比Excel公式还复杂根本没人用。后来我干脆拆分成三个独立的小工具一个管纯流水号含分组一个管带前缀的复合编号一个管空间顺序编号。每个工具都只解决一个问题参数简单明了。反而这个做法让同事用得不亦乐乎。2. 工欲善其事脚本编写前的环境准备与思路确认在打开记事本写代码之前有几件事必须提前确认这能帮你避开90%的幺蛾子。2.1 版本不对努力白费ArcGIS的Python环境是个大坑。我开发这个工具那会儿公司电脑上有的是ArcMap 10.4有的是10.8还有几个人已经换成了ArcGIS Pro。虽然都叫ArcPy但底层Python解释器完全不一样。ArcMap用的是Python 2.7ArcGIS Pro默认用Python 3.x。这就意味着你如果写print hello在旧环境没问题新环境直接就报语法错误。我的建议是如果是给公司内部分发使用最好盯着一两个主用版本开发。如果你非要写一个跨版本的也行那在脚本开头就得加上“探测代码”让它自己分辨环境。我在工具里就加了段探测逻辑# 判断当前运行的ArcGIS产品版本解决Python2/3兼容问题 import sys import arcpy def print_msg(msg): 兼容Python2和Python3的打印函数防止因为print语法挂掉 try: arcpy.AddMessage(msg) except: pass # 获取ArcGIS版本信息 try: version arcpy.GetInstallInfo()[Version] print_msg(当前ArcGIS版本: str(version)) except: pass这里有个经验就是别在ArcMap里显示英文信息除非你的用户都是老鸟。当初我工具里提示“Processing...”被一个老测绘员直接打电话骂了一顿说看不懂还以为报错了。后来所有提示都改成了纯中文比如“正在处理字段xx请稍候...”用户心里的踏实感是完全不一样的。2.2 基于“字段计算器”还是“独立脚本”这是一个核心设计分歧。ArcGIS的字段计算器Field Calculator加Python解析器也可以写复杂的编号逻辑而且很多人习惯这么做因为“写完直接跑”。但我的体会是字段计算器适合写一次性代码不适合做复用工。原因有三字段计算器里不能像脚本一样方便地写多行循环一旦写错了日志不清晰疯狂报错你只能干瞪眼。字段计算器状态条不直观。如果是几千个要素的复合编号字段计算器卡住了你根本不知道跑哪一步了是死锁还是慢字段计算器逻辑无法共享。脚本工具允许设置参数对话框比如选择“按哪个字段分组”而字段计算器你得每次改代码。所以我选择了做一个独立的Python脚本工具放到了ArcGIS自定义工具箱里。提示如果你只是临时处理一百来个要素你用字段计算器那套没问题。但一旦超过500个要素或者字段数量变多脚本工具的可靠性是碾压性的。2.3 明确输入与输出参数表设计脚本工具最重要的就是参数设计。我的“分组流水编号工具”参数设计如下参数1Input_Table待编号的要素类或表格要素图层、shp、gdb要素类皆可。参数2 (Group_Field)分组字段可选。如果选了这个字段编号就按这个字段值分组重新计数如果留空就是全局流水号。参数3 (Sort_Field)排序字段可选。编号顺序默认是按照属性表当前顺序但如果要素类内部有空间顺序或者特定顺序要求这里可以选一个排序依据例如“地类代码”。参数4 (Target_Field)目标字段也就是要把编号写进哪个字段字符串型。参数5 (Prefix_Text)前缀字符串可选。比如输入“GD”编号会变成“GD0001”。参数6 (Digit_Number)流水号位数长整型默认4位。比如4位就是从0001开始到9999。这套参数基本覆盖了日常80%的“傻瓜式”编号需求。你没看错我在这里没有放“校验码”参数那个复杂度确实太高了后来我单独做了一个脚本应对这个咱们后面讲。3. 核心代码逻辑拆解从“手算”到“自动算”3.1 分组编号的完整演算过程字段计算器里用Python写autoIncrement()是经典做法但你要做“分组自增”也就是每个Group_Field值重新从1开始那套逻辑就很绕。我的脚本用到了数据访问模块arcpy.da.UpdateCursor这个模块快而且干净利落。核心代码逻辑是这样的# -*- coding: utf-8 -*- import arcpy # 脚本参数赋值这一步通常由ArcGIS工具对话框传入 input_table arcpy.GetParameterAsText(0) group_field arcpy.GetParameterAsText(1) sort_field arcpy.GetParameterAsText(2) target_field arcpy.GetParameterAsText(3) prefix_text arcpy.GetParameterAsText(4) digit_number int(arcpy.GetParameterAsText(5)) # 对sort_field进行排序保证编号顺序稳定 if sort_field and sort_field ! #: # 拼接排序子句注意字段名要用!包裹 order_clause (None, sort_field) else: order_clause (None, None) # 用于记录每个分组当前编号的字典 group_counter {} # 拼接SQL查询如果group_field为空就自动转成全局自增 fields [] if group_field and group_field ! #: fields [group_field, target_field] else: fields [target_field] # 使用UpdateCursor逐行更新 with arcpy.da.UpdateCursor(input_table, fields, sql_clauseorder_clause) as cursor: for row in cursor: if group_field and group_field ! #: group_val row[0] # 字典计数 if group_val not in group_counter: group_counter[group_val] 1 else: group_counter[group_val] 1 new_seq group_counter[group_val] else: if TOTAL_COUNT not in group_counter: group_counter[TOTAL_COUNT] 1 else: group_counter[TOTAL_COUNT] 1 new_seq group_counter[TOTAL_COUNT] # 格式化流水号0占位符 seq_str str(new_seq).zfill(digit_number) # 加上前缀 final_code prefix_text seq_str if prefix_text else seq_str # 回写 if group_field and group_field ! #: row[1] final_code else: row[0] final_code cursor.updateRow(row) arcpy.AddMessage(编号完成共处理 {} 个要素。.format(sum(group_counter.values())))这里面的关键点我必须多啰嗦几句排序的坑很多人直接在UpdateCursor里加sql_clause发现似乎没按预想的字段排。那是因为数据源不同支持程度不一样。对于Shapefilesql_clause里只有ORDER BY是有效的对于文件地理数据库要素类FGDB排序基本没问题。但如果你用的是SDE企业级地理数据库那排序子句在某些权限下会使光标创建失败。所以稳妥做法是脚本开头先判断arcpy.Describe(input_table).workspaceType如果发现没法排序就先用arcpy.Sort_management复制一份临时数据排好序后再游标遍历最后再清理临时数据。游标效率arcpy.da.UpdateCursor比老的arcpy.UpdateCursor快得多因为后者每行都要拷贝几何。这里我们不需要几何千万别用老游标否则上万要素得跑好几分钟。如果只需要属性da游标的速度是碾压性的。Dictionary计数用字典保存分组计数是最清晰的。曾经见过有人用搜索游标SearchCursor每处理一行就去统计一遍分组内已有多少要素那复杂度是O(n²)数据一多就卡死这是典型的“能跑但效率极烂”的写法。3.2 空间顺序编号怎么让1号是左上角那个这个需求特别多因为很多地方规范要求“图斑编号按照从上到下、从左到右之字形走”。实现思路不复杂但坑不少。第一步给要素类添加两个临时双精度字段比如_TEMP_X和_TEMP_Y。第二步用游标读取每个要素的几何中心点并写入临时字段。这里注意不要用FeatureToPoint虽然它也能生成质心但它要求输出新数据效率低下。直接在UpdateCursor里row[1] shape.centroid.X一行搞定。第三步使用arcpy.Sort_management或游标的sql_clause按_TEMP_Y DESC, _TEMP_X ASC排序。这样可以实现“同一行内从左到右行间从上到下”的编号顺序。如果你要做“蛇形”之字形编号也就是第一行从左到右第二行从右到左那么需要先按Y分组然后对奇数行和偶数行分别做正序和倒序排序这一般得用它自带的“双排序字段”技巧或者直接读取所有坐标进Python列表在内存中排好序再写回。我的做法是后者因为逻辑更直白features [] with arcpy.da.SearchCursor(input_table, [OID, SHAPE, target_field]) as sc: for oid, shape, val in sc: centroid shape.trueCentroid features.append((oid, centroid.X, centroid.Y)) # 按照y降序、x升序排列先保证从上到下再保证从左到右 features.sort(keylambda x: (-x[2], x[1])) # 蛇形处理按Y坐标分组对相同Y或相近Y的组做蛇形反转 final_sorted [] row_group [] prev_y None for feat in features: if prev_y is None or abs(feat[2] - prev_y) 0.001: # 判断是否是同一行 if row_group: # 偶数行保持从左到右奇数行反转 if len(final_sorted) % 2 1: row_group.reverse() final_sorted.extend(row_group) row_group [feat] prev_y feat[2] else: row_group.append(feat) # 处理最后一行 if row_group: if len(final_sorted) % 2 1: row_group.reverse() final_sorted.extend(row_group)这个“蛇形排序”看起来简单实际跑的时候会发现一个问题地理数据的Y坐标不可能绝对相等如果图斑是大头小尾的你用abs(feat[2] - prev_y) 0.001判断很可能把明明是一行的图斑劈成两行导致编号乱跳。我后来做了一个改进不用Y坐标直接分组先用栅格化思路即把Y坐标分桶比如以所有图斑平均高度的1/2作为行容差把高度差在一定范围内的归为同一行。这种方法在国土项目里实测效果最好。代价是有时候斜着排列的图斑会被“强行拉直”编号但甲方通常接受这种“近步行”逻辑。3.3 校验码如宗地代码、不动产单元号怎么自动算这个是我最引以为傲的一块。很多朋友被“不动产单元号”的编制规则折磨过网上资料乱自己写公式容易错。这个工具里我把常见的“GB/T 7027-2002”校验规则写成了一个小函数其实就是加权因子求和取模。不动产单元号的编码逻辑比较复杂但关键是最后一位校验码它是用前面的数字根据一定的权重计算出来的。我的脚本实现大概是# 计算不动产单元号校验码简单示例具体权重按当地规范 def calc_check_digit(code_without_check): code_without_check: 不包含校验位的完整数字串 weights [1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 7, 9] # 示例加权因子 total 0 for i, ch in enumerate(code_without_check): if ch.isdigit(): total int(ch) * weights[i % len(weights)] remainder total % 10 check_digit (10 - remainder) % 10 return str(check_digit)注意这段代码是简化演示不同规范加权因子差异很大。如果你要用到生产环境务必去查当地不动产登记中心的规则说明把权重的顺序和模数核对清楚。我见过最坑的是不同版本资料里权重顺序不一样结果算出来的校验码跟系统里生成的完全对不上。校验码工具还有一个防范机制生成完整编号后自动利用arcpy.ValidateFieldName或者自定义规则校验一下目标字段的长度。因为不动产单元号通常规定必须28位如果前缀加流水号加校验码长度不对工具直接抛错并把错误要素编号导出成表格方便你检查是哪一段数据源头有问题而不是让你蒙在鼓里继续往下做。4. 实战排雷日记那些年我踩过的编号工具的坑4.1 可怕的字段长度问题和字符串零零散散Shapefile的DBF字段有历史包袱字符型字段最长254个字符这个大家都知道但很多人忽略了DBF字段的另一个坑字段名最长10个字符。如果你使用的目标字段名超过了10个字符比如“不动产单元号_修改后”在Shapefile里直接创建字段的时候会崩或者创建成功后字段名被截断成乱码。我的做法是脚本里自动判断数据源类型如果工作空间是Shapefile就调用arcpy.ValidateFieldName来检查目标字段名是否符合要求如果不符合就直接改成拼音缩写或者编号前缀比如“BDCDYH”。同时在界面上提示用户“当前数据为Shapefile格式字段名已被自动修改为xx请知悉。”这个提示能避免用户拿着软件跑完却找不到字段在哪的尴尬。4.2 小心“编辑会话”的权限坑这个真的让我记忆犹新。我第一版脚本在ArcMap里测试一个同事拿着工具去跑SDE里的要素类直接报错ERROR 000464: Cannot acquire a write lock。这是因为SDE要素类在被版本化编辑时普通脚本很难直接获取写锁。即便你只是更新属性没有编辑几何也可能会因版本化机制被拒。这个问题在ArcMap中比较常见。解决办法有两个方向在工具执行前先提示用户关闭其他编辑会话释放锁。或者脚本里偷个懒干脆先把目标数据复制到本地文件地理数据库本地编完号后再用ArcToolbox的“追加”或者“连接”工具导回去。注意“复制”会打乱原有的OID或唯一标识需要提前用某个稳定字段比如“标识码”关联回去。这个方案虽然笨但在SDE面前非常管用毕竟作为工具使用者你没有权限随时踢掉同事的编辑会话。4.3 双重排序和“稳定排序”的问题Python的list.sort是稳定排序也就是如果key相同会保持原始顺序。这个特性在编号里很重要。但ArcPy的游标排序可不是尤其是指定了sql_clause后如果排序字段有几个重复值它们的相对顺序是不是跟原来一致取决于数据库引擎。实际项目里我们经常要“先按村名排序再按组名排序最后按面积排序”。我建议的通用做法把排序字段全部读取进内存列表然后用Python进行多次排序。比如# 按照优先级依次sort由于sort稳定后面的排序不会打乱前面排序的结果 features.sort(keylambda x: x[4]) # 先按面积 features.sort(keylambda x: x[3]) # 再按组名 features.sort(keylambda x: x[2]) # 最后按村名这样最终顺序保证是“村名 - 组名 - 面积”的优先级非常直观准确并且绕开了不同数据库对ORDER BY多字段支持不统一的问题。这也是我认为这个工具比字段计算器“高级”的地方排序逻辑完全可控。4.4 错误捕捉和“傻瓜化”提示上面说了我把工具分发给同事用他们大多不是Python高手甚至不是GIS高级用户。所以脚本里的错误处理必须做到一旦出问题弹出来的不是红码天书而是一句人话。我的做法是把整个主流程套进一个大try/except然后在except里判断错误代码分门别类转译成中文。比如try: # 核心处理逻辑 pass except arcpy.ExecuteError: error_msg arcpy.GetMessages(2) if 000464 in error_msg: arcpy.AddError(数据被其他用户锁定请关闭ArcMap中的编辑会话后重试或者将该数据复制到本地gdb再执行。) elif 000854 in error_msg: arcpy.AddError(输入字段不存在或类型不对请检查目标字段是否为文本型。) else: arcpy.AddError(数据处理失败详细信息 error_msg) except Exception as e: arcpy.AddError(发生未知错误 str(e))这玩意儿看起来简单但对用户心理的安抚作用巨大。以前同事遇到报错第一反应是“完了工具坏了找XX来看”。现在遇到报错他会自己念一遍提示90%的问题就明白了省了我大量时间。5. 怎么把这块工具打磨得更顺手二次开发与界面优化5.1 从裸脚本到ArcGIS“地道”工具脚本写好了不能老是在Python窗口里粘贴运行。要做一个真正的“工具”你需要右键在ArcToolbox里新建→添加脚本→把脚本关联过来。设置参数对话框时有几个小细节值得注意参数名称尽量用英文脚本里GetParameterAsText读的是这个但显示名称一定要用中文。对于“分组字段”和“排序字段”一定要设置成Obtained from类型也就是下拉列表来自输入表的字段这样用户不用手敲不易错。参数是否必需Required要分清。全局流水号工具分组字段就设成可选毕竟不是每次都要分组。5.2 能不能做成ArcGIS Pro的. aml或Pro工具现在我主力已经切到ArcGIS Pro了因为Pro的Python 3环境支持更多第三方库而且它支持.atbx工具箱基于ArcPy的新工具箱。我在Pro里把原来ArcMap那套逻辑稍微改动了一下主要是去掉Python2的语法加了一点类型标注做成了一个新的.atbx。一个非常舒服的改进是Pro的工具支持暗色主题和进度条弹窗而且arcpy.AddMessage的消息会分层次显示信息、警告、错误这让调试脚本舒服了很多。5.3 让工具“带记忆”的配置文件我自己用无所谓但给别人用每次打开工具都得重新选一遍参数特别烦人。后来我加了一个小技巧把上一次使用的参数存到本地XML文件下次打开工具利用arcpy.GetParameterAsText的默认值逻辑在脚本工具属性的“参数”标签页里每个参数都可以设置默认值用toolbox同目录下的config.xml来预填。代码大概是这样import os import xml.etree.ElementTree as ET def load_last_params(tool_dir): config_path os.path.join(tool_dir, last_params.xml) if not os.path.exists(config_path): return {} tree ET.parse(config_path) root tree.getroot() params {} for child in root: params[child.tag] child.text return params然后在工具启动时如果检测到参数没传就自动用配置文件里的值填充。这个功能做出来之后同事们的反馈是“终于不用每次把村名敲一遍了”便利性提升显著。当然要提醒用户如果数据源变了默认参数可能会失效。所以我在界面上加了一行绿色提示“参数已自动恢复上次记录请确认数据路径是否正确。”防呆设计必须做足。6. 从编号小工具到“外业内业一体化”的扩展思考工具做完了好用是一回事更重要的是一通百通。很多人以为编号就是写字但实际上编号和“拓扑检查”“图表联动”“二维码生成”都密切相关。我在这个工具基础上又扩展了两块功能分享一下思路你们可以按需取用与拓扑检查结果联动拓扑错误比如重叠、缝隙常常也需要编号标记。我写了一个扩展脚本读取拓扑错误表err开头的表按类别分组自动把“重叠”、“缝隙”的错误代码写成OVERLAP_001这样的格式再通过“按位置选择”把编号回填到原始要素的属性里。这样外业处理时拿着一张标好编号的图纸就能精准定位问题区域省去了来回对比的折腾。生成二维码/条形码编号生成后很多项目要求做“标识牌”或者“成果二维码”比如把地块编号做成二维码贴在实地。ArcGIS本身不支持直接输出二维码但Python可以。只需要在编号工具之后加一个批量导出操作用qrcode库ArcGIS Pro的Python3环境可以pip install qrcode把每个要素的编号生成二维码再按要素的FID命名保存到文件夹。这个扩展思路非常受欢迎因为它让“编号”从一个抽象属性变成了实际可扫描的物料。当然这两块扩展有一个前提你的ArcGIS环境得允许安装第三方Python库。ArcMap的Python 2.7装库很痛苦Pro就好很多。所以如果你们已经切换到Pro强烈建议试试这条路如果还在ArcMap那就老老实实用官方库别折腾pip了。7. 我的几点实在建议送给正在折腾编号的同行工具的原理和代码都讲完了最后以一个“过来人”的身份说几句掏心窝子的话。第一别迷恋“一键全自动”。我见过很多朋友一上来就想做“读取Excel模板→自动匹配地块→自动生成带校验位的完整编号→输出报告”野心巨大。但实际做下来你会发现光是数据源格式不统一就能把你折磨疯。我的经验是把一个一个原子步骤切分开每个步骤做成独立、参数简单、验证充分的小工具然后用模型构建器ModelBuilder把这几个小工具串起来。这样每一步都可以单独排查错误也方便给不同权限的同事分发不同环节的工具。第二凡是批量修改数据一定要先备份再动手。这应该是GIS从业者的肌肉记忆了。哪怕你工具写得再稳也有数据异常导致不可逆修改的风险。我在工具里加了一个“自动备份原字段值”的选项执行编号前如果勾选了就把目标字段的原始内容复制到一个备份字段里。可能有人觉得这字段多余但正是这个操作帮我救回过一次因为行政区划代码变动而导致的全批次编号错误。第三学会利用ArcGIS的帮助文档和报错日志。很多朋友一报错就截图发群其实自己用arcpy.GetMessages(2)拉一下详细日志大部分问题都能定位。我见过太多人是被吓住的实际上错误信息写得明明白白“字段不存在”“数据被锁定”……你把英语单词查一查问题就解决一半了。第四有条件的话尽量往ArcGIS Pro迁移。我理解很多单位还在用ArcMap那是因为历史项目和插件太多迁移成本高。但从做工具的角度讲Pro的Python 3环境、更加现代的数据访问方式以及处理海量数据的性能都远远好于旧平台。我做这个编号工具从ArcMap迁移到Pro大概只花了一个下午的时间改语法但带来的回报是长期且显著的。最后再分享一个容易被忽略的小技巧字段计算器里写代码一定要先选对“解析程序”。很多人在ArcMap的字段计算器里直接写Python代码但默认用的是VB Script结果按确定就报语法错误。这个低级错误居然特别常见。如果我还留在字段计算器的方案上估计这帖子光讲这个坑就得占一半篇幅。这也是我坚持用独立脚本工具的原因——少一个让用户踩坑的机会工具就成功了一大半。这个编号小工具后续我还打算加入对“ArcGIS Pro 3.x”新特性如属性域和条件规则的支持实现“输入即校验”的效果。不过在目前阶段上面这套流程已经稳定扛住了不少项目的考验。如果你也在为编号的事情焦虑别急理清需求从最痛的一个点下手你的“小工具”也能变成项目里的“大功臣”。本文还有配套的精品资源点击获取
返回列表