ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

ArcGIS卫片图斑批量出图:Data Driven Pages与arcpy脚本实战

ArcGIS卫片图斑批量出图:Data Driven Pages与arcpy脚本实战

简介:这份资源面向使用ArcGIS处理卫片图斑的GIS从业者与测绘人员,聚焦按图斑批量导出JPG图片这一高频需求,帮助解决手动逐张出图耗时低效的问题。资源包共1个doc文档,约29KB,以图文步骤形式讲解数据驱动页面配置、布局动态文本设置及Python脚本批量出图思路,并附有可直接参考的arcpy示例代码,涵盖图层与名称字段指定、排序与比例尺设定、ExportToPNG导出参数调整等关键环节。内容还涉及动态文本展示图斑编号、所在村、面积与类型等信息,便于每张成图携带对应属性。目前已有8293人学习下载,适合具备ArcGIS 10.2及以上环境、希望掌握自动化出图流程的中级用户参考,可据此快速搭建批量出图方案并迁移到自身项目。

1. 卫片图斑批量出图:从手动一张张点到脚本一键跑完

手里压着几百个卫片图斑,每个图斑要单独出一张 JPG,还要带上图斑编号、面积、类型这些信息——这事要是纯手工在 ArcMap 里一张张切、一张张导,干到后半夜也出不完。ArcGIS 按图斑批量出图这套流程,核心就是拿 Data Driven Pages(数据驱动页面)当引擎,再用 arcpy 脚本把「翻页 + 导出」这个动作循环起来。它解决的不是画图问题,而是「同一套布局模板,按属性表逐行换内容、逐行导文件」的重复劳动。适合手里有卫片图斑层、需要按图斑出专题图的 GIS 从业者,ArcGIS 10 以上版本都能跑,10.2 是当年最稳的搭配。下面把我自己拆过、跑过、翻过车的完整链路摊开讲。

2. 数据驱动页面配置:让每个图斑自动占一页

2.1 为什么是 Data Driven Pages 而不是手动切图

批量出图这件事,本质上要解决三个变量:范围跟着图斑走、标题跟着属性走、文件名跟着字段走。手动切图是三个变量全靠人盯,Data Driven Pages 是把这三个变量绑到图斑层的属性表上,让 ArcMap 自己按行驱动。

它的工作机制不复杂:你指定一个索引图层(Index Layer),这个图层的每一条记录对应输出的一页;再指定一个名称字段(Name Field),这个字段的值会成为页面的标识,后面脚本拿它当文件名;排序字段决定出图顺序;显示范围和比例尺决定每页的视野。配置完之后,ArcMap 的布局视图里就多了一套「翻页」能力,你翻到第几页,地图范围就跳到第几个图斑。

选它而不是自己写游标遍历要素、逐个改范围,理由很直接:Data Driven Pages 把范围计算、比例尺锁定、动态文本刷新这几件事都封装好了,脚本只需要负责「翻页」和「导出」两个动作。自己用 arcpy 的 extent 去算,遇到不规则图斑、需要留边距、比例尺要统一的时候,边界情况能写到你怀疑人生。

提示:索引图层不一定是图斑层本身。如果图斑太碎、一个页面想覆盖多个相邻图斑,可以单独做一个「出图单元」图层当索引,用空间连接把图斑属性挂上去。

2.2 配置步骤与关键参数

打开 ArcMap,在 Customize → Toolbars 里勾上 Data Driven Pages,工具栏就出来了。点 Setup 进入配置对话框,几个参数逐个说清楚:

参数项作用常见设置
Layers指定索引图层选卫片图斑层或专门的出图单元层
Name Field页面名称来源,脚本取它当文件名图斑编号字段,如 TBBH
Sort Field出图顺序图斑编号或面积,按需
Extent每页显示范围一般选「Best Fit」或「Center And Scale」
Scale比例尺统一比例尺填固定值,或按图斑大小自适应
Rotation页面旋转图斑有方向要求时按角度字段旋转

配置里最容易翻车的是 Extent 和 Scale 的组合。选「Best Fit」会让每个图斑自动缩放到合适大小,但比例尺就不统一了,出图看着大小不一;选「Center And Scale」配合固定比例尺,图斑大小一致,但特别大的图斑可能超出页面。卫片图斑出图一般要求比例尺统一,方便比对,所以我通常锁固定比例尺,再给页面留够边距。

配置完点 OK,在布局视图里翻几页看看,范围是不是跟着图斑跳、动态文本是不是跟着变。这一步没验证就去写脚本,后面导出来一堆空图或者错位图,排查起来更费劲。

2.3 布局里的动态文本怎么加

动态文本是让每张图「有信息量」的关键。在布局视图里 Insert → Dynamic Text,能看到一堆可选类型:Data Driven Page Name、Data Driven Page Attribute、Data Driven Page Number 等。

  • Data Driven Page Name:插入当前页名称,也就是 Name Field 的值,通常放标题位置。
  • Data Driven Page Attribute:插入索引图层任意字段的值。卫片图斑场景下,把所在村、图斑面积、图斑类型这几个字段各插一个,出图时每张自动带对应图斑的信息。
  • Data Driven Page Number:插入「第 X 页 / 共 Y 页」,方便归档。

插入的时候注意字段类型。面积字段如果是双精度,直接插进去可能带一长串小数,在动态文本属性里可以设格式,保留两位就行。类型字段如果是编码,要么在属性表里先算好中文,要么在动态文本里做映射,别导出来一堆「01」「02」看不懂。

布局调好后保存 mxd。这一步的成果是:手动翻页能正常出图,动态文本内容正确。脚本只是把这个手动过程自动化,手动都不对,脚本只会错得更快。

3. arcpy 批量导出脚本:循环、命名与格式控制

3.1 脚本骨架与逐行拆解

原始脚本很短,但每一行都有讲究。先看完整版,再逐段说:

# coding:utf-8 import arcpy # 打开配置好 Data Driven Pages 的地图文档 mxd = arcpy.mapping.MapDocument(r"F:\GeoData\ChinaArea\ChinaVector.mxd") # 从第 1 页遍历到最后一页 for pageNum in range(1, mxd.dataDrivenPages.pageCount + 1): # 切换到当前页 mxd.dataDrivenPages.currentPageID = pageNum # 取当前页名称字段的值,作为输出文件名 mapName = mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) print mapName # 导出当前页为 PNG arcpy.mapping.ExportToPNG( mxd, r"F:\GeoData\ChinaArea\Province\\" + mapName + ".png" ) print 'ok'

逐行说。arcpy.mapping.MapDocument加载 mxd,路径用原始字符串r"...",避免反斜杠被当转义符——这是 Windows 路径下最常见的翻车点,不写r前缀,\t、\n会被解释成制表符和换行,路径直接废掉。

mxd.dataDrivenPages.pageCount是总页数,等于索引图层的记录数。循环用range(1, pageCount + 1),因为currentPageID是从 1 开始计数的,不是 0。原始脚本写的是range(1, pageCount),会漏掉最后一页,这是个隐蔽的 off-by-one,图斑少的时候不容易发现,几百个图斑跑完少一张,对账才对得出来。

currentPageID = pageNum是翻页动作,执行后 mxd 的当前页面就切过去了,布局里的动态文本、地图范围全部刷新到对应图斑。

pageRow.getValue(pageNameField.name)取当前页名称字段的值。pageRow是当前页对应的属性行,pageNameField是配置时指定的名称字段。取出来的值直接当文件名用。

ExportToPNG导出当前布局。第一个参数是 mxd 对象,第二个是输出路径。路径拼接用+,注意目录末尾要有反斜杠,否则Province和文件名会粘在一起。

3.2 导出格式与分辨率参数

ExportToPNG只是导出函数家族里的一个。arcpy.mapping 提供了一整套:

函数输出格式典型用途
ExportToPNGPNG无损、带透明通道,适合后续叠加
ExportToJPGJPG体积小,适合汇报、存档
ExportToPDFPDF矢量保留好,适合打印
ExportToTIFFTIFF带地理信息,适合再分析
ExportToBMPBMP无压缩,体积大

这些函数都支持resolution参数控制 DPI。默认导出用的是屏幕分辨率,出图偏糊。卫片图斑出图如果要打印或者细看,把 resolution 提到 200 或 300:

arcpy.mapping.ExportToJPG( mxd, r"F:\GeoData\ChinaArea\Province\\" + mapName + ".jpg", resolution=300 )

JPG 还可以传world_file=True生成坐标世界文件,方便图片带地理参考。但注意 JPG 是有损压缩,图斑边界线细的时候会有噪点,对精度要求高就换 PNG 或 TIFF。

注意:导出分辨率拉高,单张图体积和耗时都会涨。几百个图斑跑 300 DPI,内存吃紧的机器可能中途卡死。稳妥做法是先拿 5 个图斑试跑,看单张耗时和体积,再决定全量参数。

3.3 文件名清洗与目录预建

mapName直接当文件名,前提是名称字段的值「干净」。实际数据里图斑编号可能带斜杠、冒号、星号这些 Windows 文件名非法字符,也可能有前后空格。不清洗,脚本跑到那一页直接抛异常,前面导出的全白跑。

常见做法是加一层清洗:

import re def clean_name(name): # 去掉首尾空格 name = name.strip() # 把 Windows 非法字符替换成下划线 name = re.sub(r'[\\/:*?"<>|]', '_', name) # 空值兜底 if not name: name = 'unnamed' return name mapName = clean_name( mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) )

re.sub那段正则把反斜杠、斜杠、冒号、星号、问号、双引号、尖括号、竖线全部替换成下划线,这几个是 Windows 文件名明确不允许的字符。空值兜底是防止某些图斑名称字段为空,空字符串拼出来的路径会变成目录本身,导出直接失败。

输出目录也要预建。脚本不会自动创建不存在的文件夹,路径里Province目录不存在,第一张图就报错。跑之前手动建好,或者在脚本开头用os.makedirs判断创建:

import os out_dir = r"F:\GeoData\ChinaArea\Province" if not os.path.exists(out_dir): os.makedirs(out_dir)

3.4 在 Notepad++ 里跑脚本的实操细节

原始流程提到用 Notepad++ 执行 Python,图的是轻量、改完就能跑。Notepad++ 本身不执行 Python,是靠 NppExec 插件调用系统 Python 解释器。配置路径:Plugins → NppExec → Execute,输入类似:

python "$(FULL_CURRENT_PATH)"

$(FULL_CURRENT_PATH)是 Notepad++ 的宏,代表当前文件完整路径。保存脚本时另存为.py后缀,编码选 UTF-8,因为脚本头有# coding:utf-8,编码不对中文注释和中文文件名都会乱。

这里有个版本坑:ArcGIS 10.2 自带的是 Python 2.7,print是语句不是函数,脚本里写print mapName没问题,写print(mapName)也能跑但语义不同。如果系统里同时装了 Python 3,Notepad++ 调用的可能是 3.x,print mapName直接语法错误。稳妥做法是明确指向 ArcGIS 自带的解释器路径,通常在C:\Python27\ArcGIS10.2\python.exe这类位置。

跑起来后控制台会逐行打印图斑名称,最后打印ok。看到ok再去目标文件夹核对数量,别中途瞄一眼就开始数。

4. 避坑与排查:批量出图最常见的五类翻车

4.1 导出图片数量对不上

现象:索引图层有 300 个图斑,导出目录只有 299 张,或者多出一张空图。

原因:循环范围写错是最常见的。range(1, pageCount)少一页;range(0, pageCount)多一页且第 0 页无效。另外索引图层如果有重复的名称字段值,后导出的会覆盖先导出的,数量也会少。

解决:循环统一写range(1, pageCount + 1)。名称字段先做重复检查,在属性表里用字段计算或 Python 脚本找出重复值,重复的加序号后缀区分。

4.2 动态文本显示为字段名而不是值

现象:导出的图上标题位置显示的是「TBBH」而不是具体图斑编号。

原因:动态文本插入时选错了类型,插成了普通文本或者字段名占位,没有绑定到 Data Driven Page Attribute。也可能是 mxd 保存前没刷新页面。

解决:重新插入动态文本,确认选的是 Data Driven Page 相关类型。插入后在布局里翻几页,看文本是否跟着变。不变就是没绑对。

4.3 路径报错或文件导到奇怪位置

现象:脚本报 IOError,或者图片导到了上一级目录、文件名被截断。

原因:路径没加r前缀导致转义;目录末尾漏了反斜杠导致路径拼接错误;文件名含非法字符。

解决:所有路径加r前缀;拼接时确保目录以\结尾;文件名走清洗函数。这三条一起上,路径类问题基本绝迹。

4.4 比例尺不统一或图斑超出页面

现象:有的图斑占满整页,有的只占一个小角;或者大图斑被裁掉一部分。

原因:Extent 选了 Best Fit 导致比例尺自适应;或者固定比例尺下页面边距留得不够。

解决:要统一比例尺就选 Center And Scale 加固定值,然后在布局里把数据框缩小一点留边距。要自适应就接受比例尺不统一,但至少保证图斑完整。两者不可兼得,按出图目的选。

4.5 脚本跑一半卡死或内存溢出

现象:跑到一两百张时 ArcMap 无响应,或者报内存错误。

原因:mxd 对象在循环里持续累积状态,高分辨率导出尤其吃内存。Python 2.7 的内存回收也不够积极。

解决:分批跑,比如每 100 个图斑重启一次脚本,用参数控制起止页。或者降低导出分辨率。跑之前关掉 ArcMap 里其他不相关的地图文档,释放内存。

5. 进阶:把出图脚本改成可复用工具

跑通基础版之后,我一般会把脚本往「可复用」方向改,省得每次换项目都重写。核心思路是把硬编码的路径、字段名、格式参数抽成变量,再包一层命令行参数。

# coding:utf-8 import arcpy import os import re import sys def clean_name(name): name = name.strip() name = re.sub(r'[\\/:*?"<>|]', '_', name) return name if name else 'unnamed' def batch_export(mxd_path, out_dir, fmt='JPG', dpi=200): if not os.path.exists(out_dir): os.makedirs(out_dir) mxd = arcpy.mapping.MapDocument(mxd_path) total = mxd.dataDrivenPages.pageCount for pageNum in range(1, total + 1): mxd.dataDrivenPages.currentPageID = pageNum raw = mxd.dataDrivenPages.pageRow.getValue( mxd.dataDrivenPages.pageNameField.name ) name = clean_name(str(raw)) out_path = os.path.join(out_dir, name + '.' + fmt.lower()) if fmt.upper() == 'JPG': arcpy.mapping.ExportToJPG(mxd, out_path, resolution=dpi) elif fmt.upper() == 'PNG': arcpy.mapping.ExportToPNG(mxd, out_path, resolution=dpi) print('%d/%d %s' % (pageNum, total, name)) del mxd print('done') if __name__ == '__main__': batch_export( r"F:\GeoData\ChinaArea\ChinaVector.mxd", r"F:\GeoData\ChinaArea\Province", fmt='JPG', dpi=200 )

改动点说几个。os.path.join拼路径,跨平台且自动处理分隔符,比手写+稳。del mxd在循环结束后显式释放地图文档对象,配合分批跑能缓解内存压力。进度打印改成当前页/总页 名称,跑长任务时一眼能看出卡在哪一页。格式和 DPI 做成参数,换需求不用改函数体。

验证方法很直接:拿一个只有 3 到 5 个图斑的小索引层先跑,核对三件事——数量对不对、文件名对不对、图上动态文本对不对。三样都过,再换全量数据。这个习惯是我踩过「跑了两小时发现字段绑错」的坑之后养成的,从那以后每次改完脚本,都强制先拿小数据走一遍全流程,确认无误再上生产数据。

如果图斑量特别大,还可以在循环里加异常捕获,单页失败不中断整体,把失败页号记到日志里,跑完统一补。这样即使个别图斑数据有问题,也不至于让整批任务前功尽弃。希望帮到你。

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

RELATED READING

延伸阅读

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