干过国土数据处理这一行的朋友,对这些场景应该都不陌生:项目用地预审、规划基数转换、土地报批组件,第一步基本都是把手里的三调地类图斑,转成农用地、建设用地、未利用地这三大类口径。三调数据里一个图斑一个地类编码,密密麻麻几万行,如果靠人眼在属性表里对着Excel台账一条条改,改到后面眼都花了,还特别容易串行看错。
我也是被逼到没办法才写了这套脚本,用Python + ArcGIS Pro,一次性把一个要素类里几万个图斑的地类编码自动映射成三大类,写进新增字段,跑完直接统计面积就能用。哪怕你没怎么写过ArcPy代码,照着文章的步骤走,也能在半小时内落地。这篇文章主要面向自然资源、规划、测绘行业里做数据处理的同行,也适合刚接触ArcGIS Pro二次开发的人当入门案例。
1. 需求场景与分析思路
1.1 三调地类和土地三大类到底差在哪
三调数据落地到图斑后,核心属性就是“DLBM(地类编码)”和“DLMC(地类名称)”。这套编码体系是由标准分类规则衍生出来的,全国三调数据库里统一维护,最直观的表现形式就是四位数字编码,比如最典型的0101代表水田,0301代表乔木林地。前两位说明一级类,后两位往下细分二级类,整体层级很清晰。
但问题来了,“土地三大类”并不跟一级类完全对应。农用地、建设用地、未利用地这三大类是规划分析、规模核算时常用的一种归并口径,它要把三调里的几十个二级类重新归堆。最典型的就是水域及水利设施这一类,里面有些要算农用地,有些要算建设用地,还有些必须算未利用地。比如沟渠、坑塘这种偏农业生产配套的,一般归农用地;水工建筑用地偏工程设施性质,归建设用地;而河流水面、湖泊水面这些天然水体,才归到未利用地。
同一个一级分类被拆到三个不同方向,这就是为什么不能简单拿地类编码前两位一把梭处理。我见过有人直接在属性表里用前两位匹配,结果“1109水工建筑用地”和“1107沟渠”全被归成了同一类,最后面积台账怎么都对不上,返工成本极高。正确思路必须落到完整的地类编码上做精确映射,至少也要把特例清单单独列出来覆盖一遍。
1.2 为什么选择Python + ArcGIS Pro
当时摆在我面前的可选方案有三个:手动编辑、ModelBuilder、Python脚本。手动编辑最原始,小数据量还能忍,图斑量一旦上到5万以上,人就变成了人肉检查机,改到后面已经分不清是在看编码还是在看花眼。ModelBuilder理论上能做,用“计算字段”工具反复叠加条件表达式,也能跑出结果,但条件一多,模型图就变成蜘蛛网,而且每换一批数据都要重新指定输入输出,后期维护成本反而更高。
Python + ArcGIS Pro的好处在于逻辑清晰、可复用、可批量处理。ArcGIS Pro自带完整的Python 3运行环境和arcpy库,不需要额外安装任何东西,直接把脚本放进去跑就行。脚本写完后,以后所有项目共用一个文件,换路径换字段名就能复用,还能封装成自定义脚本工具扔给同事用。另外,ArcGIS Pro的Python环境对中文支持比ArcMap时代的Python 2舒服太多,至少不用整天折腾中文字符集报错的问题。
选这个方案还有一个现实原因:三调数据后续几乎不可能只做一次转换。随着项目进度推进,有时候要按不同口径重新归类,有时候要局部修正某几个地类的归属,如果每次都用Python脚本,改几行规则重新跑一遍即可,效率提升非常明显。
2. 动手前先把规则理清楚:对照表是核心
2.1 三调地类编码体系怎么读
拿到一份三调数据,先打开属性表,找到DLBM字段,正常情况下看到的是一串四位数字。不同地区的数据可能来自不同建库软件,字段名不一定完全统一,有的叫DLBM,有的叫DLBM_1,有的干脆叫地类编码,但内容基本都是编码加名称的组合。我的经验是编码字段永远比名称字段靠谱,因为名称容易被人为修改,编码经过建库检查相对规范。
从编码结构看,前两位是一级类,后两位是二级类。整个体系的二级类数量不算少,而且不同版本标准对个别一级类的归并有差异,所以不建议硬背编码表。实际操作时,我一般先把数据里所有不重复的DLBM值统计一遍,拿到完整编码清单后再对照规则表做映射,这样最稳,也最能避免漏掉冷门地类。
还有一种情况需要注意,部分数据从其他平台导出来后,DLBM字段可能变成了整数型,四位编码的前导零会被吃掉。比如0101变成101,0204变成204,这时候如果直接用编码去匹配字典,全都匹配不上。遇到这种数据,得先做一步补零处理,把编码统一格式成四位字符串再往下走。
2.2 三大类对照规则与常见口径
三大类的对照规则,不同地区、不同项目可能存在细微差别,所以下面这张表只能作为最常见口径的参考。我处理过的大多数项目里,农用地、建设用地、未利用地的边界大致是这么切的。
| 三大类 | 典型三调地类 | 判断逻辑 |
|---|---|---|
| 农用地 | 水田、水浇地、旱地、果园、茶园、乔木林地、竹林地、灌木林地、其他林地、天然牧草地、人工牧草地、其他草地 | 耕地、园地、林地整体归农用地,草地多数也归农用地,个别“其他草地”口径需要项目确认 |
| 农用地 | 农村道路、坑塘水面、沟渠、设施农用地、田坎 | 这些虽然属于交运、水域、其他土地大类,但是农业生产配套,一般算农用地 |
| 建设用地 | 零售商业用地、批发市场用地、餐饮用地、旅馆用地、商务金融用地、娱乐用地、工业用地、采矿用地、盐田、仓储用地、城镇住宅用地、农村宅基地 | 商服、工矿仓储、住宅整体归建设用地 |
| 建设用地 | 机关团体新闻出版用地、科教文卫用地、公共设施用地、公园与绿地、风景名胜设施用地 | 公共管理与公共服务整体归建设用地 |
| 建设用地 | 铁路用地、公路用地、机场用地、港口码头用地、管道运输用地、城市轨道交通用地、水工建筑用地 | 交通运输类除农村道路外基本都算建设用地 |
| 未利用地 | 河流水面、湖泊水面、水库水面、沿海滩涂、内陆滩涂、沼泽地、冰川及永久积雪 | 天然水体和滩涂沼泽归未利用地 |
| 未利用地 | 空闲地、盐碱地、沙地、裸土地、裸岩石砾地 | 其他土地大类里除设施农用地和田坎外,多数归未利用地 |
这张表的价值在于把容易混淆的二级类提前标了出来。真正跑过一遍之后你会发现,90%的图斑都是正常的耕地、园地、林地,剩下的10%才是判断难点。尤其是坑塘水面、沟渠、农村道路、水工建筑用地这些,分布不规律但数量不小,一旦归错,面积误差就会被放大。我的建议是,在写脚本之前先把这张表跟项目负责人确认一遍,确认完再动手写代码,避免返工。
2.3 数据准备:先检查这4个细节
规则理完之后别急着写代码,先把数据基础检查一遍,不然脚本跑一半报错,或者跑完了结果不对,排查起来更浪费时间。我总结过四个关键检查点,每次都先过一遍再动手。
- 字段类型和编码完整性:先看DLBM字段是文本型还是整数型。如果是整数型,先确认有没有前导零丢失问题。可以在属性表里筛选几条,比如看水田编码是0101还是101,如果是101,脚本里就要先补零。
- 空值和异常值分布:用属性表的统计功能看一下DLBM字段缺失值数量,再排个序看看有没有非法编码。这个过程能提前暴露数据质量隐患,脚本里也好提前准备“待核实”兜底。
- 数据格式和存储位置:三调数据通常是File Geodatabase里的要素类,直接在要素类上跑脚本最省事。如果是Shapefile,先转成要素类再操作,避免Shapefile字段名长度限制、中文路径乱码等老问题。
- 坐标系和面积字段:三大类转换后一般要统计面积,面积统计依赖要素类自带的SHAPE_Area字段,而这个字段跟数据的投影坐标系直接相关。尽量确认数据是CGCS2000高斯投影,别用WGS84经纬度数据直接算面积,单位不对,结果没法用。
这四个检查点看起来不起眼,但能在后面省下大量排查时间。我印象特别深的一次,就是数据里的DLBM字段是整数型,编码前导零全被吞了,脚本跑完所有图斑都进了“待核实”,我一度以为规则表写错了,排查了半天才发现是字段类型问题。
3. 核心脚本实现:批量转换的代码拆解
3.1 整体逻辑与框架
脚本不需要写得很复杂,核心流程就四步:添加结果字段、读取地类编码、按规则映射分类、写回结果字段。难点不在流程,而在分类规则的实现上。
我采用的是“编码字典精确匹配 + 名称关键词兜底”的双层方案。第一层用DLBM四位编码去查一个预设字典,命中就直接返回三大类,速度快、可解释性强;第二层是兜底逻辑,当某个编码不在字典里时,再去读DLMC名称字段,通过关键词判断方向,最大程度降低漏匹配率。两层都匹配不到的内容,统一输出为“待核实”,这样最后的结果里能直接筛出需要人工确认的少数图斑,而不是把错误静默掉。
为什么不直接用一长串if-elif?因为字典结构更直观,规则调整时只需要改字典内容,不用动主逻辑。比如后期要把“其他草地”从农用地改成未利用地,改一行字典就行,比在一堆条件判断里找分支方便得多。
3.2 完整脚本:可直接复制的实现
下面这个脚本是我实际项目里精简出来的版本,保留了核心功能,把路径和字段抽到了配置区,你拿到之后改四个变量的值就能跑。脚本运行环境是ArcGIS Pro自带的Python 3。
# -*- coding: utf-8 -*- """ 三调地类图斑 -> 土地三大类 批量转换脚本 运行环境:ArcGIS Pro 自带 Python 3 + arcpy """ import arcpy # ============ 参数配置区(每次运行前修改) ============ TARGET_FC = r"C:\gis_workspace\san_diao.gdb\DLTB" # 三调地类图斑要素类 CODE_FIELD = "DLBM" # 地类编码字段 NAME_FIELD = "DLMC" # 地类名称字段 RESULT_FIELD = "SDLB" # 转换结果字段(自动创建) # ====================================================== # 地类编码精确匹配字典(按实际数据补充,未命中的走名称兜底) CODE_RULE = { # 农用地 "0101": "农用地", "0102": "农用地", "0103": "农用地", "0201": "农用地", "0202": "农用地", "0203": "农用地", "0204": "农用地", "0301": "农用地", "0302": "农用地", "0303": "农用地", "0304": "农用地", "0401": "农用地", "0402": "农用地", "0403": "农用地", "1003": "农用地", # 农村道路 "1104": "农用地", # 坑塘水面 "1107": "农用地", # 沟渠 "1202": "农用地", # 设施农用地 "1203": "农用地", # 田坎 # 建设用地 "0501": "建设用地", "0502": "建设用地", "0503": "建设用地", "0504": "建设用地", "0505": "建设用地", "0506": "建设用地", "0507": "建设用地", "0601": "建设用地", "0602": "建设用地", "0603": "建设用地", "0604": "建设用地", "0701": "建设用地", "0702": "建设用地", "0801": "建设用地", "0802": "建设用地", "0803": "建设用地", "0804": "建设用地", "0805": "建设用地", "0901": "建设用地", "0902": "建设用地", "0903": "建设用地", "0904": "建设用地", "0905": "建设用地", "0906": "建设用地", "1001": "建设用地", "1002": "建设用地", "1004": "建设用地", "1005": "建设用地", "1006": "建设用地", "1007": "建设用地", "1109": "建设用地", # 水工建筑用地 # 未利用地 "1101": "未利用地", "1102": "未利用地", "1103": "未利用地", "1105": "未利用地", "1106": "未利用地", "1108": "未利用地", "1110": "未利用地", "1201": "未利用地", "1204": "未利用地", "1205": "未利用地", "1206": "未利用地", "1207": "未利用地", } # 名称关键词兜底,优先级低于编码精确匹配 NAME_RULE = [ # (包含关键词, 返回结果) ("其他草地", "待核实"), # 口径不确定的地类,先人工确认 ("设施农用地", "农用地"), ("农村道路", "农用地"), ("坑塘水面", "农用地"), ("沟渠", "农用地"), ("水工建筑", "建设用地"), ("公路", "建设用地"), ("铁路", "建设用地"), ("机场", "建设用地"), ("河流", "未利用地"), ("湖泊", "未利用地"), ("水库", "未利用地"), ("滩涂", "未利用地"), ("沼泽", "未利用地"), ("空闲地", "未利用地"), ("盐碱地", "未利用地"), ("沙地", "未利用地"), ("裸土地", "未利用地"), ("裸岩石砾地", "未利用地"), ] def classify(dlbm, dlmc): """根据地类编码和名称返回三大类,命中不了输出待核实""" # 第一层:编码精确匹配 if dlbm is not None: code = str(dlbm).strip() if code in CODE_RULE: return CODE_RULE[code] # 第二层:名称关键词兜底 if dlmc is not None: name = str(dlmc).strip() for keyword, result in NAME_RULE: if keyword in name: return result return "待核实" def main(): field_names = [f.name for f in arcpy.ListFields(TARGET_FC)] if RESULT_FIELD not in field_names: arcpy.AddField_management(TARGET_FC, RESULT_FIELD, "TEXT", field_length=20) print("[OK] 已添加结果字段: %s" % RESULT_FIELD) else: print("[提示] 结果字段已存在,将直接写入值") updated = 0 pending = 0 with arcpy.da.UpdateCursor(TARGET_FC, [CODE_FIELD, NAME_FIELD, RESULT_FIELD]) as cursor: for row in cursor: result = classify(row[0], row[1]) row[2] = result cursor.updateRow(row) updated += 1 if result == "待核实": pending += 1 print("[完成] 共处理 %d 个图斑,待核实 %d 个" % (updated, pending)) if __name__ == "__main__": main()这段脚本的逻辑不复杂,但有一个设计点我觉得很关键——待核实机制。实际数据里永远会有你没预料到的编码或名称组合,与其让程序自作主张归到某一类,不如标记成“待核实”,最后统一筛选出来人工判断。这样做虽然多了一道人工工序,但避免了系统性错误悄悄混进结果里。
3.3 性能优化与更新策略
脚本默认用UpdateCursor逐行更新,这套方式在单机File Geodatabase上跑十几万个图斑,速度通常都在可接受范围内。但如果你面对的是百万级图斑的省级库,或者数据源是企业级地理数据库,就需要提前想一下性能问题。
先从最简单的情况说起,数据量在20万以下时,直接跑上面的脚本就行。20万以上,建议先做一个抽样测试,用arcpy.Select_analysis筛出前几百个图斑试跑,确认规则映射和字段写入都没问题后,再全量执行。另一种优化思路是先用SearchCursor把全表的“编码→结果”映射关系算出来,再配合UpdateCursor快速写入,减少不必要的重复判断逻辑。
企业级地理数据库里,尤其是开了版本化的数据,直接跑UpdateCursor会触发版本化冲突检测,速度会慢很多。我的做法是先复制一份非版本化数据到本地File Geodatabase,在副本上跑完验证无误后,再通过Arcpy的追加或截断操作把结果同步回去。还有一个更直接的方案,如果数据库允许裸SQL更新,可以直接写SQL来完成批量赋值,速度能快一个数量级,但使用SQL更新要非常小心,操作前必须备份,而且ArcGIS的数据字典和元数据不会自动更新,有风险。
性能优化这件事,核心原则是“先小样本验证、再全量执行”,别一上来就拿全库数据试错。
4. 在ArcGIS Pro里跑通全流程
4.1 运行环境和入口选择
这一步没有太多技术门槛,关键是把运行入口选对。ArcGIS Pro自带的Python环境就在安装目录下,不熟悉的人可能找半天,其实打开ArcGIS Pro以后,最快捷的方式是直接用软件内置的Python窗口,把脚本粘贴进去运行,适合快速验证。要是想写正式脚本文件,则推荐在外部IDE里写好,然后再用ArcGIS Pro的“运行Python脚本”工具来执行。
我个人建议的路线是:先在Pro的Python窗口里把脚本逻辑调通,尤其是规则字典和待核实数量是否符合预期,确认没问题后,再把脚本保存成.py文件,后面所有项目都直接调用这个文件。这样既能快速迭代,又能保证最终交付物是一个可复用、可版本管理的脚本。
如果希望连代码都不用打开,可以做成ArcGIS Pro脚本工具。新建一个工具箱,添加脚本,设置好要素类、编码字段、名称字段、结果字段这四个参数,保存成功后,同事以后只需要在工具箱面板里双击工具、选择图层、点击运行即可,门槛大幅降低。
4.2 分步操作:从打开Pro到跑完检查
我按实际操作的顺序把步骤拆出来,你一步一步跟就行。
- 打开ArcGIS Pro,新建工程,把三调地类图斑要素类加载到地图里。
- 打开属性表,先确认DLBM和DLMC字段存在,手动翻几行记录,看看编码格式是否完整。
- 打开Python窗口,把脚本主体粘贴进去,修改TARGET_FC为你的要素类完整路径。
- 执行脚本,等待输出统计信息。正常几十秒内就能跑完几万条记录。
- 刷新属性表,新增的SDLB字段已经带上值,右键字段用“符号化”按唯一值显示,地图上会画出三大类分布。
- 执行面积统计,用arcpy.analysis.Statistics按SDLB字段汇总面积,跟台账数据对比,误差在合理范围内就说明转换成功。
这里尤其要提一下“跑完用符号化看看”这个习惯。很多时候你光看属性表数字是看不出问题的,但地图上如果某些图斑颜色分布明显不对劲,比如城镇边缘出现大片未利用地,那就是规则有遗漏,需要马上回去补字典。空间分布检查比纯属性检查更敏感。
4.3 进阶:把脚本封装成自定义工具
封装成脚本工具属于加分项,但确实能省下大量重复劳动。具体操作不多说,核心是记得设置参数时,要素类变量要设成Feature Layer或Feature Class类型,地类编码字段和名称字段要设成Field类型,结果字段可以设成String类型并给个默认值“SDLB”。
封装好之后,工具会出现在自定义工具箱里。下次拿到新数据,只需要在ArcGIS Pro里打开工具,把图层一拖一选,点击运行,结果字段自动加好。这样团队里不熟悉Python的同事也能用它,再也不用来回传代码、改路径。
5. 常见问题与排查技巧实录
5.1 字段加不上、值算不了
遇到“添加字段失败”或者“更新值失败”时,先检查数据源的写权限。File Geodatabase里的要素类一般不会有权限问题,但企业级数据库里经常遇到只读账号,或者要素类本身被其他用户锁定。还有一个高频问题,就是RESULT_FIELD字段名已经存在但类型不对,比如之前手工建过一个短整型字段,脚本再想以文本类型添加就会报错。代码里虽然做了存在性判断,但没检查类型,稳妥起见在配置区换一个新字段名。
“明明改了配置区路径但还是报错”也是常见问题。ArcGIS Pro里面复杂路径经常包含反斜杠和中文字符,字符串前一定要加r前缀,否则反斜杠会被转义成别的含义。路径里有中文时,最好确认文件系统编码是UTF-8,虽然ArcGIS Pro 3.x对中文路径支持已经很好,但偶尔遇到特殊符号时还是需要注意。
5.2 数据量大、更新卡顿
更新特别慢的时候,第一反应不要急着调代码,先看数据源是不是在SDE上。SDE数据跑逐行更新,每条记录都要经过版本化检查,慢是正常的。解决办法是先复制到本地File Geodatabase,跑完再导回去。如果本地也慢,重点检查是否开着ArcGIS Pro的编辑会话,编辑状态下UpdateCursor的每一次更新都会被记录在编辑事务里,数据量一大就会卡。脚本执行前退出编辑模式,速度能提升不少。
还有一种情况是被数据源锁住了,比如要素类在ArcMap或者另一个ArcGIS Pro实例中被打开,也会导致更新操作排队等待。跑批前最好把数据源相关的工程全部关掉,留一个Pro实例专门执行脚本。
5.3 跑错了怎么恢复:备份与回滚
这个经验我特别想强调,跑批前一定要备份,这不是形式主义。最简单的方式就是在脚本开头加一行复制要素类的代码,把原始数据备份到另一个要素类里,跑完如果发现问题,直接拿备份覆盖回去重新来。
我之前吃过一次亏,当时数据量不大,觉得没必要备份,直接在原要素类上跑了转换脚本。结果规则字典里有两个编码写反了,导致一批图斑归错类,又因为已经做过其他操作,只能手动一条条改回去,浪费了整整一天时间。之后我所有的批处理脚本都会先自动复制一份带时间戳的备份,成本低、收益高,值得养成习惯。
备份之外,脚本里还有个习惯可以借鉴,就是在写回结果前先统计“待核实”数量,如果待核实数量突然比上一次跑高很多,大概率是新数据的编码规则有变化,先停手检查,而不是继续往下跑。
6. 对几个易混淆地类的补充判断
我在前面对照表里留了一些伏笔,比如“其他草地”、“水库水面”、“农村道路”这些地类,实际操作中口径经常不一样,这里单独拿出来多说几句。
“其他草地”是最典型的一个争议点。从农业生产角度看,它跟天然牧草地、人工牧草地放在一起,很多口径默认归农用地;但从利用现状看,它往往集中在水源条件偏差的区域,植被覆盖度低,有些项目按未利用地处理。这个没有绝对的对错,只能看项目所在地区技术规定怎么要求。脚本里我把“其他草地”单独标成“待核实”,就是逼着自己去确认口径,而不是默认一种规则用到黑。
“水库水面”也有类似情况。传统土地利用现状分类里,水库水面属于水域及水利设施用地,从规划用途角度有些人倾向归未利用地,但如果这个水库是农田灌溉配套工程,部分地区又会把它算作农用地或水利设施用地。我一般会根据项目的实际用途判断,比如建设用地报批范围里的水库,通常跟水利建设用地合并考虑;单独做耕地保有量分析时,水库水面一般不计入农用地。
“农村道路”和“沟渠”在三调数据里数量不小,但面积往往都不大,很多处理人员会顺手忽略。如果整个项目只是粗口径分析,可能影响不大;但如果做地类面积平差或者对比历年数据,这些小图斑累加起来的面积也相当可观。脚本里已经把它们明确归进农用地,实际操作时只要按照项目口径检查一下这个设置就行。
同类问题的还有“设施农用地”与“田坎”。设施农用地是农业生产设施占地,归农用地没问题;田坎也是耕地配套的一部分,归农用地同样合理。但要注意,田坎在三调数据里如果单独成图斑,面积一般很小,统计口径里往往跟周围的耕地合并计算,跑完脚本后可以顺手看一下田坎图斑数量,如果异常多,可能是建库时碎图斑过多,需要用融合工具处理一下。
7. 个人实操中的几点体会
用这套流程处理过几次真实项目之后,我最深的体会是“规则先行、数据兜底”。很多刚接触这个任务的人会先写代码,再去套规则,结果规则跟实际数据对不上,来回改脚本。正确顺序应该是先导出数据里所有不重复的地类编码,逐个确认归属,规则的准确性要优先于代码的运行效率。
脚本本身并不复杂,真正有价值的部分反而是那张对照表和对异常数据的处理机制。如果你所在单位有内部统一的三调转三大类对照标准,建议直接维护一个CSV或Excel表,脚本改成读表匹配的模式,这样以后标准更新时,只需要发一张新表下去,所有项目组拿新表重新跑一遍就行。
最后再分享一个我自己的小习惯:每次跑完转换,我都会把“待核实”图斑单独导出来发一份给专业人员确认,确认结果再手工修正。这样既保证了大批量处理的效率,又把人工审核聚焦到真正需要判断的少数图斑上,两边都不耽误。希望这套方法也能帮你从“手动对照”的坑里解放出来。