
干了多年国土数据处理最怕听到的一句话就是“这批三调数据要转成土地三大类你今天赶一下。”以前每次接到这种活我的流程都是打开ArcGIS Pro加载地类图斑对着DLBM字段一行行查编码、翻分类标准、再填新字段。县里的图斑少说两三万个多的时候十几万个手动对照一搞就是大半天眼睛看花了还容易把“建设用地”填成“农用地”。后来我实在受不了写了个Python脚本丢给ArcGIS Pro去跑几分钟出结果还能顺手统计各分类面积。这篇就把脚本思路、完整代码和踩过的坑都整理出来给同样被手工对照折磨过的朋友一条捷径。1. 为什么要把三调地类转成“土地三大类”手动对照到底错在哪1.1 三大类和详细地类的关系第三次全国国土调查成果里的地类图斑表达得非常细。同一块地里水田、水浇地、旱地要分开果园和茶园也要分开甚至连农村道路、沟渠、田坎都有单独的图斑。这种精度对调查统计是好事但对规划、报批、耕地保护这类业务来说口径又显得太细了。很多场景下上级要的是“农用地、建设用地、未利用地”三大类的汇总结果于是就需要把细化的三调地类归并到三个大类里。这个转换本身不复杂复杂的是量大。一个普通县的图斑数一般在3万到10万之间地类编码林林总总有几十上百个。每个图斑都要判断一次“这个编码属于哪一类”判断的依据又分散在好几份标准文件里特别适合写成脚本去处理。1.2 手动流程的四个致命问题手动操作看起来很简单打开属性表按DLBM字段排序对照分类表再在新增字段里填“农用地”或者“建设用地”。实际操作起来问题非常现实。一是效率低。假设一个图斑从看到编码到填完值需要10秒3万个图斑就是30万秒差不多8个多小时而且这中间还不包括中途走神、接电话、重启软件的时间。二是容易错。地类编码并不是“01开头全是农用地”这么简单交通运输用地里的1006农村道路算农用地1001铁路却算建设用地其他土地里的1202设施农用地算农用地1201空闲地又算建设用地。这种特殊情况特别多人眼连续看几百行之后出错率会直线上升。三是难复核。手动填完之后想检查哪些图斑填错了只能再从头扫一遍或者做随机抽样根本做不到全量核查。四是不可复用。下周又来一批更新数据同样的活再干一遍之前的工作成果一点都沉淀不下来。1.3 自动化的实际收益写个脚本跑一遍本质上是把“人眼对照”变成“字典映射”。脚本的核心逻辑不复杂读每个图斑的地类编码去字典里查它属于哪一类然后把结果写回新字段。好处是肉眼可见的3万个图斑大概1到3分钟跑完只要映射字典没问题准确率是100%跑完还能自动打印一份面积统计哪个大类有多少公顷一目了然下次数据更新了换条路径重跑一遍就行。2. 地类编码映射表先跟分类口径较真代码才写得稳2.1 三调地类编码的结构三调的地类编码通常是4位数字前两位是一级类后两位是二级类。比如0101表示“水田”0103表示“旱地”1006表示“农村道路”。这种编码规则意味着我们既可以用完整的4位编码去精确匹配也可以用前两位做粗略归类。但这套编码有一个非常坑的地方一级类内部并不是铁板一块。以“10交通运输用地”为例1001到1005基本都算建设用地唯独1006农村道路算农用地以“11水域及水利设施用地”为例1109水工建筑算建设用地1107沟渠又算农用地以“12其他土地”为例1201空闲地算建设用地1202设施农用地却算农用地。所以写脚本时如果只判断前两位必然出错。2.2 常见的“三大类”映射参考表我根据实际项目经验整理了一份常见的映射关系直接做成了表格。需要特别说明的是这份表只代表我接触过的通用口径不同省份、不同项目可能有细微差异动手前一定要先跟业主或技术文件核对清楚。地类编码地类名称归结大类0101水田农用地0102水浇地农用地0103旱地农用地0201果园农用地0202茶园农用地0203橡胶园农用地0204其他园地农用地0301乔木林地农用地0302竹林地农用地0303灌木林地农用地0304其他林地农用地0401天然牧草地农用地0402沼泽草地农用地0403人工牧草地农用地0404其他草地未利用地0501-0504商服用地建设用地0601-0603工矿仓储用地建设用地0701城镇住宅用地建设用地0702农村宅基地建设用地0801-0805公共管理与公共服务用地建设用地0901-0905特殊用地建设用地1001铁路用地建设用地1002公路用地建设用地1003机场用地建设用地1004港口码头用地建设用地1005管道运输用地建设用地1006农村道路农用地1101河流水面未利用地1102湖泊水面未利用地1103水库水面未利用地1104坑塘水面农用地1105沿海滩涂未利用地1106内陆滩涂未利用地1107沟渠农用地1108沼泽地未利用地1109水工建筑用地建设用地1110冰川及永久积雪未利用地1201空闲地建设用地1202设施农用地农用地1203盐碱地未利用地1204沙地未利用地1205裸土地未利用地1206裸岩石砾地未利用地2.3 最容易翻车的水域、交通和“其他土地”表格里已经能看出规律最容易翻车的就集中在10、11、12这三个一级类里。手动操作时很多人习惯性地认为“交通运输都是建设用地”结果农村道路全部归错“水域都是未利用地”结果沟渠、坑塘全部归错。这正是脚本要重点照顾的地方。我在代码里做了两层设计第一层用完整的4位编码去匹配匹配不上就用前两位去查“05、06、07、08、09”这些整体归入建设用地的类别。这样既保证了特殊编码能精准归类又不会漏掉那些一级类整体归入某一类的编码。映射表本身不写死你可以按本地口径随意增删。2.4 映射表可配置别把规则写死我见过不少人直接在字段计算器里写Python表达式把映射关系硬编码在表达式里。这种做法的最大问题是不可维护项目口径一变就要重新打开表达式在一长串代码里找需要改的编码眼睛都能看花。我的做法是把映射关系集中放在脚本开头的配置区甚至可以直接外置成一个CSV文件脚本启动时读取。改规则只改一张表任何同事都能看懂。这也是脚本工具能反复复用的基础。3. 环境准备让ArcGIS Pro自带的Python环境先跑起来3.1 ArcGIS Pro的Python环境在哪ArcGIS Pro自带了一个完整的Python环境arcpy模块就装在里面不需要额外安装。不同版本的Pro对应的Python版本略有不同Pro 3.x通常自带Python 3.9或更高版本。这个环境一般位于C:\Program Files\ArcGIS\Pro\bin\Python\envs\arcpypro-py3\python.exe要确认环境是否正常最快的办法是打开ArcGIS Pro在界面底部的“Python”窗口里输入import arcpy print(arcpy.GetInstallInfo()[Version])如果能打印出版本号说明arcpy环境可用。3.2 三种运行方式怎么选把脚本跑起来的方式主要有三种我实际用下来的感受是前期调试用外部IDE稳定后直接做成脚本工具。运行方式优点缺点适合场景Pro内嵌Python窗口零配置直接执行大数据量跑起来卡界面断点调试不便几十个图斑的小测试外部IDEPyCharm/VSCode配置arcpy解释器便于调试、看日志、管理代码需要手动把解释器指到Pro的python.exe开发脚本、处理复杂逻辑ArcGIS Pro工具箱脚本工具可视化参数方便交给同事创建参数比较繁琐实际业务交付、重复使用如果你习惯用PyCharm或VSCode不需要安装任何额外Python包只要在设置里把解释器指向上面那个python.exe即可。有一点要注意不要跑到系统Python里pip install arcpyarcpy不支持这样安装它必须跟ArcGIS Pro的授权环境绑定。3.3 先用一段最小代码确认arcpy能读数据正式写长脚本之前我建议先用一段最小代码验证数据能不能读通。很多人在最后阶段才发现路径不对、字段名写错白等了半天。最小验证代码如下import arcpy fc rC:\data\san_tiao.gdb\DLTB print(要素类存在, arcpy.Exists(fc)) print(字段列表, [f.name for f in arcpy.ListFields(fc)]) with arcpy.da.SearchCursor(fc, [DLBM, SHAPEAREA]) as cursor: for i, row in enumerate(cursor): if i 5: break print(row)这段代码能确认三件事路径是否可访问、DLBM字段名是否准确、以及编码的值长什么样。特别是最后一点编码究竟是带前导0的字符串还是被存成了整型直接影响后面脚本怎么写。4. 一键脚本完整实现映射表驱动UpdateCursor逐图斑计算4.1 代码设计思路整个脚本的设计思路可以概括成一句话用字典建立“地类编码→三大类”的映射用UpdateCursor逐行读取编码、查字典、写回结果。为什么不推荐用“字段计算器”字段计算器适合做简单的数学计算和格式化但要做“查字典、收集未知编码、统计面积、打印报告”这几件事它就很别扭。UpdateCursor则可以把所有逻辑放在一个循环里中间还能随时打印进度、收集异常情况完全可控。脚本的运行流程是检查字段是否存在避免重复添加字段合并基础映射表和扩展码映射表遍历所有图斑规范化编码后查字典对无法匹配的编码单独提示最后按分类统计图斑面积并打印报告。每一步都有输出跑完不用再打开属性表人工核对。4.2 完整脚本# -*- coding: utf-8 -*- 三调地类图斑 → 土地三大类 自动转换脚本 运行环境ArcGIS Pro 3.x (Python 3.9/3.11 arcpy) import arcpy # 配置区 # 地类图斑要素类支持要素数据集、shapefile、SDE要素类 fc rD:\project\san_tiao\san_tiao.gdb\DLTB # 地类编码字段名三调库常见为DLBM code_field DLBM # 要生成的字段名 type_field 土地三大类 # 映射表地类编码 - 三大类 # 以下映射为常见口径实际项目请先与业主或技术文件核对 land_type_map { # 耕地 0101: 农用地, 0102: 农用地, 0103: 农用地, # 园地 0201: 农用地, 0202: 农用地, 0203: 农用地, 0204: 农用地, # 林地 0301: 农用地, 0302: 农用地, 0303: 农用地, 0304: 农用地, # 草地 0401: 农用地, 0402: 农用地, 0403: 农用地, 0404: 未利用地, # 商服、工矿、住宅、公共管理与公共服务、特殊用地整体归建设用地 05: 建设用地, 06: 建设用地, 07: 建设用地, 08: 建设用地, 09: 建设用地, # 交通运输用地 1001: 建设用地, 1002: 建设用地, 1003: 建设用地, 1004: 建设用地, 1005: 建设用地, 1006: 农用地, # 水域及水利设施 1101: 未利用地, 1102: 未利用地, 1103: 未利用地, 1104: 农用地, 1105: 未利用地, 1106: 未利用地, 1107: 农用地, 1108: 未利用地, 1109: 建设用地, 1110: 未利用地, # 其他土地 1201: 建设用地, 1202: 农用地, 1203: 未利用地, 1204: 未利用地, 1205: 未利用地, 1206: 未利用地, } # 扩展码单独处理三调数据里常出现类似0101K、0303K这种带后缀的编码 extend_code_map { 0101K: 农用地, 0303K: 农用地, 0404K: 未利用地, } # 主程序 def main(): arcpy.env.overwriteOutput True if not arcpy.Exists(fc): print(要素类不存在{}.format(fc)) return # 1. 检查目标字段避免重复添加 field_list [f.name for f in arcpy.ListFields(fc)] if type_field in field_list: print(字段 {} 已存在跳过添加。如需重新计算请先删除该字段。.format(type_field)) else: print(正在添加字段 {} ....format(type_field)) arcpy.AddField_management(fc, type_field, TEXT, field_length10) # 2. 合并映射表 mapping dict(land_type_map) mapping.update(extend_code_map) # 3. 遍历更新 unknown_codes set() area_stat {} count 0 print(开始遍历更新 ...) with arcpy.da.UpdateCursor(fc, [code_field, type_field, SHAPEAREA]) as cursor: for row in cursor: raw_code row[0] # 规范化编码兼容整型地类码补齐前导0 if raw_code is None: code elif isinstance(raw_code, (int, float)): code str(int(raw_code)).zfill(4) else: code str(raw_code).strip() if code in mapping: land_type mapping[code] else: # 匹配不到时尝试用前两位匹配05/06/07/08/09 prefix code[:2] if prefix in mapping: land_type mapping[prefix] else: land_type 未分类 unknown_codes.add(code) row[1] land_type cursor.updateRow(row) # 面积统计 area row[2] or 0 area_stat[land_type] area_stat.get(land_type, 0) area count 1 print(完成共处理 {} 个图斑.format(count)) if unknown_codes: print(以下地类码未匹配到分类请在映射表中补充) for c in sorted(unknown_codes): print( , c) else: print(所有地类码均匹配成功。) # 4. 输出面积统计 print(\n各分类面积统计单位平方米 / 公顷) total_area 0 for land_type, area in area_stat.items(): print( {}{:.2f} 平方米 {:.2f} 公顷.format(land_type, area, area / 10000.0)) total_area area print( 合计{:.2f} 公顷.format(total_area / 10000.0)) if __name__ __main__: main()4.3 几个关键细节字段检查、前导零、前缀匹配代码里藏了几个我踩过坑之后才加进去的细节单独拎出来说一下。字段检查那段非常重要。脚本跑第二次的时候字段早就存在了如果再调AddField_management会直接报错。所以每次先读一次字段列表存在就跳过不存在才添加。前导零处理是大多数人都想不到的坑。如果原始地类编码字段是字符串那“0101”没毛病但有些三调库在建库时把DLBM字段做成了整型存进去就变成了101显示的时候看不出问题一旦你用字符串去字典里查永远匹配不上。代码里的zfill(4)就是干这个的。前缀匹配是为了兼容“05、06、07、08、09”这种整体归入建设用地的类别。我不需要把0501、0502、0503全部写在字典里只要在映射表里写上“05”查不到完整编码时自动用前两位去匹配。这样映射表更短维护成本更低。4.4 面积统计和未知编码提示跑完脚本之后光在属性表里看到字段填上了还不算完我还需要一份面积汇总用来验收和出报告。代码里在UpdateCursor循环中顺手累加了每个大类的SHAPEAREA最后打印出来。这样一份“农用地多少公顷、建设用地多少公顷、未利用地多少公顷”的统计直接就有了。未知编码提示是我觉得最有用的设计之一。如果数据里混进了一个映射表没覆盖的编码脚本不会静默漏掉而是把编码收集起来最后统一打印。比如我第一次跑某地的数据时提示出现了“0101K”“0303K”这类扩展码我回去一查才知道是特殊标注地类把它们补进扩展码映射表就完事了。5. 跑在真实数据上的几个坑字段类型、扩展码、面积统计5.1 坑一地类编码是整型0101只剩101这是我接手一个县域数据时真实碰到的。打开属性表DLBM字段显示的是101、103、201而不是0101、0103、0201。一开始我没在意脚本跑完一看几千个图斑全变成“未分类”才意识到是字段类型的问题。解决办法就在代码里统一用str(int(raw_code)).zfill(4)把编码补成4位。这样不管原始字段是整型、浮点型还是字符串都能得到“0101”这样的标准编码再去查字典。5.2 坑二字段已存在就想报错脚本第一次运行很顺利第二次运行就报错让我一度以为代码有bug。后来发现是AddField_management在字段已经存在时直接抛异常。对单次操作来说无所谓但脚本一旦要做成工具反复使用就必须处理这种情况。我的解法是在添加字段之前先遍历一遍现有字段。如果字段存在就跳过添加直接进入更新逻辑。想重算的时候手动删掉字段再跑或者加一个“是否强制重建”的参数都是更灵活的扩展。5.3 坑三扩展码和混合地类三调数据里有不少带后缀的扩展编码比如“0101K”这类字典里只有“0101”就匹配不上。另外还有极少数图斑是混合地类比如“0101/0103”这种写法一个图斑里同时出现两个地类编码。面对扩展码最合理的做法是把它们单独维护一个扩展字典跟基础映射分开将来标准变了也好找。面对混合地类需要先判断项目要求是按主编码算还是按面积占比最大的算。如果数据里真有这种我建议单独抽出来人工处理脚本负责把这类特殊图斑识别出来、列清单而不是自作主张给一个可能错误的分类。5.4 坑四SHAPEAREA统计面积的前提SHAPEAREA在多边形要素类里可以直接取到要素面积但它的单位跟数据的坐标系强相关。三调数据建库一般用CGCS2000高斯投影面积单位是平方米统计结果没问题。如果数据是地理坐标系单位是度或者要素类没有正确投影那统计出来的“面积”数值会非常离谱。解决办法是跑脚本之前先看一眼数据框属性或者坐标系信息确认是投影坐标系。如果源数据是地理坐标可以先用arcpy.management.Project投影一遍再处理或者在统计时用arcpy.management.CalculateGeometryAttributes按投影后面积字段计算。5.5 坑五数据被锁定更新写到一半如果要素类正在ArcGIS Pro中编辑、或者被其他进程打开UpdateCursor更新时会被锁定卡在某个图斑上不是报错就是写不进去。更麻烦的是某些SDE版本数据对长事务支持不好中途失败很难回滚。所以我执行脚本前会习惯性关掉其他编辑会话脚本里也会在开头打印时间和数据路径方便排查。如果处理的是企业级地理数据库数据建议先复制一份本地文件地理数据库副本跑通了再回写到SDE速度快很多风险也小很多。5.6 跑完怎么验收脚本跑完不是结束我至少要做三重验证。第一重看脚本打印的统计。农用地、建设用地、未利用地的面积加起来应该约等于图斑总面积误差不能超过1平方米量级否则说明有图斑没更新成功。第二重随机抽查。用选择工具随机挑几十个图斑人工对照属性表看是不是填对了重点看农村道路、沟渠、设施农用地这些容易出错的编码。第三重从其他维度交叉验证。比如调出上一年度的三大类统计表对比总面积和各分类占比。如果数据本身没发生大的变化几次统计之间的比例应该是吻合的。若偏差巨大基本可以断定映射口径有问题。6. 从脚本到能用一辈子的工具批量、参数化与结果验证6.1 封装成ArcGIS Pro脚本工具脚本调试稳定之后我一般会把它封装成ArcGIS Pro的工具箱脚本工具。这样双击就能打开参数对话框输入要素类、选择映射表、点确定就跑完不需要同事打开代码编辑器内容。封装过程不算复杂在“目录”窗格里右键工具箱添加脚本把fc、code_field、type_field改成脚本工具参数即可。脚本工具的参数有校验能力比如要素类字段必须存在才允许点确定从源头上避免了低级错误。6.2 批量处理多个GDB一个项目经常有多个县的数据每个县一个GDB里面都有地类图斑。我通常会在外面套一层循环遍历目录下所有GDB逐个执行转换。核心是arcpy.da.Walk可以递归获取所有要素类路径import arcpy import os gdb_folder rD:\project\san_tiao for dirpath, dirnames, filenames in arcpy.da.Walk(gdb_folder, datatypeFeatureClass): for filename in filenames: fc_path os.path.join(dirpath, filename) if filename DLTB: print(正在处理, fc_path) # 在这里调用前面main()里的核心处理逻辑这里再配合一个专门的字段看到“DLTB”再处理避免把其他无关要素类也扫进去。如果是几十个GDB建议先跑一遍脚本生成日志跑完统一查日志。6.3 再往后规则表外置、与Excel联动、定时重算脚本再进化一点就是把映射表外置成CSV。这样每次项目口径变了只需要打开Excel改表不用改代码。让不懂代码的同事也能参与维护。code,name,land_type 0101,水田,农用地 0102,水浇地,农用地land_type_map可以直接从CSV读取脚本里加几行csv.DictReader就行。三大类面积统计结果也可以同步导出成Excel表或者用arcpy.management.TableToTable转出属性表后续做专题图、写报告都很方便。如果地类数据是定期更新脚本还可以做成定时任务。ArcGIS Pro自带的任务调度能力有限但Windows计划任务完全可以定时调起一个Python命令行脚本配合日志文件实现无人值守重算。到这个阶段你已经不是“在处理数据”而是“在维护一条自动化的数据处理流水线”了。从我自己的经验看这种数据转换类脚本最大的价值不只是省时间而是把所有判断规则沉淀成了可配置、可审计、可重复使用的东西。手动对照的年代做完一个项目经验全在脑子里用脚本以后经验全在映射表和代码里。下次遇到同类任务哪怕换一批数据、换一个地区十几分钟就能交付。如果你们单位也有类似的三调转三大类需求直接拿这个脚本改改映射表和路径就能用。等你把它做成工具丢给同事你会发现最开心的不是省下的那几小时而是终于不用再半夜接到电话说“有个图斑的地类填错了明天要重新报”了。