简介:ArcGIS Python Add-In 入门源码与教程,面向需要利用 Python 扩展 ArcGIS Desktop 的 GIS 工程师和二次开发初学者,围绕自定义工具、菜单与面板的快速开发需求,提供一份从零到一的实操指引。压缩包共包含 7 个文件,由 pdf 开发说明、两个 py 脚本、esriaddin 扩展包、xml 配置文件、txt 说明文档以及示例位图文件组成,整体大小仅 142KB,轻量便捷。目前已有 1216 人浏览学习,是被多次验证的入门资料。内容详细覆盖了开发环境搭建、Add-In 组件创建、XML 用户界面配置、ArcPy 脚本编写、Add-In Manager 调试以及打包分发等完整流程,教程还特别说明了如何利用 Python 常用库强化空间分析能力。配套的源码模板不是孤立代码,而是与 xml、esriaddin 文件联动的可运行项目,读者可通过对照 PDF 文档理解各个文件的作用,直接在模板上修改,快速生成自己的 GIS 工具,适用于个人学习、团队内部分享和项目原型搭建。 ArcGIS Python Add-In,我最早接触它是在做内业批量处理的时候。当时每天要在ArcMap里重复做同一套操作:打开属性表、筛选、计算、导出,点得手酸。后来花了一个周末,用Python写了个小插件,把整个流程缩成工具栏上一个按钮,点一下就全跑完。那感觉就像终于给自己配了个“自动化小弟”。这篇文章要讲的,就是怎么从零开始做一个ArcGIS Python Add-In,包括源码结构、核心写法、打包分发,以及我一路踩过的坑。适合刚接触GIS二次开发、想在ArcMap里自定义工具按钮的从业者和学生参考。
1. 为什么还要折腾Add-In
1.1 它能帮你把“重复劳动”变成“点一下”
ArcGIS Desktop 10.x 时代,给ArcMap加一个自定义按钮,传统路线是写.NET或Java组件,各种COM注册、DLL引用,没接触过的光看教程就能劝退。Python Add-In把这件事拉低到了一个非常友好的门槛:按钮、工具、菜单、工具条,本质上都是Python脚本包了一层“壳”,壳负责把按钮画在界面上,脚本负责干实际活。
我拿它做过最典型的几类事情:批量出图、按字段拆分数据、自动检查拓扑错误、批量坐标系转换、统计选中要素面积并导出Excel。以前需要在工具箱里翻半天再一步步执行的操作,封装成Add-In以后,同事只需要记住“点那个按钮”就行。说句实话,这种开发模式特别适合内业岗、数据处理岗的人自给自足,不用依赖开发部门排期。
1.2 和ArcGIS Pro的Add-In比,怎么选
很多人在网上查资料会看到ArcGIS Pro的Add-In,用的工具链和桌面版完全不一样。我直接用实际体验对比一下,方便你判断自己该学哪条路线:
| 对比项 | 桌面版 Python Add-In | ArcGIS Pro Add-In |
|---|---|---|
| 适配平台 | ArcGIS Desktop 10.x | ArcGIS Pro |
| 开发语言 | Python 2.7 + arcpy | C#/Python(Pro 3.x后主要靠ArcGIS Pro SDK) |
| UI描述方式 | config.xml 声明式配置 | .NET构建面板/按钮 |
| 打包分发 | makeaddin.py 生成 .esriAddIn | 生成 .ppkx |
| 上手难度 | 较低,纯Python能搞定 | 较高,需要接触Visual Studio或Pro SDK |
| 适用场景 | 存量环境、老项目维护、快速自用工具 | 新项目、大数据、三维场景 |
我的态度很明确:如果单位还在用ArcMap 10.x,桌面版Add-In就是性价比最高的自研工具方案;如果新项目已经在ArcGIS Pro上跑,那就别花大力气学这套老壳子了,直接把arcpy处理逻辑抽出来,迁到Pro的脚本工具里去。文章后面给的源码思路,其实两头都能用。
2. 开发环境与前置准备
2.1 ArcGIS Desktop与Python版本必须对上
ArcGIS Desktop 10.0捆绑的是Python 2.6,10.1到10.8.2捆绑的是Python 2.7。Add-In开发绕不开这个版本约束,因为ArcMap进程内加载Python插件时,用的就是安装ArcGIS时自带的那个Python环境。
这就带来一个必须接受的现实:在桌面版Add-In里,你写的是Python 2.7语法,不是3.x。代码里常见的差异点包括:print是语句不是函数,except后面要写except Exception, e:而不是as e,字符串默认是byte字符串,中文处理要格外小心。我见过不少朋友把所有精力花在按钮逻辑上,结果被一个中文字符编码问题卡了半天,所以后面会专门写一节编码避坑。
另外要注意32位和64位的问题。ArcMap本身是32位程序,Add-In跑在ArcMap进程里,用的自然是32位Python。ArcGIS有一个“64位后台地理处理补丁”,那个Python是64位的,但Add-In不能依赖它。大数据量处理时,Add-In更适合先把任务写进脚本工具,再通过GP服务或批处理后台跑,别在按钮里一次性塞几千万要素的密集运算。
2.2 装好环境,避开Win11兼容性坑
网上经常有人问“ArcGIS Desktop 10.8.2与Win11不兼容怎么办”。我实测下来的情况是:大部分功能能跑,但偶尔会遇到启动慢、界面控件异常、或者Add-In管理器加载不稳定的问题。比较有效的处理顺序是:
- 先安装ArcGIS Desktop 10.8.2的官方补丁和更新包,很多兼容性问题在补丁里已经修了。
- 如果启动还异常,右键ArcMap快捷方式,用“兼容性疑难解答”选Windows 8兼容模式运行。
- 还解决不了,最省心的方案是换到ArcGIS Pro环境,把老工具迁移过去。
配合检查一下机器上是否已经装好Python 2.7和arcpy。正常装完ArcGIS Desktop后,ArcMap自带的Python会在类似C:\Python27\ArcGIS10.8的目录下,可以直接在命令行运行python验证。Add-In开发本身不要求单独安装Python,直接用内置的就好,也不用额外配IDE,记事本、VSCode、PyCharm都可以。
2.3 Add-In Wizard:生成骨架的神器
开发Add-In最推荐的起步方式是先装“Python Add-In Wizard”,一个很小的向导程序,用来生成项目骨架。官网和镜像站都能下载到对应ArcGIS版本的版本,安装后桌面会出现快捷方式。它会在你填完项目信息后自动生成一整套文件夹和示例代码,省去手写config.xml的麻烦。
我不太建议一上来就手写全套文件,因为Add-In对config.xml里的节点格式比较敏感,少一个标签或者类名对不上,按钮就静默消失,不报错也不显示。用向导生成,能保证初始骨架是正确的,后面所有精力都可以放在脚本逻辑上。
3. 从零写一个能跑的Add-In
3.1 用向导生成项目骨架
具体操作流程大概是这样的:
- 双击打开“Python Add-In Wizard”,点击Next。
- 填写项目名称、项目描述、作者和公司。这里有个血泪教训:项目名称和类名不要写中文,尽量用英文字母和下划线,否则生成后的模块导入阶段很容易出问题。
- 选UI类型。先勾上Button和Toolbar,一个按钮加一个工具条,是最典型的组合。
- 设置工具条显示名称,比如叫“我的批量工具”。
- 添加一个按钮,填上Caption(按钮显示文字)、Tooltip(悬停提示)和类名,类名建议用一开始就能看懂的,比如
Button_StatArea。 - 点Finish,向导会在指定目录生成项目文件夹。
生成后的项目里会有一个readme.txt,里面写着开发Python Add-In的基本步骤,说实话比很多网上的教程都清楚,值得先读一遍。
3.2 生成的源码到底长什么样
项目目录结构大概是这样:
MyAddIn/ ├─ Install/ ├─ Images/ │ └─ button.png ├─ arcpy/ ├─ MyAddIn.py ├─ config.xml ├─ makeaddin.py └─ README.txt几个关键文件的作用分别是:
MyAddIn.py:真正的脚本文件,你的按钮逻辑都写在这个文件里。config.xml:界面配置,声明有哪些按钮、工具、菜单、工具条,以及每个界面元素对应的脚本类名。makeaddin.py:打包脚本,运行后会生成.esriAddIn安装文件。Images:存放按钮图标。Install:存放打包结果和安装说明。
config.xml里最关键的一段大概长这样:
<Toolbars> <Toolbar caption="我的批量工具" category="MyAddIn" show="false"> <Items> <Button caption="统计面积" class="Button_StatArea" image="button.png" /> </Items> </Toolbar> </Toolbars>注意class字段必须和MyAddIn.py里的类名完全一致,包括大小写。我第一次做的时候就是改了类名忘记改config.xml,结果工具栏干干净净,什么按钮都没有。
3.3 第一个按钮:统计选中要素面积并导出CSV
生成骨架后,打开MyAddIn.py,会看到一个模板类。我一般把它改造成这样,做一个非常实用的小功能:统计当前地图中选中图层里每个要素的面积,导出为CSV。
# -*- coding: utf-8 -*- import arcpy import pythonaddins import csv import traceback class Button_StatArea(object): def __init__(self): self.enabled = True self.checked = False def onClick(self): try: mxd = arcpy.mapping.MapDocument("CURRENT") layer = None for lyr in arcpy.mapping.ListLayers(mxd): if lyr.isFeatureLayer and lyr.getSelectionSet(): layer = lyr break if not layer: pythonaddins.MessageBox("请先选中一个图层", "提示") return out_csv = r"D:\temp\area_stat.csv" with open(out_csv, "wb") as f: writer = csv.writer(f) writer.writerow(["OBJECTID", "AREA"]) with arcpy.da.SearchCursor(layer, ["OBJECTID", "SHAPE@"]) as cursor: for oid, shape in cursor: writer.writerow([oid, shape.area]) pythonaddins.MessageBox("导出完成:" + out_csv, "完成") except Exception as e: pythonaddins.MessageBox(str(e), "错误") traceback.print_exc()这段代码有几个点值得展开说一下。arcpy.mapping.MapDocument("CURRENT")获取的是当前打开的ArcMap文档,这个只能在ArcMap界面内运行时生效,如果脱离ArcMap用外部Python跑,需要传mxd文件路径。getSelectionSet()判断图层是否有选中要素,没有选中就弹个提示,避免后面遍历空数据。arcpy.da.SearchCursor返回的结果里,SHAPE@代表几何对象,调用.area得到的是要素的面积,单位由当前数据框的坐标系决定。
4. 核心源码逐段拆解与扩展
4.1 在onClick里安全地操作arcpy
onClick按钮事件是Add-In最核心的入口,所有点击后的逻辑都从这儿开始。我在实操中养成了几个固定习惯,能明显减少翻车概率。
第一,每次onClick开头先设置arcpy环境变量。有人觉得arcpy.env.workspace设一次就好,其实不然,ArcMap里用户随时可能切换数据源,上一次脚本留下的workspace状态会在下次点击时残留。我习惯在函数开头重新指定:
arcpy.env.workspace = r"D:\gis_data\project.gdb" arcpy.env.overwriteOutput = True第二,尽量把数据处理逻辑拆成独立的普通函数,不要在按钮类里堆一大坨。比如说先写一个stat_areas(layer, out_csv)函数,然后在onClick里只做界面交互和参数获取。这样以后把这个函数复制到ArcGIS Pro或者脚本工具里,几乎不用改就能复用。
第三,所有可能出错的地方都要用try/except包住,并且把错误信息弹出来或写进日志。Add-In的按钮一旦抛出未捕获异常,用户看到的是按钮没反应,不会看到堆栈,排查体验很差。
4.2 做一个需要画框的交互式Tool
按钮适合“选中参数,点一下执行”的场景,但如果要在地图上画一个矩形、画一条线、或者点一个点,就得用Tool类型。这个需求在批量处理里特别常见,比如框选一批要素做缓冲区分析。
向导生成时如果选了Tool,会生成一个继承或包含Tool逻辑的类。核心代码大概这样:
import arcpy import pythonaddins class Tool_SelectByBox(object): def __init__(self): self.shape = "Rectangle" self.cursor = 3 def onRectangle(self, rectangle): try: mxd = arcpy.mapping.MapDocument("CURRENT") df = mxd.activeDataFrame for lyr in arcpy.mapping.ListLayers(mxd): if lyr.isFeatureLayer and lyr.visible: arcpy.SelectLayerByLocation_management(lyr, "INTERSECT", rectangle) arcpy.RefreshActiveView() pythonaddins.MessageBox("框选完成", "提示") except Exception as e: pythonaddins.MessageBox(str(e), "错误")解释一下关键点:self.shape = "Rectangle"告诉ArcMap,用户点击这个工具后,鼠标拖拽画的是矩形。onRectangle在画完矩形后触发,矩形对象会作为参数传进函数。接下来用arcpy.SelectLayerByLocation_management按空间位置选中所有与矩形相交的要素。
除了onRectangle,还有onLine、onPoint、onMouseDown、onMouseUp等回调。如果是需要连续点击多次才能结束的图形,建议优先找现成的回调,比如画线用onLine,画点用onPoint,不要自己在MouseDown里维护状态机,一旦状态错乱会很头疼。
4.3 控制按钮的可用状态
按钮在不需要的时候应该置灰,比如没有打开地图文档、没有选中图层时,点了反而报错。这个可以通过enabled属性和onUpdate回调控制。
class Button_StatArea(pythonaddins.Button): def __init__(self): self.enabled = True def onUpdate(self): try: mxd = arcpy.mapping.MapDocument("CURRENT") self.enabled = self._hasSelectedLayer(mxd) except Exception: self.enabled = False def _hasSelectedLayer(self, mxd): for lyr in arcpy.mapping.ListLayers(mxd): if lyr.isFeatureLayer and lyr.getSelectionSet(): return True return False def onClick(self): # 实际统计逻辑 passonUpdate会在ArcMap界面空闲时被反复调用,用来刷新按钮状态。这里注意性能,别再回调里做重量级操作,简单检查图层和选择集就够了。判断逻辑要轻量,否则界面会卡顿。
4.4 打包、分发与卸载
开发完成后,在项目目录下打开命令行,运行:
python makeaddin.py会在Install文件夹里生成MyAddIn.esriAddIn文件。双击这个文件,ArcMap会自动识别并提示安装。安装后,在ArcMap的“自定义 > 加载项管理器”里能看到已安装的插件,点击工具条名称勾选显示,你的按钮就出现了。
分发给同事时,直接把.esriAddIn文件发过去,在对方机器上双击安装。有几件事要提前注意:
- 目标机器必须装有对应版本的ArcGIS Desktop,且License可用。
- 如果按钮里写了绝对路径,换台机器很可能跑不通,最好把路径参数写到config.xml或外部配置文件里。
- 卸载很简单,加载项管理器里选中插件,点“删除”即可。
5. 常见问题与避坑实录
5.1 按钮不显示、点不动怎么排查
我把这几年最常遇到的界面问题整理成一个速查表:
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 工具条根本找不到 | 安装未成功 | 重新运行makeaddin,重新双击esriAddIn安装 |
| 工具条存在但按钮空白 | config.xml里class类名和脚本类名不一致 | 打开config.xml核对class字段 |
| 按钮置灰 | onUpdate里enabled返回False,或当前环境不满足 | 检查是否有当前地图文档、选中图层 |
| 点击没反应 | 脚本抛出异常但弹窗被吞 | 加try/except调试日志,看堆栈 |
| 图标变形 | 图片尺寸不对 | 图标用16x16 PNG,放在Images目录 |
5.2 Python 2.7的语法和中文编码坑
这个必须单独拿出来说,因为太容易踩了。Add-In跑在Python 2.7,和主流的Python 3语法有不少差别。常见错误包括:
# 错误写法(Python 3语法) except Exception as e: print(e) # 正确写法(Python 2.7) except Exception, e: print e中文编码问题更隐蔽。脚本文件第一行我建议写上# -*- coding: utf-8 -*-,中文字符串前加u前缀。读文件时用codecs.open或指定编码,写入CSV文件时文件要用二进制模式"wb",否则中文可能写成乱码。我见过一个同事在按钮里拼接中文路径,怎么跑都报“找不到文件”,最后发现是编码没统一,一旦路径里有中文就出问题。
5.3 arcpy导入失败与运行环境问题
Add-In运行在ArcMap进程里,所以正常情况下import arcpy不会失败。如果你在外部Python命令行里import arcpy报错,大概率是因为你用的不是ArcGIS自带的Python环境,或者安装时没有把Python组件装上。解决办法是改用ArcGIS捆绑的C:\Python27\ArcGIS10.x\python.exe跑脚本,或者修复ArcGIS安装。
另外注意,如果机器上自己装了其他版本的Python,系统PATH可能指向那个版本,导致命令行里输入python后import不到arcpy。处理方式是直接写全路径调用ArcGIS的Python,不要依赖PATH。
5.4 调试日志:看不见print时的救命稻草
在Add-In里,print输出很难看到,按钮点击后脚本报了什么错完全不透明。我的做法是封装一个简单的日志函数,把运行信息写到本地文本文件:
import datetime import traceback def log(msg): with open(r"D:\temp\addin_debug.log", "a") as f: f.write("[%s] %s\n" % (datetime.datetime.now(), msg)) def log_exception(): with open(r"D:\temp\addin_debug.log", "a") as f: traceback.print_exc(file=f)然后在所有onClick、onRectangle的except里调用log_exception()。按钮没反应时,先去看日志尾部,错误堆栈一目了然。这个方法救了我好多次,强烈建议从一开始就加上。
5.5 配合天地图等在线底图的小思路
平时做项目时经常要叠加在线底图,尤其天地图用得很多。严格说,天地图的加载和Add-In开发是两件事。天地图影像一般通过WMTS服务或者ArcGIS Online方式接入ArcMap,需要在天地图官网申请开发者Key,然后在ArcMap里添加WMTS服务器地址,按规范填写Key和图层名称。
在Add-In里如果想自动化加载底图,可以用arcpy.mapping.AddLayer添加一个保存了天地图服务参数的lyr文件。实际操作时,我通常先把天地图配置手动调好,另存为图层文件,然后在Add-In里引用它。要注意在线底图涉及网络、服务可用性和数据版权问题,用于项目成果时务必遵守天地图的授权规则。
回到Add-In本身,我的理解是:它能解决的是“我自己的操作流程自动化”,而不是“把在线地图塞进ArcMap”。搞清楚边界,开发思路才会清晰。
最后再分享一个我正在用的习惯:把Add-In项目丢进Git仓库,每次改代码都提交一次,config.xml、脚本、图标保持版本同步。后来单位升级ArcGIS Pro,我整理老工具时才发现,这些历史提交记录帮了大忙,哪些按钮被谁改过、功能怎么迭代的,一清二楚。而且前期把arcpy处理逻辑都写成独立函数的话,迁到Pro的时候基本只换壳,不用重写核心逻辑。这个习惯,算是比写出一两个按钮更值钱的经验了。
本文还有配套的精品资源,点击获取