ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

ArcGIS按图斑批量出图:Data Driven Pages+Python

ArcGIS按图斑批量出图:Data Driven Pages+Python

简介:《Arcgis按图斑批量出图(按卫片图斑批量出图)》是一份面向GIS数据处理、测绘遥感与规划人员的实操型图文文档,重点解决在卫片图斑数量大、需要逐图斑导出JPG的场景下,依靠手动出图效率低下的问题。文档围绕ArcGIS 10及以上版本的Data Driven Pages功能展开,完整讲解了数据驱动页面的图层与字段配置、布局视图中动态文本的添加方式,以及利用Python脚本arcpy模块自动遍历全部图斑并批量导出PNG/JPG的操作流程。压缩包内共1个文件,为doc格式文档,大小约29KB,内容组织紧凑,适合环境满足ArcGIS 10.2且具备基础Python阅读能力的读者对照上手。已有8265人学习下载。借助脚本中的页面切换与ExportToPNG等函数,可自动按图斑编号或属性字段命名输出图片,并将图斑所在区域、面积、类型等信息写入图面,大幅节省重复工作,为大规模遥感图斑制图与成果交付提供了一条高复用、易定制的自动化路径。

1. ArcGIS 按图斑批量出图:告别手动几百张图的导出疲劳

干 GIS 这行,最枯燥的活儿莫过于按图斑一张张出图。尤其是卫片执法、变更调查这类项目,图斑层动辄几百上千个,每个图斑都要出一张带编号、带面积、带位置信息的 JPG,手动操作的话一天下来手能点抽筋,还容易漏图、错图。这份资源解决的就是这个痛点:基于 ArcGIS 10.2 的 Data Driven Pages(数据驱动页面)配合一段 Python 脚本,把「遍历图斑—切换布局—导出图片」整条链路自动化,让机器替你完成重复劳动。适合测绘、国土、规划从业者,也适合刚入行但被出图任务折磨的 A 同学。你只需要把配置做对一次,脚本跑一遍,几百张图几分钟就能出来。

2. 数据驱动页面配置:先让 ArcMap 自己认识每个图斑

2.1 为什么选 Data Driven Pages 而不是按属性选择导出

在 ArcGIS 里实现批量出图,常见路子有两个:一是用模型构建器(ModelBuilder)配合迭代要素选择,二是用 Data Driven Pages。我一开始用模型构建器试过,迭代要素选择(Iterate Feature Selection)确实能按图斑逐个缩放显示,但每迭代一个图斑就要重新刷新一次布局、触发一次导出,遇到几百个图斑时速度明显变慢,而且在布局视图中控制比例尺和范围很别扭。Data Driven Pages 的思路完全不同——它先把图斑层定义成「页面索引图层」,ArcMap 自动把每个图斑映射成一个页面,你在布局视图里只需要设计好一个「模板页」,它就能按图斑逐个渲染。这种机制在 10.0 版本引入,到 10.2 已经很稳定,最关键的是它支持 Python 脚本直接调用页面对象,给后续自动化留好了口子。

有没有替代方案?有。ArcGIS Pro 的布局(Layout)功能也能做类似的事,但如果你是存量项目、手里全是 .mxd 工程文件,Pro 还得转换工程,反而多一道工序。所以在这个场景下,Data Driven Pages 就是最稳的选择。

2.2 打开工具栏并指定索引图层

首先确认你的 ArcMap 版本在 10.0 以上,9.x 不带这个功能。打开 ArcMap,加载卫片图斑图层和底图数据,然后在菜单栏空白处右键,勾选「Data Driven Pages」工具栏。如果没有这个选项,检查一下是否在自定义模式下。

点击工具栏上的「Set Up Data Driven Pages」按钮,进入设置对话框。关键配置项如下:

参数说明推荐设置
图层(Layer)索引图层,即要按哪个图层出图选择卫片图斑图层
名称字段(Name Field)每个页面的名称来源,通常用图斑编号字段选择你的图斑编号字段,如 "BSM" 或 "TBBH"
排序字段(Sort Field)页面排序依据,决定出图顺序按图斑编号升序,方便核对
范围(Extent)每个页面的显示范围来源保持默认「该图层要素范围」

这里有个容易忽略的点:名称字段的值会直接用于后续脚本生成文件名,所以这个字段必须是唯一且规范的。如果图斑编号字段里存在空值或重复值,后面脚本会报错或覆盖同名文件。我一般会在出图前先用属性表检查一遍这个字段的完整性,顺手把空值补上。

2.3 布局视图里的动态文本:让每张图自带属性信息

配置好数据驱动页面后,切到布局视图(Layout View)。你会发现布局画布里多了一个「数据框」之外的东西——当页面切换时,数据框的显示范围会跟着当前图斑走。但仅靠地图范围还不够,图斑的编号、所在村、卫片类型、面积这些属性信息,也需要动态地显示在每张图上。

这时候要用到动态文本(Dynamic Text)。在布局视图中,插入菜单里找到「Dynamic Text」,子菜单里会多出「Data Driven Page」相关选项,比如「Page Name」「Page Number」等。如果你要显示图斑的其他属性字段——比如面积、地类——就需要稍微绕一下:插入一个文本元素,右键属性,在文本框中输入动态文本语法。常见做法是这样:

  • 打开文本属性对话框;
  • 选择「Insert」下拉菜单中的「Data Driven Page」;
  • 选择「Page Name」或「Page Number」。

但如果你要显示的是名称字段以外的属性,比如DLMC(地类名称),有种更灵活的办法:在属性框的「Text」标签页里勾选「Store as HTML」,然后写类似这样的片段:

<P>所在村: &lt;dyn type="dataDrivenPage" property="attribute"&gt;属性字段名&lt;/dyn&gt;</P>

不过我实测下来,直接手工编辑动态文本的 XML 语法容易出错,显示不出来还不好排查。最稳妥的做法是用「Insert」菜单里提供的动态文本,或者用布局视图里的「链接到属性值」功能——选中文本工具后,在文本属性里选「属性值(Attribute Value)」来源方式,选择你要的字段,它才能随页面切换自动刷新。

动态文本这块你只要记住:需要随图斑变化的字段,必须在布局上用动态方式绑定,而不是手工打字写死。否则切到第 3 个图斑时,图上显示的还是第 1 个图斑的编号,那就白出了。

2.4 固定比例尺与固定范围的取舍

设置范围时,Data Driven Pages 给了你「保持比例尺」和「保持范围」两个选项,前者叫「Best Fit」,后者叫「Center and Maintain Scale」。这俩区别很大:

  • Best Fit:每个图斑把数据框范围缩小到正好套住图斑,比例尺会随图斑大小变化。适合图斑面积差不多的场景,出图后图斑在图上都撑满画布;
  • Center and Maintain Scale:保持你预设的比例尺不变,图斑居中显示,图斑小的话周边留白就大。适合需要统一比例尺对比的场景,比如卫片执法要求同一比例尺量算面积。

卫片图斑批量出图,我一般建议用 Best Fit,除非项目有硬性比例尺要求。因为图斑大小差异往往很大,固定比例尺会导致小图斑在地图上小得看不见,大图斑直接超出画布范围。这里没有绝对的对错,取决于甲方要求,但你在配置时要清楚自己选的是哪一种,而不是稀里糊涂地接受默认值。

2.5 保存地图的两点提醒

配置完成后,保存 .mxd 工程文件,最好另存为一个专用于出图的版本。存好后先点预览区右侧的下一页面箭头,翻两页看看动态文本有没有跟着变、地图范围有没有跟着图斑走。这个动作花不了 30 秒,但能提前暴露绝大部分配置问题——比如图层选成了别的面图层、名称字段重复、布局里动态文本没刷新。确认没问题再进入下一步写脚本。

提示:如果你的图斑层是带注记的、需要标注字段,记得先在图层属性里打开标注(Label),否则出的图上是没有文字的。标注样式尽量简洁,图斑号、面积字段标注清晰即可,标注权重调低一点,避免和底图压盖。

3. 批量导出脚本:用 Python 驱动每一页

3.1 脚本核心逻辑拆解

Data Driven Pages 配置好了以后,批量导出其实就剩下一层窗户纸。ArcGIS 的 arcpy 库提供了mapping模块,允许你直接读取 .mxd 工程文件,遍历数据驱动页面,逐页设置当前页 ID,然后调用导出函数生成图片。整个脚本的骨架无非是这三件事:加载工程、切换页面、导出图片。

先看第一段,加载工程文件。在 ArcGIS 10.2 里,路径不支持中文会导致读取失败的情况比较多,我习惯把 .mxd 和输出文件夹都放在纯英文路径下,脚本文件和 Notepad++ 也建议同行处理,少踩编码的坑。下面先放一个最精简但能跑通的版本,解释完逻辑再给增强版。

# -*- coding: utf-8 -*- import arcpy mxd = arcpy.mapping.MapDocument(r"F:\GeoData\ChinaArea\ChinaVector.mxd")

这段代码里,MapDocument()接受一个.mxd文件的全路径字符串,前面的小写r表示原始字符串,避免路径里的反斜杠被转义。如果你把 .mxd 文件路径粘贴错了,或者路径里带了中文,运行时会报找不到文件的错——这是新手最容易翻车的第一步。

之后是遍历页码。.dataDrivenPages.pageCount是当前工程里数据驱动页面的总页数,在脚本里可以通过循环从 1 到 pageCount,把当前页 ID 逐个设置过去,同时通过pageNameField.name拿到当前页的名称字段名,用它来拼后面的输出文件名。

# -*- coding: utf-8 -*- import arcpy mxd = arcpy.mapping.MapDocument(r"F:\GeoData\ChinaArea\ChinaVector.mxd") for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1): mxd.dataDrivenPages.currentPageID = pageNum mapName = mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) print(mapName) arcpy.mapping.ExportToPNG( mxd, r"F:\GeoData\ChinaArea\Province\\" + mapName + ".png" ) print("ok")

这里range(1, mxd.dataDrivenPages.pageCount + 1)是从 1 开始到总页数结束,因为数据驱动页面的页码从 1 开始编号。currentPageID = pageNum是切换当前页;pageRow是当前页索引图层的属性行,getValue()取出名称字段的具体值,即图斑编号。最后ExportToPNG把当前布局导出为图片,文件名直接用 mapName,这样每张图的文件名就是图斑编号,方便后面核对。

3.2 控制导出分辨率和尺寸:导出参数怎么调

很多人在导出 PNG 之后,发现图是出来了,但分辨率不够,放大看不清地类边界,或者尺寸太大,几百张图占了几个 G 的硬盘。这个问题的根源是ExportToPNG的默认参数不够适合你的场景。

arcpy 里ExportToPNG的完整签名长这样(10.2 版本):

arcpy.mapping.ExportToPNG( map_document, out_jpg, data_frame="PAGE_LAYOUT", df_export_width=640, df_export_height=480, world_file=False, resolution=96, color_mode="RGB", background_color="#FFFFFF", transparent_color=None )

常见的场景里,你主要改的是resolution(分辨率)、df_export_width和df_export_height(输出尺寸)。卫片图斑出图,我一般建议分辨率设 150,宽度和高度设 1920×1080 或者按你的布局比例来。这里有个细节:data_frame="PAGE_LAYOUT"表示导出的是整个布局版面(也就是你布局视图中看到的整张图),不是只导出地图数据框。如果你的布局里有图例、指北针、标题这些制图元素,必须选 PAGE_LAYOUT,它们才会被画进图里。

# -*- coding: utf-8 -*- import arcpy mxd = arcpy.mapping.MapDocument(r"F:\GeoData\ChinaArea\ChinaVector.mxd") outPath = r"F:\GeoData\ChinaArea\Province" for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1): mxd.dataDrivenPages.currentPageID = pageNum mapName = mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) print(mapName) arcpy.mapping.ExportToPNG( mxd, outPath + "\\" + mapName + ".png", "PAGE_LAYOUT", 1920, 1080, resolution=150, color_mode="RGB" ) print("ok")

如果你需要出 JPG 格式,把ExportToPNG换成ExportToJPEG。ExportToJPEG的参数略有不同,它有quality参数控制压缩质量,默认 85,可以适当调高到 90 以上以保真。同样地,如果甲方指定要 PDF,也有ExportToPDF函数,但 PDF 文件会大很多,除非明确要求,一般用不到。

3.3 在 Notepad++ 里直接运行脚本

脚本放好后,用 Notepad++ 打开。Notepad++ 的「运行」菜单里点「运行...」,弹出对话框后输入以下命令来调用系统 Python 执行脚本:

cmd /k python F:\GeoData\ChinaArea\export_pages.py

当然,前提是你机器上安装了 Python 并能在命令行里直接调python命令。如果提示找不到 Python,常见原因是系统环境变量没配好,或者你安装的是 Python 2 但路径没加进 PATH。ArcGIS 10.2 自带的是 Python 2.7,如果你不想折腾环境,可以直接用 ArcMap 安装目录下的python.exe:

cmd /k "C:\Python27\ArcGIS10.2\python.exe" F:\GeoData\ChinaArea\export_pages.py

这个路径是针对默认安装位置的,如果你的 ArcGIS 装到了别的盘,通过开始菜单找到「ArcGIS」→「Python Command Line」的快捷方式,右键查看目标路径,那里有准确地址。跑起来之后,Notepad++ 的 cmd 窗口会一行行打印 mapName 的名字,等最后一行ok出来,说明全部页面导出完毕。

3.4 脚本运行完后需要检查什么

脚本跑完不是终点,你要做两件事:

  • 打开输出文件夹,统计一下图片数量。数量应当与图斑层属性表的记录数一致,如果图斑数 356,输出图片却只有 352 张,那一定是有 4 个图斑的名称字段重复或为空,脚本因为mapName重名覆盖了前面的文件,或者取到了空值报错中断。这类问题要在配置数据驱动页面时就排查掉。
  • 随机抽看十几张图,重点看每张图的图斑编号和图上标注是否对应,动态文本是否正常刷新。肉眼抽查这个步骤不能省,尤其在第一次跑通流程的时候。

注意:如果循环中遇到某个页面名称重复,后面的图片会覆盖前面的同名文件,最终文件夹内的图片数会少于图斑数。排查方法是回到属性表按名称字段排序,检查重复值和空值。

4. 避坑指南:批量出图最常见的五个翻车现场

4.1 动态文本不刷新,所有图显示同一个编号

现象:脚本跑出来几百张图,每一张图上写的都是同一个图斑编号,其他字段也一样不更新。

原因:布局里的文本是手工打进去的静态文字,没有使用动态文本绑定,或者用了动态文本但绑定的是「页面编号」而不是「名称字段」。页面编号是固定的页序号,每一页当然不会变。

解决:确认框架布局里的文本是从「Insert」菜单的「Dynamic Text」插入的,插入后选择对应的图斑属性字段。插入后选中该文本元素,右键打开属性面板,查看「Text」标签里是否包含dyn开头的动态标签,如果没有,删掉重插。

4.2 路径含中文导致脚本报错或输出乱码

现象:脚本运行时不报错,但输出文件夹里文件名变成了一串乱码,或者干脆一张图都没生成,只打印了几行mapName就中断了。

原因:在 ArcGIS 10.2 的 Python 2.7 环境下,中文路径和中文文件名的处理非常脆弱。mxd 路径含中文,MapDocument读取可能失败;输出路径含中文,文件可能被写到错误位置;名称字段含中文,输出的文件名乱码率高。

解决:我的建议是出一套「纯英文工作链」:mxd 文件名、输出文件夹路径、图斑编号字段值,全部要求是英文字母、数字、下划线的组合。如果业务上必须保留中文图斑号,那就用mapName.encode('utf-8')处理后再拼路径,但 10.2 下的坑多,还是建议在配置数据驱动页面时选一个英文字段(如 FID 或自增序列)作为名称字段源。

4.3 图片尺寸巨大,占了几个 G 的空间

现象:出了 300 张 PNG,每张 20MB,硬盘瞬间爆掉。

原因:默认分辨率 96,但实际导出还会跟随布局页面设置。如果布局纸型是 A0、宽高又没在脚本里显式指定,导出的位图会大得吓人。

解决:在ExportToPNG里显式指定df_export_width=1920, df_export_height=1080,并把resolution控制在 150 以内。如果目标只是看图和归档,resolution=120会进一步缩小体积;只有需要打印或放大检查时才提高到 200 以上。调整后再看每张图的大小,通常能控制在 2~5MB。

4.4 脚本提示 unknown argument 或找不到 ExportToPNG

现象:在 Notepad++ 里运行脚本,报AttributeError: 'module' object has no attribute 'ExportToPNG',或者提示TypeError: unknown argument。

原因:第一种情况是 import 的模块名写错了,比如写成了import arcpy.mapping但调用时用了arcpy.mapping.ExportToPNG,在 10.2 里这是可行的,但如果拼写成arcpy.ExportToPNG就会报模块没有属性。第二种情况是参数名称打错了,比如把resolution写成了resolutions,Python 直接报 unknown argument。

解决:脚本里统一用arcpy.mapping.前缀调用所有导出函数,参数名对照官方文档逐字核对。最稳妥的方式是在 ArcMap 里打开 Python 窗口,逐行输入脚本测试,窗口自带的代码补全和报错提示比外部编辑器友好得多,定位错误快得多。

4.5 只有地图框没看到图例和指北针,导出的图「要素不全」

现象:脚本写好后导出的图片里,地图框内容是对的,但图例、比例尺、标题都不见了。

原因:ExportToPNG的第二个参数data_frame指定了导出对象。如果写成data_frame="PAGE_LAYOUT",导出的是布局整体;如果写成数据框的名字(比如"Layers"),那只会导出地图框内的内容,所有布局元素(图例指北针标题)全部被省略掉。

解决:确认脚本里第二参数是"PAGE_LAYOUT"而不是数据框名称。如果你想二者对比一下,可以分别跑两版脚本各导出两张图,差异一目了然。

5. 进阶技巧:自定义页码命名规则与自动化验证

当图斑数量超过几百个,脚本能跑通只是第一步。真正有价值的是让它更顺手、更防错。我这里分享三个进阶处理:一是给脚本加上输出路径不存在就自动创建的机制,二是支持从命令行传入 mxd 路径和输出目录,省得每次改脚本;三是加一点简单的导后校验逻辑。

自动创建输出目录的代码放在脚本前段:

# -*- coding: utf-8 -*- import arcpy import os mxdPath = r"F:\GeoData\ChinaArea\ChinaVector.mxd" outPath = r"F:\GeoData\ChinaArea\Province" if not os.path.exists(outPath): os.makedirs(outPath) mxd = arcpy.mapping.MapDocument(mxdPath)

这里os.path.exists判断目录是否存在,os.makedirs递归创建多级目录。输出目录不存在时脚本也能直接跑起来,少一个「忘记建文件夹导致失败」的可能性。

支持命令行参数传入路径的话,把路径定义换成读取sys.argv:

# -*- coding: utf-8 -*- import arcpy import os import sys mxdPath = sys.argv[1] if len(sys.argv) > 1 else r"F:\GeoData\ChinaArea\ChinaVector.mxd" outPath = sys.argv[2] if len(sys.argv) > 2 else r"F:\GeoData\ChinaArea\Province"

这样你可以在 Notepad++ 运行命令里传参,或者在 cmd 窗口手动执行,不必为不同项目反复编辑脚本。但这属于锦上添花,如果你的项目固定,直接把路径写死在脚本里也没问题。

第三个技巧更实用:加一个导后校验循环,统计成功的张数和失败的位置。这个做法在我处理三千多张卫片图斑的时候救过我一次,当时某段数据里有三个图斑的几何有问题,导致中间循环断掉,而那批图之前是靠人工一张张查的,根本查不过来。加校验循环后就能精准锁定出问题的图斑编号,把范围缩小到几个对象上。

# -*- coding: utf-8 -*- import arcpy import os mxd = arcpy.mapping.MapDocument(r"F:\GeoData\ChinaArea\ChinaVector.mxd") outPath = r"F:\GeoData\ChinaArea\Province" successCount = 0 failList = [] for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1): mxd.dataDrivenPages.currentPageID = pageNum mapName = mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) outFile = os.path.join(outPath, mapName + ".png") try: arcpy.mapping.ExportToPNG( mxd, outFile, "PAGE_LAYOUT", 1920, 1080, resolution=150, color_mode="RGB" ) successCount += 1 print("导出成功: " + mapName) except Exception as e: failList.append(mapName) print("导出失败: " + mapName + " 原因: " + str(e)) print("共成功导出 %d 张,失败 %d 张" % (successCount, len(failList))) if failList: print("失败列表: " + ", ".join(failList))

os.path.join用来拼接路径,比直接写字符串加\\更规范、跨平台时也不容易出错。try/except捕获单页导出异常,让它不会因为某一页失败就中断整个脚本,最后统一打印失败列表,方便有针对性的修复。这套验证机制加上后,即便出图过程中有叉子,你也能在一分钟内定位到是哪个图斑的哪个环节出了问题。

从那以后,我每次批量出图都强制自己走一遍「配置检查 → 小批量试跑 → 全部导出 → 校验数量与失败列表」的流程。就在两周前,某项目甲方临时追加了三十多个图斑,我重新配好数据驱动页面,脚本改了下路径直接跑,两分钟出完图,检查无误后打包发走。希望这套流程也能让你从重复的导图工作中解脱出来。

希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进