
上个月帮朋友处理一个项目500多个自然村的村级挂图甲方要求一周内交付JPG和PDF两套图。朋友一开始打算手动导打开MXD右键图层缩放至某个村导出再点下一张。我当时跟他算了笔账一张图从缩放、渲染到导出成功快的话也要1到2分钟500张就是10个小时起步中途如果比例尺没调好、图名写错又得回炉。最后我们用数据驱动页面加脚本一个晚上全部跑完第二天只花半天校对图名。这个经历让我想认真聊聊ArcGIS批量导出图片这件事它不复杂但很多人在“手动导出”和“脚本批量”之间一直没跨过那道坎。ArcGIS批量导出图片本质上是把“设置范围、选择图层、制定命名、点击导出”这四步自动化。它适用的场景远不止村级挂图标准分幅出图、按行政区划出图、按要素逐个生成截图、同一区域不同专题的批量出图全都属于这个范畴。本文不打算只贴一段代码而是把需求拆解、数据驱动页面、ArcPy脚本、参数细节和常见坑位都过一遍争取让刚接触的人能直接照着做也让已经会脚本的人能查漏补缺。1. 别急着点“文件-导出地图”批量导出的场景与需求拆解很多教程上来就教你点哪里但实际项目里翻车的往往不是操作而是需求没想清楚。你连要导出多少张、按什么规则命名、每张图的比例尺是固定还是自适应都没定脚本写得再漂亮也白搭。1.1 四种最常见的批量出图场景我经手的项目基本逃不出下面这四种你可以先对号入座。第一种是按属性分幅典型例子是按行政村、乡镇、流域边界出图每个面要素对应一张图片图名就是村名、乡镇名或流域编码。这种需求在国土、水利、农业项目里特别多面要素的属性表里通常有名称字段直接拿来做文件名。第二种是标准格网分幅按固定大小格网把研究区切成一幅幅图比如1:10000标准图幅或者2km乘2km的网格。这种场景通常需要先做一个格网面要素再逐格导出往往还伴随图幅编号规则命名一般类似“图幅号_行号_列号”或者标准图幅编号。第三种是同一区域的多专题导出研究范围固定不变但图层切换了好几种状态比如同一个区域分别用土地利用、坡度、地质灾害易发性三套符号系统出图。这种“按图层方案批量导出”的任务用数据驱动页面反而不太合适更适合用脚本循环切换图层显隐或者直接在Pro里用书签地图序列处理。第四种是逐要素截图说白了就是遍历要素类里的每个要素把地图缩放过去导出图片常用于生成POI周边环境图、地物巡查照片、选址点位地形图等。这种需求最灵活但也是最容易出现“范围一会儿大一角、一会儿小一角”问题的地方。1.2 为什么手动导出撑不住批量任务有句话叫“一张图手动导没问题一百张就是灾难”。单张导出时你人为地做了大量“隐性决策”当前视口范围在哪、比例尺显示多大、图例要不要露出来、文件名怎么起。批量任务里这些“隐性决策”必须显式化脚本或者数据驱动页面才能替你干活。更关键的是时间账。手动导一张图假设只花1分钟500张图就得6到8个小时而且每50张左右人就会疲劳后续出错率直线上升。改用数据驱动页面之后ArcMap渲染和导出的时间几乎不增加夜间挂机跑完早上起来直接验收。这不是什么高深技术就是一个生产力账。1.3 批量导出前必须拍板的三个设计决策第一输出范围。你是希望每一页刚好显示一个要素的范围还是固定比例尺自适应范围适合不规则的不动产宗地、行政村边界固定比例尺适合标准分幅、地形图图幅之间的比例尺必须一致才能拼接。这个决策直接影响后续索引要素的制作方式。第二命名规则。文件名是人验收的入口命名错了等于全线崩盘。你需要在动手前就确定用哪个字段做文件名是否要在前面加县域代码或年份前缀如果字段里有中文、空格、斜杠等特殊字符怎么处理重名了怎么办。第三输出格式与参数。JPG体积小但不支持透明背景PDF便于打印和矢量化PNG支持透明适合装配到PPT和Web上。分辨率、色彩模式、是否输出世界文件也会在后面专门细说但务必提前想好。提示如果单位用的是ArcGIS基础版许可数据驱动页面很可能用不了只能靠脚本批量导出。许可级别最好在方案阶段就确认不然做到一半换方案很折腾。2. 数据驱动页面ArcMap批量出图的标准答案数据驱动页面Data Driven Pages简称DDP是ArcMap里最正统的批量出图功能它把“每个要素对应一张图”这件事做成了图形化界面不需要写代码。2.1 数据驱动页面的核心原理一个MXD里的“动态窗口”你可以把DDP理解成“同一个MXD套了一个会翻页的窗口”。窗口的边界由索引图层里的每个面要素决定MXD里的数据框每次都自动跟随这个窗口范围刷新。窗口里放什么图例、标题、比例尺、指北针全部由你预先排好的布局决定。索引图层是DDP的灵魂它的属性表里至少要有两样东西一个名称字段比如村名或图幅号和一个排序字段决定导出的先后顺序。DDP本身并不复制数据它只是反复改变数据框的显示范围所以做DDP之前你的MXD内容必须已经调好了符号、标注、图例剩下的工作只是“翻页”。2.2 从索引图层到完整出图流程先准备索引要素。最常用的是用“创建渔网”工具生成规则格网工具路径在Data Management工具集下的Feature Class组里设置好输出范围、行数和列数后会自动生成格网面要素。如果你的分幅是不规则的行政区边界那就直接用现有的村界、乡界面要素确保属性表里有唯一名称字段。然后在ArcMap里打开“数据驱动页面”工具条在Customize菜单的Toolbars里勾选Data Driven Pages。点击工具条上的“数据驱动页面设置”按钮选择索引图层指定名称字段和排序字段。这里有一个容易忽略的点定义范围里一定要选对数据框如果你的MXD里有多个数据框默认可能不是你要导出的那一个。设置完后在布局视图里插入动态文本。用“插入”菜单下的动态文本选择“数据驱动页面名称”或“数据驱动页面编号”再配合索引图层里的字段就能在每张图上自动显示村名或图幅号。我自己习惯把图名放在布局的左上角或正下方字体和位置在普通文本里调好后再替换成动态文本这样排版最稳。全部配好后用“文件→导出地图”导出PDF或者在数据驱动页面工具条上点“导出”按钮。工具条上的导出按钮会列出所有页面你可以选择全部导出也可以只导出当前页或指定页面范围。2.3 DDP配置中的比例尺与命名经验DDP最坑的是比例尺设置。设置窗口里有一个“数据驱动页面”的范围选项常见的是“居中并保持当前比例尺”和“最佳范围”。如果你在数据框里手动缩放到1:5000再设置DDP选“居中并保持当前比例尺”每一页都会以1:5000显示适合规则分幅。但村界这种不规则要素如果比例尺固定了比较窄的村只占图面一小块很难看这时候要用“最佳范围”让每个要素尽量充满图面。命名方面DDP的名称字段直接决定导出PDF里的页面名称。早期版本的ArcMap对中文字段名支持还可以但生成PDF的页面书签如果含特殊字符容易出问题。我的习惯是如果项目允许图名尽量用拼音缩写或图幅编号中文字段只用于图面上的动态文本显示不直接当文件名。DDP里还有一个加分项“地理数据驱动页面”的“网格索引要素”工具在Cartography工具箱里它可以基于现有面图层生成带行列编号的格网索引适合标准分幅的正式项目。3. ArcPy脚本批量导出当内置工具满足不了你的时候数据驱动页面虽好但遇到“要往不同子目录放文件”“排序逻辑特别复杂”“需要在导出前做字段计算”这类需求时脚本才是最终解。ArcPy里最常用的一组函数是arcpy.mapping.ExportToXXX系列。3.1 什么时候必须自己写脚本我对朋友的建议向来是“能用DDP就用DDP但以下场景直接上脚本”。索引要素不是单纯面要素而是每个点要素缓冲后的范围。每一张图的输出路径不同比如按乡镇建子文件夹把村图放进去。导出前需要更新图廓外注记且注记内容来自属性字段拼接。需要把导出的图片再转成固定像素尺寸供系统上传。你的许可级别不支持DDP或者你在旧版ArcView环境里工作。需要在批量导出后自动生成一个汇总Excel清单。脚本的另一个优势是可控性。DDP是“点一下全部导出”脚本则能精确控制哪几张先导、哪几张跳过还能在导出时检测磁盘空间避免中途崩掉。3.2 核心参数解读与可直接套用的脚本模板先看ExportToJPEG这个最常用的函数。它的核心参数有MXD文档对象、输出路径、数据框然后是导出宽度、高度、分辨率、是否输出世界文件、色彩模式、JPEG压缩质量、背景色、透明色。如果你只传了数据框导出结果就是数据框范围内的图片不带图廓和图例如果你不指定数据框或者传入空字符串那么导出的就是整张布局页面。下面这段脚本我直接从一个村级挂图项目里精简出来的逻辑是遍历村界图层里的每个要素把数据框范围切到该要素固定比例尺为1:5000然后导出JPG到对应乡镇子目录。import arcpy import os mxd_path rD:\Work\village_map.mxd out_root rD:\Work\output mxd arcpy.mapping.MapDocument(mxd_path) df arcpy.mapping.ListDataFrames(mxd, 图层)[0] layer arcpy.mapping.ListLayers(mxd, 村界, df)[0] # 先建好乡镇子目录集合 county_field 乡镇名 name_field 村名 with arcpy.da.SearchCursor(layer, [SHAPE, county_field, name_field]) as cur: for row in cur: geom, county, name row out_dir os.path.join(out_root, county) if not os.path.exists(out_dir): os.makedirs(out_dir) out_file os.path.join(out_dir, {}_{}.jpg.format(county, name)) if os.path.exists(out_file): print({} 已存在跳过.format(out_file)) continue df.extent geom.extent df.scale 5000 arcpy.mapping.ExportToJPEG(mxd, out_file, df, resolution300, jpeg_quality90, background_color(255, 255, 255)) print(已完成: {}.format(out_file)) del mxd注意三个细节。第一df.extent赋值后ArcMap会尝试保持当前显示范围的比例你随后再赋df.scale才会真正锁定比例尺这个顺序别搞反。第二SearchCursor只创建一次循环内部只是改数据框范围不要每次循环都重新打开游标几百张图性能差距很明显。第三输出路径里用了“乡镇/村名”的层级目录这就是脚本比DDP灵活的地方。3.3 多格式输出与PDF合并的进阶写法很多项目要求“JPG和PDF各一份”PDF还要合并成一个完整的图册。JPG用上面的脚本直接循环导出即可PDF有两种思路一是用ExportToPDF逐张生成PDF文件再调用arcpy.mapping.PDFDocument把它们合并二是直接把DDP导出成单个多页PDF然后在Pro或Adobe里批量转JPG。import arcpy # 合并PDF out_master_pdf rD:\Work\all_pages.pdf pdf_doc arcpy.mapping.PDFDocument.open(out_master_pdf) for i in range(1, 501): tmp_pdf rD:\Work\page_{}.pdf.format(i) if arcpy.Exists(tmp_pdf): pdf_doc.appendPages(tmp_pdf) pdf_doc.saveAndClose()如果各分幅PDF是分乡镇生成的PDFDocument还能在合并之前用updateDocProperties设置标题和作者最终交付给甲方时专业感会强很多。这个功能知道的人不多但应对“要求按图幅顺序合订”的项目非常管用。4. 导出图片的细节问题分辨率、背景、命名和范围批量导出的脚本写完之后精细调参才是最耗时的环节。很多人在这一步栽了跟头比如导出的图发虚、黑色背景、文件名乱码、范围偏了一半。4.1 DPI、像素尺寸与输出清晰度怎么算ArcPy导出图片有两个控制尺寸的入口df_export_width/df_export_height像素和resolutionDPI。两者是配合关系最终图片的像素尺寸约等于“页面物理尺寸英寸×DPI”。比如你的数据框在布局里宽8英寸分辨率设定为300DPI输出JPG宽度就是2400像素左右如果只设定df_export_width2400ArcMap会反算比例关系。屏幕浏览用的图96到120DPI足够了打印挂图、提交审图至少300DPI如果要做规划公示图放大看细节600DPI也不算夸张。但DPI并非越高越好分辨率翻倍渲染时间可能翻好几倍文件体积也会暴涨。我的经验是非正式校核图统一96DPI跑批终稿才用300DPI出校核和交付分两次跑不会浪费一晚上时间。还有一个容易被忽略的参数是world_file。如果导出的图片后续要放进其他GIS软件里配准或者要和无人机影像叠加强烈建议设为True它会额外生成一个JGW文件存放图片的地理参考信息。没有这个文件图片在其他软件里就是一张“普通照片”。4.2 Nodata黑底问题的成因与解法看过不少人在群里问为什么导出的JPG大片黑色背景。这通常不是导出功能的问题而是渲染背景和透明通道的设置问题。JPEG格式本身不支持透明所以地图窗口里的背景色、数据框背景色、栅格数据的NoData颜色最终都会被“拍扁”成某个RGB值。解决方案有几层。第一层在“数据框属性→框架→背景”里把背景色设为白色这是最直接的。第二层在图层符号系统的“显示NoData”里把NoData颜色改成白色避免栅格数据周边出现黑边。第三层如果你确实需要透明背景比如把图贴到汇报PPT的深色背景上那么改用ExportToPNG并检查“透明色”参数。ArcPy里对应的是transparent_color参数比如把白色设为透明色背景就空了。arcpy.mapping.ExportToPNG(mxd, out_file, df, resolution300, transparent_color(255, 255, 255))这段代码里transparent_color把纯白变成透明但要注意如果图里本身存在白色地物它也会变成透明所以透明色这套方案要慎用。4.3 输出文件命名、目录结构与重名防范脚本批量导出最容易翻车的不是ArcMap而是操作系统的文件命名规则。中文、空格、顿号、括号这些字符在Windows路径里虽然大部分合法但偶尔会产生意想不到的编码问题。我踩过的坑是村名里带“/”的脚本直接报错带特殊引号的生成的图片在其他软件里打不开。更稳妥的做法是对名称字段做一层清洗。把/ \ : * ? |这些字符全部替换成下划线同时避免文件名末尾出现空格。下面是清洗函数我几乎在每个项目脚本里都留一份import re def clean_filename(name): cleaned re.sub(r[\\/:*?\|], _, str(name)) cleaned cleaned.strip().replace( , _) return cleaned重名问题同样常见。两个村重名或者同一要素被重复遍历后导出的文件会不知不觉覆盖先导出的。所以我在脚本里坚持加一道“存在即跳过”的判断同时在日志里记录哪个文件是重复的。批量导出的时候睡一觉不管它醒来再查日志比导出完发现少了100张要省心得多。4.4 大数据量导出的内存与速度优化导出图片本质上是一遍完整的地图渲染地图文档越复杂单张耗时越长。优化不是导完之后做而是从MXD本身入手。批量导出前把不需要显示的图层直接关掉把符号系统里过于复杂的制图表达简化把标注缓存打开这些都能实打实减少渲染时间。脚本层面除了前面说的“SearchCursor只创建一次”还要注意ArcMap进程的内存占用。跑几百张图时内容列表里的临时数据、撤销缓存都会累积。如果文档特别大建议每导出50张左右时调用一次arcpy.RefreshActiveView()让ArcMap清理一部分显示缓存或者干脆每100张重启一次ArcMap进程防止导出到后面越来越慢。另外一个工程经验是“导完一张就释放一张”。大项目的MXD里往往挂了影像金字塔和占用内存极大的符号库如果不释放图片句柄Windows很快会报磁盘或内存不足。不过用arcpy.mapping已经不用手动del图片对象了关键是不要把几百个导出路径都放进一个列表再统一处理边遍历边导出才是最稳的。5. 从ArcMap到ArcGIS Pro批量导出的变化与迁移注意点ArcGIS Pro已经成为默认选择很多新项目直接跳过ArcMap开工。批量导出这件事在Pro里的叫法和逻辑都变了早期上手会有点别扭。“数据驱动页面”在Pro里改叫“地图序列Map Series”而ArcPy接口从arcpy.mapping换成了arcpy.mp两者完全不兼容。5.1 Pro里的地图序列两种方式适配不同场景Pro的地图序列分两类空间地图序列和书签地图序列。空间地图序列和ArcMap的DDP最像需要一个索引图层每一页对应一个要素书签地图序列则把一组固定视点书签作为页面适合前面说的“同一区域多专题”出图。创建方式是在布局视图里通过“插入→地图序列”来添加选择索引图层后设置名称字段和排序字段。Pro会把地图框和动态文本一体管理配置界面比ArcMap直观很多但也少了一些“自由放置”的糙快感。如果你是从ArcMap迁过来的最容易漏掉的是动态文本里的“地图序列名称”标签Pro里插入动态文本的入口放在“动态文本”选项卡需要手动绑定地图序列字段。导出的操作也变了不再是“文件→导出地图”而是右键布局或在地图序列面板里选择“导出布局”。Pro支持直接导出当前页面或所有页面格式包含JPEG、PNG、PDF、TIFF等在“输出”对话框里一次设置即可。5.2 脚本迁移arcpy.mp与arcpy.mapping的关键差异最核心的差异是顶层对象ArcMap里是MapDocumentPro里变成ArcGISProjectArcMap里数据框是ListDataFramesPro里地图是ListMaps。但Pro的Map对象本身没有导出图片的方法“导出”动作都挂在Layout上也就是说你至少要有一个人工创建的布局才能把某张地图导出成图片。import arcpy aprx arcpy.mp.ArcGISProject(rD:\Work\demo.aprx) layout aprx.listLayouts(挂图布局)[0] layout.exportToJPEG(rD:\Work\output\page1.jpg, resolution300)如果配合地图序列可以按页抽取aprx arcpy.mp.ArcGISProject(rD:\Work\demo.aprx) layout aprx.listLayouts(挂图布局)[0] ms layout.mapSeries ms.currentPageNumber 1 while ms.currentPageNumber ms.pageCount: out_path rD:\Work\output\page_{}.jpg.format(ms.currentPageNumber) layout.exportToJPEG(out_path, resolution300) ms.currentPageNumber 1这段循环里mapSeries对象会同步更新地图框范围动态文本也会跟着翻页和ArcMap里手动翻页的效果一致。迁移旧脚本时不要试图机械地替换函数名最好按“项目-Layout-地图序列”的层级重新组织代码否则很容易出现ListMaps取到了对象却没有导出方法的尴尬。5.3 旧工程迁移前的渲染一致性提醒MXD在Pro里打开时符号渲染大概率会有细微变化特别是字体、晕线、透明度和图层混合模式。批量导出是“无人值守”的如果渲染变了你不一定第一时间发现等甲方打回来就晚了。所以从ArcMap迁到Pro做批量导出务必先抽样导出10张和ArcMap的旧成果放在一起对比确认符号、标注位置、图例内容没有明显差异再决定是否全量跑批。Pro的Python环境默认是Python 3.x而ArcMap 10.x是Python 2.7两者在编码处理、字符串方法上差异很大。旧脚本里的print加不加括号、中文字符串前缀u这些细节迁移时都会冒出来。最稳妥的做法是旧脚本只做参考基于Pro的arcpy.mp重新写核心逻辑而不是抱着“复制粘贴改改名”的心态。6. 实际项目里的排查经验几个我反复踩过的坑批量出图的功能本身并不难难是难在出了问题没人告诉你它为什么坏了。这里写几个我在项目里真实遇到过的坑每个都是花钱买来的教训。6.1 导出几百张图全是空白只有图框这个坑最容易出现在DDP配置阶段。表现是页面能翻但数据框里什么都没有。排查了一遍发现问题出在“定义范围”指向了错误的数据框或者索引图层的坐标系和数据框坐标系不一致导致数据框范围被设到了奇怪的位置。检查线索是随便翻到某一页看状态栏里的坐标显示如果坐标值和索引要素明显对不上十有八九是坐标系或范围参数的问题。解决办法是进入数据驱动页面设置重新确认索引图层和数据框对应关系并保证索引图层的数据框坐标系一致。6.2 图名永远显示第一页的名称不跟着页面走这个问题在ArcMap里特别容易骗到新手。你把动态文本插入布局后如果不进入“数据驱动页面”预览模式它显示的是当前页的内容或者第一页的内容。很多人看到第一页正常就以为全部正常结果导出来的PDF每一页图名都叫“村A”。验证方法很简单用DDP工具条翻到第二页、第三页看动态文本是否跟着变。如果不变检查动态文本是否真的含有dyn类型的表达式而不是普通文本。唯一的解决办法是重新插入动态文本并确认插入时选的是“数据驱动页面”系列而不是固定文本。6.3 固定比例尺导出后相邻图片范围接不上做标准分幅时脚本里设置df.scale5000后导出本来应该严丝合缝的相邻图幅拼在一起却出现了重叠或者裂缝。问题多半出在数据框的尺寸和比例尺的换算上。数据框在布局中的物理尺寸是固定的当比例尺固定时它在地图上的实际覆盖宽度是“物理尺寸×比例尺分母除以1000”之类的关系如果格网尺寸和这个覆盖范围不匹配自然对不齐。对策是回到“创建渔网”那一步先算好数据框的物理宽度按目标比例尺反算格网的实际边长再生成索引要素。不要用任意格网去迁就比例尺那只会越调越乱。6.4 批量导到一半卡住或中断怎么续导大项目夜里跑批最怕半夜崩了早上起来发现只导了200张还要从头再来。我的做法很土但很有效脚本里加入“存在即跳过”逻辑同时把成功和失败记录写入一个文本日志第二天只重跑失败的页号即可。这个习惯帮我在无数个项目里挽回时间。log_path rD:\Work\export_log.txt with open(log_path, a, encodingutf-8) as log_f: # in loop: try: arcpy.mapping.ExportToJPEG(...) log_f.write(OK {}\n.format(out_file)) except Exception as e: log_f.write(FAIL {} {}\n.format(out_file, e))另外注意Windows的路径长度限制。如果输出目录层级太深加上文件名后总长度超过260个字符脚本会莫名报错。解决办法是尽量把输出根目录放在磁盘根目录下比如D:\out\乡镇_村名.jpg而不是D:\项目\2024\交付\正式成果\图册\乡镇\村名\这种超长路径。批量导出的核心从来不只是“把图导出来”而是“可控、可复现、可持续”。每次项目交付前我都会把MXD、索引图层、脚本和日志一起归档因为甲方大概率会在三个月后说“图件要局部修改请把某几个村重新出一份”。有了一套跑通的批量流程三十分钟就能交付没有这套流程你就得重新面对五百张图的噩梦。希望这篇东西能帮你少走几条弯路。