先交代背景。前段时间有个同行发来一批零散地块要我帮他在 ArcGIS 里把几十个 Polygon 合并成一个完整的 Polygon 范围开口就问“有没有现成代码”。这其实是再典型不过的“多个 polygon 合并为一个 polygon”需求——在 ArcGIS 里对应的是“融合Dissolve”工具在 Python 脚本里核心就是arcpy.Dissolve_management。这篇文章不打算丢一句命令给你就完事而是把背后那层逻辑讲清楚什么时候该写哪句代码、参数为什么这么填、碰见奇奇怪怪的报错又该怎么查。适合正在折腾数据处理脚本的人也适合刚从工具箱往 Python 迁移的同学。1. 动手前先分清多个面合并你属于哪种情况很多人搜“多个 polygon 合并为一个 polygon 的代码”时会发现网上答案五花八门看起来都能用但一跑就不是自己想要的结果。问题出在中文里的“合并”这个词在 GIS 里对应了至少三种完全不同的操作。1.1 同一图层里的多个面合成一个要素用 Dissolve 融合这是最常见的字面需求你有一个要素类里面有多条相邻或者重叠的面记录想把它们之间的公共边界消掉最终得到一条记录、一个整体图斑。比如把上百个小地块合成一块宗地或者把零碎的小区面合并成社区范围。这种操作叫“融合”英文叫 DissolveArcPy 接口是arcpy.Dissolve_management。官方定义很简单按指定字段或整体将相邻、重叠或同一属性的面要素合并为一个要素并可以选择在合并时计算统计信息。听起来很好理解但实际执行之后很多人会愣一下为什么属性表没了我原来的字段为什么有些面合并后看起来还是一个整体但部件很多这些我都放在后面章节展开。现在你只需要记住一旦需求描述里有“消除内部边界”“变成一个整体面”你先想到 Dissolve 就对了。1.2 多个独立要素类合成一个要素类用 Merge 或 Append 再融合第二种场景长得也很像“合并”比如你从不同部门拿来了几十个 SHP 文件每个文件里都有几个地块面你希望最后只有一个面文件。很多人第一反应是 Merge点了工具才发现图层里面的地块还是一个个的内部边界依然存在。这是因为 Merge合并和 Append追加干的事情是“把多个输入数据集中的要素复制到一个输出要素类里”它只是把数据堆到一起不会消除任何几何边界。正确的写法通常是两步先用arcpy.Merge_management把这些要素类合到一个临时要素类里再用arcpy.Dissolve_management把它们整体融合成一个 Polygon。如果数据量不大也可以先用工具箱里的 Merge 按钮把文件合在一起再拿我的脚本去跑 Dissolve效果完全一样。关键要理解的是Merge 只是搬运Dissolve 才是真正做“粘合”的那一步。1.3 把分散面变成外轮廓范围用最小边界几何和聚合面还有一种隐藏需求标题里也说“合并成一个 polygon”但真正想要的是“把一堆离散的地块包出一个总范围轮廓”。比如你手上有很多不在同一个位置的小块地四周有大量空白你想得到它们的外轮廓边界用来做用地范围线、汇报底图。这种情况下你不能直接 Dissolve因为 Dissolve 只能把相邻或重叠的面融合对于互不相连的面它会给一个多部件MultiPart要素但绝不会自动把你中间的空隙填平。如果你要的是“外轮廓包络”ArcPy 里的arcpy.MinimumBoundingGeometry_management可以生成最小边界几何参数里选CONVEX_HULL或者RECTANGLE_BY_AREA如果希望逻辑上尽量贴近原有地块边界更合适的是“聚合面Aggregate Polygons”工具。把这个问题放在最前面讲是因为方案选错后面全白干。遇到需求先别急着贴代码先问一句你要的是粘成一个整体还是只要一个大外边框2. 核心代码用 ArcPy 实现 Polygon 融合的几种写法现在进入正题。下面这几个版本的代码都是我实际项目里改出来的按需求从简单到复杂排。所有示例默认你已经能打开 ArcGIS 自带的 Python 窗口或者 ArcGIS Pro 内置的 Python 环境。2.1 最基础版所有面不分字段直接融成一个要素先看最朴素的需求一个要素类里不管有多少面也不管什么属性我一口气全部融合成一个整体。代码非常短import arcpy arcpy.env.overwriteOutput True in_fc rC:\data\模拟项目X.gdb\parcel out_fc rC:\data\模拟项目X.gdb\parcel_dissolved arcpy.Dissolve_management(in_fc, out_fc)第一行overwriteOutput True一定要养成习惯写上。ArcGIS 默认不允许覆盖已有输出第二次跑脚本如果输出要素类已经存在直接报 ERROR 000725。有了这个设置你会少挨很多冤枉报错。Dissolve_management不指定融合字段默认就是把输入的全部面要素合成为一个输出要素。这时候有几个结果特征你需要心里有数输出要素类里只有一条记录输出字段基本不带源属性字段只保留几何和 OBJECTID如果输入的面之间有缝隙、彼此不相邻输出会是一个多部件要素而不是一个连续的实心面。在实际项目中我经常把输出先加载进地图看一遍如果你看到一条记录但图形上有很多“孤岛”那就是多部件。这不是程序有问题是数据本身的几何关系决定的。2.2 按字段融合一组要素合并为一个 Polygon大部分业务场景不会真的把全部面都揉成一团而是需要按某个属性进行分类融合。举个例子地块数据里有一个字段叫land_type里面记录着“居住”“商业”“工业”等类型你想把相同类型的地块合并成一个 Polygon同时输出结果里保留这个类型字段。代码如下import arcpy arcpy.env.overwriteOutput True in_fc rC:\data\模拟项目X.gdb\land_parcel out_fc rC:\data\模拟项目X.gdb\land_parcel_by_type arcpy.Dissolve_management( in_fc, out_fc, dissolve_field[land_type], multi_partMULTI_PART )注意dissolve_field我写的列表形式这在 ArcPy 里是可以接受的传单个字符串land_type也可以但当你要融合多个字段时列表会更清晰。multi_partMULTI_PART的意思是允许输出要素是多部件这是绝大多数面融合场景想要的默认行为。如果你写SINGLE_PART系统会尽量把不相连的部分拆成多个要素记录那样输出就不是“一个 Polygon”了。还有个容易踩的细节不指定dissolve_field时不会保留任何源属性字段指定了融合字段输出里也只会保留这个融合字段。如果你还有其他字段想带出来比如每个地块负责人的名字那要么把它也加进dissolve_field但要小心结果会按多个字段组合分组拆得更碎要么用接下来这招统计字段。2.3 融合时自动统计面积和数量融合过程中最常用的附属需求就是统计面积。比如把同类型地块融合后我想直接算出每一类地块的总面积、一共有多少块这就不用再单独跑一次字段计算直接在 Dissolve 里一步完成。import arcpy arcpy.env.overwriteOutput True in_fc rC:\data\模拟项目X.gdb\land_parcel out_fc rC:\data\模拟项目X.gdb\land_parcel_stat stat_fields [ [Shape_Area, SUM], [FID, COUNT] ] arcpy.Dissolve_management( in_fc, out_fc, dissolve_field[land_type], statistics_fieldsstat_fields )statistics_fields的格式是[[字段名, 统计方式], [字段名, 统计方式]]常用的统计方式有SUM、MEAN、MIN、MAX、FIRST、LAST、COUNT。如果你的源数据是文件地理数据库里的要素类Shape_Area字段天然存在单位取决于坐标系投影坐标系下一般是平方米。如果你用的是 SHP通常在属性表里没有Shape_Area建议先用“添加几何属性”工具把面积字段算出来或者直接用SHAPEAREA游标读取。有一点很容易被忽略统计面积的前提是坐标系单位是米之类的地图单位。如果数据是经纬度Shape_Area就是平方度算出来的数字没有现实意义。这种情况要先投影别在没投影的数据上做面积统计。2.4 关于 MultiPart 多部件必须搞清楚前面几次提到“多部件”这是新手最容易懵的地方。一个 Polygon 要素可以有多个“零件”比如海南岛的省界面如果做成一整条记录它天然就是多部件的因为包含了无数个小岛。无数个互不相邻的地块融合后也会变成这类多部件要素。它在地图上是一体的选中所属属性只有一条但零部件之间不连通。判断一个输出到底是不是多部件可以在 Python 里写几行代码快速检查import arcpy out_fc rC:\data\模拟项目X.gdb\parcel_dissolved with arcpy.da.SearchCursor(out_fc, [SHAPE]) as cursor: for row in cursor: geom row[0] print(是否多部件:, geom.isMultiPart) print(部件数量:, geom.partCount) print(面积:, geom.area)isMultiPart返回布尔值partCount表示零件数量。如果你希望输出严格只有一个单部件连续面但原始数据中间有缝隙Dissolve 没办法替你补缝。这时只能考虑“凹包”“凸包”或者后期补面。反过来如果你只是不想让数据里有多部件可以再加一行arcpy.MultipartToSinglepart_management(out_fc, split_fc)但这会拆成多条记录不再满足“一个 Polygon”的说法。所以动手前先想清楚你到底要哪种形态。3. 实战进阶从一批 SHP 批量合并成一个 Polygon 的完整脚本前面几段讲的是单个要素类内部融合。但实际工作中我接到的需求往往更糙一个文件夹下面几十个 SHP每个 SHP 是一个地块或者一组地块最终希望只有一个完整的 Polygon。这种场景下完整脚本比单个命令更有价值。3.1 目录下多个 SHP先 Merge 再 Dissolve核心思路就是第 1 节里说的先用 Merge 把一堆要素类汇总到一个临时要素类再用 Dissolve 融合。直接上可运行脚本import arcpy import os arcpy.env.overwriteOutput True src_folder rC:\data\shp_batch gdb rC:\data\模拟项目X.gdb arcpy.env.workspace src_folder fc_list arcpy.ListFeatureClasses(*, Polygon) if not fc_list: raise Exception(目录下没有面要素请检查路径) merged_fc os.path.join(gdb, merged_all) dissolved_fc os.path.join(gdb, dissolved_all) arcpy.Merge_management(fc_list, merged_fc) arcpy.Dissolve_management(merged_fc, dissolved_fc) count arcpy.GetCount_management(dissolved_fc)[0] print(完成融合后要素数量:, count)这里我把arcpy.env.workspace设成 SHP 所在文件夹再用ListFeatureClasses(*, Polygon)自动抓取所有 Polygon 类型的要素类。注意参数里第二个参数Polygon是要素类型过滤如果你目录下还有线、点这样写能避开它们。Merge 的输出我建议放到文件地理数据库里而不是直接输出成 SHP。原因是后面 Dissolve 需要处理大量节点GDB 的读写和空间索引都比 SHP 稳。而且如果你要保留统计字段GDB 对字段名的长度限制也比 SHP 友好得多。SHP 的字段名只有 10 个字符一旦字段名长了会被截断排查起来特别折磨人。3.2 坐标系不统一会导致融合失败或结果错位这台坑我踩过不止一次。多个 SHP 来自不同部门数据坐标系可能一个 4326一个 3857甚至还有没定义坐标系的老数据。直接 Merge 再 Dissolve系统不会自动帮你转换坐标系而是把所有坐标值当成同一个坐标系来算。结果就是不同数据的要素在空间上错位融合出来的 Polygon 完全没法用。所以被人送来的数据第一步永远别急着融合先摸清楚坐标系。可以在脚本里做一个统一投影预处理import arcpy import os arcpy.env.overwriteOutput True src_folder rC:\data\shp_batch gdb rC:\data\模拟项目X.gdb target_sr arcpy.SpatialReference(3857) # 这里换成你项目实际使用的坐标系 arcpy.env.workspace src_folder fc_list arcpy.ListFeatureClasses(*, Polygon) projected_list [] for fc in fc_list: desc arcpy.Describe(fc) if desc.spatialReference.name ! target_sr.name: base_name os.path.splitext(os.path.basename(fc))[0] out_fc os.path.join(gdb, proj_ base_name) arcpy.Project_management(fc, out_fc, target_sr) projected_list.append(out_fc) else: projected_list.append(fc) arcpy.Merge_management(projected_list, os.path.join(gdb, merged_all)) arcpy.Dissolve_management( os.path.join(gdb, merged_all), os.path.join(gdb, dissolved_all) )这段逻辑并不复杂逐个要素类检查坐标系如果和目标不一致就用Project_management投影到目标坐标系并把投影后的文件塞进列表一致就直接用原始文件。等所有输入都在同一个坐标系下再走 Merge 和 Dissolve 就是顺理成章的事。注意最后目标坐标系的选择要符合你项目的地图单位和精度要求用 3857 只是举例不是所有场景的万能解。3.3 大批量数据的效率优化心得处理几十个 SHP 时脚本跑得很快但要是几千个、几万个面融合就没那么轻松了。Dissolve 的算法本质是要在面之间做几何求交和边界移除数据量越大耗时指数上升。我自己的经验有几条第一中间结果优先存 GDB。SHP 在大量节点读写下明显拖慢速度而且 SHP 没有空间索引反复遍历非常吃力。只要你不是最终交付 SHP中途数据都建议放到 GDB 里。第二能分批融合就先分批融合。比如你有几百个 SHP每个 SHP 内部的面数量不多可以先把每个 SHP 分别 Dissolve 一次得到几百个临时要素再用 Merge 汇总最后对汇总结果做一次整体 Dissolve。这样做能显著降低单次计算的复杂度。如果所有 SHP 叠加在一起面积巨大这种写法常常能快好几倍。第三保留顶点多的高精度边界但有冗余节点可以先用“简化面”工具处理。Dissolve 处理的是边界关系节点越多越慢如果面边界精度要求没那么高简化后能大幅提速。当然简化之后边界形状会有细微变化正式出图前要确认精度能不能接受。第四养成测试习惯。全量跑之前先复制两三个要素到一个测试 GDB 里把脚本参数指过去先跑通一遍确认输出符合预期再换回全量数据。很多人报错就是因为拿着几十万条数据直接调试报错信息堆了一屏还没定位到真正的问题。4. 实操中常见的报错与避坑点写 ArcPy 脚本最烦的不是逻辑而是那些明明数据没问题、代码也没大问题但一跑就报错的诡异情况。我把这几年遇到的高频问题集中盘一下有些是数据问题有些是习惯问题。4.1 融合前先修几何别等报错如果你是拿别人发来的数据直接跑融合第一步最该做的不是 Dissolve而是先做一次几何修复。手动圈出来的地块经常会有自相交、缝隙、重叠、空几何这类隐藏问题。Dissolve 在处理拓扑错误要素时特别容易触发底层错误最常见的就是 ERROR 999999报错信息又短又没用实际上往往是数据里藏了脏要素。推荐处理方式很简单在融合前加一句arcpy.RepairGeometry_management(in_fc, DELETE_NULL)RepairGeometry_management会修正自相交、删除空几何等常见几何问题第二个参数DELETE_NULL表示自动删除空要素。虽然这个方法不能处理所有拓扑错误但能把大部分“看起来正常、一跑就炸”的脏数据挡在门外。另外再说一个路径问题。SHP 路径里尽量别有中文虽然不是所有版本都会出问题但一旦出问题就是玄学排查半天也不知道错在哪。我个人的习惯是数据文件一律英文或拼音命名输出路径用绝对路径减少这类无谓的麻烦。4.2 报错速查表为了方便查我把实际工作中高频遇到的报错整理成一个简单的速查表报错信息可能原因处理办法ERROR 000725输出路径已经存在且未开启覆盖设置arcpy.env.overwriteOutput True或删掉已有输出ERROR 000732输入要素类不存在或路径不正确检查路径、文件名、扩展名GDB 要素类不要加.shp后缀ERROR 000728指定的融合字段或统计字段不存在用arcpy.ListFields打印字段名检查大小写和实际字段名ERROR 999999底层几何错误或环境问题先执行RepairGeometry_management再检查磁盘空间和许可状态输出要素数量为 0输入是空要素类检查GetCount确认输入里真的有面融合结果面积不对坐标系为经纬度或未投影统一投影到米制坐标系后再融合和统计这里特别要解释一下 ERROR 000732。很多人拿着 SHP 路径写rC:\data\地块.shp但工作空间如果已经切到了某个 GDBArcPy 对相对路径的处理会变得很微妙最容易出问题。所以我写脚本时尽量都用绝对路径一个函数吃遍全局不会被环境变量带偏。4.3 成品是不是“一个 Polygon”检查脚本融合完成之后别急着直接把结果交给别人先自检一遍。我常用的自检脚本是这样的import arcpy out_fc rC:\data\模拟项目X.gdb\parcel_dissolved desc arcpy.Describe(out_fc) print(要素类型:, desc.shapeType) print(要素数量:, arcpy.GetCount_management(out_fc)[0]) with arcpy.da.SearchCursor(out_fc, [SHAPE]) as cursor: for row in cursor: geom row[0] print(是否多部件:, geom.isMultiPart) print(部件数量:, geom.partCount) print(平面面积:, geom.area)如果要素数量是 1并且是否多部件是 False那就是一个严格意义上的单部件 Polygon。如果数量是 1但是否多部件是 True说明你得到了一个多部件 Polygon去地图上看一眼是不是你能接受的形态。如果你发现自己预期“应该是一块连续的面”但部件数量特别多那就要回到数据本身去查是不是原始面之间存在缝隙是不是有些面没接触上这个时候问题八成不在代码而在源数据。5. 最后分享一点个人使用习惯写到这里基本把 ArcGIS 里多个 Polygon 合并成一个 Polygon 的常用代码路线捋了一遍。最后再分享几个我自己的操作习惯不一定标准但实用性很强。第一个习惯任何融合操作都不在原数据上直接做。Dissolve 是不可逆的原始面一旦被融合边界信息就彻底没了。所以我永远把输入当作只读输出写到另一个要素类或者另一个 GDB 里。宁可在磁盘上多存几个中间文件也不要图省事把原数据洗掉。第二个习惯想法里想着“隐藏边界”比“删除边界”用的场景更多。有时候领导只是要一张看起来是一个整体的图并不是非要把数据真正融合。这种时候我干脆不改数据直接在符号化层面把所有面要素的边框设置为无边框加上统一填充色视觉上就是一个完整的 Polygon。这么做好处是原始属性还在以后还想按小地块统计面积随时能恢复。真到了必须分析、出图、空间运算的时候再做数据层面的融合。第三个习惯脚本不只在 Python 窗口里跑。如果你发现自己经常要做这类批量融合建议把代码拆成带参数的脚本工具输入要素类、输出路径、融合字段都做成参数存进 ArcGIS 工具箱里。下次双击就能跑不用每次改代码。数据整理工作最怕的就是同一个步骤反复手动操作能自动化的地方千万别偷懒。说到底ArcGIS 里合并 Polygon 本身不难真正难的是在动手之前判断用户要的是哪种“合并”然后在动手之后验证结果是不是真的符合那个预期。希望这篇经验贴能帮你少踩几个坑。