ArcGIS数据驱动页面与arcpy批量出图实战
简介面向GIS制图与自然资源卫片核查人员的实操文档聚焦ArcGIS按图斑批量出图尤其是按卫片图斑逐一出图并导出JPG、PNG的场景适合已具备ArcMap基础、希望把重复制图流程自动化的初中级用户。压缩包内共1个doc文件约29KB以图文说明形式整理数据驱动页面配置、布局动态文本设置及arcpy脚本批量导出思路并交代ArcGIS 10.2及以上环境与Notepad执行脚本等前提便于对照排查。该文档已有8322人学习下载说明其在批量出图需求中具有一定参考度。获取后可了解从图斑分页、页名与排序字段设置到动态展示村名、面积、类型等属性再到用脚本遍历页面并输出图片的完整做法也能据此调整导出格式、图像参数并补充错误处理减少手工出图耗时适合作为卫片执法、项目制图批量出图的参考材料。1. 从一堆图斑里逐个点“导出”批量出图的真实痛点一叠卫片下发下来图斑编号从 1 排到几十上百每个图斑都要单独出一张 JPG图上还得带图斑编号、所在村、面积和类型。手动做法是挪一次地图框、改一遍文字、点一次导出几十张下来手都麻了编号和截图还容易对错位。ArcGIS 批量出图卡住的从来不是“会不会点导出按钮”而是怎么把图斑变成一份能循环遍历的索引再让脚本按索引自动翻页、自动命名、自动存盘。这套方案对准的就是这类场景ArcGIS 10 以上环境用 Data Driven Pages 配好分幅再用一段 arcpy 脚本把“一个图斑一张图”从体力活变成一次配置一次执行。适合正在做课程设计、毕业设计里带地图输出环节的学生也适合要交付成套制图成果的从业者。2. 总体方案数据驱动页面管分幅arcpy 脚本管循环在动手写任何一行脚本之前先把职责分清楚比急着打开 Python 窗口重要得多。这套方案能跑起来靠的是三层分工图斑图层提供“每页是谁”地图文档提供“每页长什么样”脚本只负责“把每一页重复导出来”。任何一层没准备好后面都是玄学排错。2.1 为什么不是手动分幅也不是 ModelBuilder第一次接这类需求最容易想到的是手动挪地图框一个图斑挪一次改一次文字导出一次。几十个图斑下来手速快的也要大半天而且这种活对眼睛极不友好编号和截图对应的关系全靠人脑记中间只要漏掉一个后面全部错位返工成本比第一次做还高。血泪经验是只要是“每个要素出一张图”这种结构重复的活就不该交给人手。第二个念头通常是 ModelBuilder。工具做数据处理确实好用批量投影、批量裁剪、批量计算字段都能搭出来但它做不了布局页——模型里没有“当前是第几页”这个概念地图框范围不会自动跟着要素跳。所以 ModelBuilder 在这套方案里的正确定位是前置处理把图斑图层洗干净、补好字段、转好坐标系然后交给数据驱动页面接手。至于市面上各种“一键批量出图”的插件本质上还是在调 arcpy 的导出函数。绕不开核心机制不如自己把机制弄明白出了问题才知道去哪看。2.2 三层结构索引层、布局、脚本各管一段把方案拆成三层之后排错就有了方向感。哪一层出问题表现是完全不一样的。层次负责的事配置位置出问题时的典型表现索引层图斑要素类提供每页的要素、页名、排序图层属性表字段总页数为 0、页名重复、空名布局mxd 数据框地图框范围、动态文本、图例、比例尺条ArcMap 布局视图图里文字缺失、图例串页、比例尺不对脚本.py遍历页、取页名、调导出函数Notepad 或 Python 窗口只出一部分图、文件名乱、直接报错先说明白第一层的逻辑数据驱动页面Data Driven Pages会把索引图层的每一行当成一页翻页时用“页面定义查询”把其他图层过滤到当前这一行对应的要素上。也就是说你不需要为每个图斑单独存一个 mxd一个 mxd 加一个索引图层就能覆盖全部图斑。第二层是模板。布局里放的东西所有页共用只有绑定到“页面属性”的那部分内容会随页变化比如页名、某个字段的值。把模板配好一次后面几十上百张图长得都一致这正是评审和归档最看重的。第三层是循环体。脚本不该承担任何“业务判断”它只做三件事切到第 N 页、拿到这一页的名字、把布局导出成文件。任何想往脚本里塞“如果面积大于多少就换个颜色”的念头都应该挪回索引层用字段解决。2.3 环境准备清单版本、Python 和编辑器版本这条线必须说清楚。ArcGIS 10 及以上才有数据驱动页面9.x 没有这个功能拿到 9 的环境别硬试。10.x 系列自带的是 Python 2.7arcpy 装在类似C:\Python27\ArcGIS10.x\的目录下ArcGIS Pro 用的是 Python 3模块名变成arcpy.mp接口完全另一套两者不能混着套代码。编辑器方面Notepad 这类文本编辑器的作用是方便看和改 .py 文件语法高亮、编码切换都省事。但执行不要指望它稳妥的做法是在 ArcMap 的 Python 窗口里跑或者用 cmd 调对应版本的 python.exe。先在 ArcMap 的 Python 窗口里跑一段自检确认环境没走偏# coding:utf-8 # 在 ArcMap 的 Python 窗口里执行确认运行环境 import arcpy, sys print(sys.version) # 10.x 通常输出 2.7.x print(arcpy.GetInstallInfo()[Version]) # 期望看到 10.x 的版本号 print(arcpy.env.workspace) # 当前工作空间建议手动设成项目目录三行的作用不一样。第一行确认解释器是 2.7决定了你后面写except Exception, e这种 Python 2 语法不会被判错第二行拿到 ArcGIS 的真实版本避免在 9.x 上白折腾第三行看工作空间后面拼路径时如果用了相对路径就靠它兜底。跑完这一段没报错说明 arcpy 本身是通的再往下走。3. 索引层与布局让每个图斑对应一张可复用的“图纸”这一章是整套方案里最费时间、也最值得花时间的部分。脚本写得再漂亮索引层和布局没配好出来的图照样不能用。我一般把这一章的工作拆成字段体检、页面配置、动态文本、路径约定四段一段一段验收不跳步。3.1 图斑图层的字段体检名称唯一、无空值、无非法字符索引图层决定了总页数。图斑图层里有 120 个要素正常情况下就该出 120 张图。所以第一件事是确认要素数量和你预期的图斑数量对得上有没有多出来的空要素、重复要素。第二件事是准备一个“页名字段”。数据驱动页面用这个字段的值给每页命名脚本也用它给文件命名。这个字段必须满足三个条件唯一、非空、只含合法文件名字符。图斑编号本身看起来唯一但真实数据里经常出现1(1)、1号、带前后空格、甚至带斜杠的情况直接拿来当文件名轻则文件覆盖重则导出直接失败。稳妥的做法是新建一个专用字段比如DDP_NAME用脚本统一清洗。shapefile 的字段名上限是 10 个字符DDP_NAME是 8 个正好放得下# coding:utf-8 # 为图斑图层生成安全、唯一的页名字段 DDP_NAME import arcpy fc rF:\GeoData\ChinaArea\WeiPian\weipian_tb.shp name_field DDP_NAME # 1) 字段不存在则新增TEXT 长度给 60够放“村名编号”这类组合 exist [f.name.upper() for f in arcpy.ListFields(fc)] if name_field not in exist: arcpy.AddField_management(fc, name_field, TEXT, field_length60) # 2) Windows 文件名禁用字符空格也一并替换掉 bad set(\\/:*?| ) seen {} # 第 4 个参数是读取的字段第 5 个参数是排序字段 rows arcpy.UpdateCursor(fc, , , TBH;DDP_NAME, TBH A) for row in rows: raw row.getValue(TBH) raw u if raw is None else unicode(raw).strip() safe u.join([u_ if c in bad else c for c in raw]) or uUNKNOWN # 同名时追加序号保证导出时不会互相覆盖 if safe in seen: seen[safe] 1 safe u%s_%d % (safe, seen[safe]) else: seen[safe] 1 row.setValue(name_field, safe) rows.updateRow(row) del rows print OK: %d unique names % len(seen)关键点逐条说明。arcpy.UpdateCursor在 10.x 里是旧式游标第 4 个参数传要读写的字段列表用分号分隔第 5 个参数传排序这里按图斑编号升序保证生成顺序稳定。u前缀是 Python 2 的 unicode 字符串中文数据必须走 unicode否则str()一转换就可能抛编码异常。bad集合里把空格也算进去是因为文件名带空格在批量调用其他工具时最容易出问题。seen字典做去重同一个页名第二次出现就补_2、_3这一步能省掉后面“为什么少了几张图”的排查时间。跑完看最后那行输出唯一名数量和要素数量应该一致。如果一致说明索引层的“身份”部分合格了。3.2 数据驱动页面配置名称字段、排序字段、显示范围与比例尺打开 ArcMap在Customize → Toolbars → Data Driven Pages调出工具条点Setup进入配置。四个参数要一次定好索引图层选图斑图层。名称字段选刚生成的DDP_NAME。排序字段按出图习惯来验收方一般希望按图斑编号顺序翻那就选编号如果成果要按村分发选村名字段排。显示范围这一项是整套方案里最容易埋雷的地方。三个选项的差别得说清楚Best Fit每页自动缩放到刚好包住当前图斑。好处是不用管图斑大小坏处是比例尺每页都不一样一本册子翻过去图斑忽大忽小评审看着会觉得不专业。Center and Maintain Scale比例尺固定地图框中心对准图斑。这是最常用的默认值图斑大小差异不大时首选成果里每张图的比例尺条读数一致看起来规整。Data Driven Scale用图层里的一个数值字段给每页单独定比例尺。适合图斑面积跨度极大的情况比如既有几百平方米的小图斑又有几平方公里的连片图斑固定比例尺必然有一头看不清。我的习惯是先按Center and Maintain Scale配一版主比例尺导出两三张看看图斑在图幅里的占比太小就调小比例尺分母太大就调大。真要上 Data Driven Scale比例尺字段必须提前算好并写成数值型字符串字段它认不了。配置完点Apply工具条上的页码输入框应该能正常翻页翻几页看看地图框是不是跟着换了位置。这一步没过不要往下走。3.3 布局动态文本把图斑编号、村名、面积、类型绑上去每张图要让人一眼看出这是哪个图斑、在哪个村、多大面积、属于什么类型。这些信息都来自索引图层的字段靠“动态文本”绑定不用写代码。在布局视图里用Insert → Dynamic Text找到 Data Driven Page 那一组选Page Name插入图斑编号选Page Attribute插入其他字段。插入后会生成一串类似dyn typepage propertyattribute fieldCUNNAME domaindictionary/的标签这串东西交给界面自动生成就行不要手动敲字段名拼错一个字母运行时只会得到一句含糊的报错。四个常用字段的绑法图上文字绑定对象注意事项图斑编号页名Page Name就是DDP_NAME的值和文件名一致所在村村名字段字段有空值时图上会显示空白先补全图斑面积面积字段双精度会输出一长串小数在字段属性里设好显示格式图斑类型类型字段如果类型是编码先建个对照字段存中文面积这一项特别容易翻车。双精度字段绑上去图上可能显示12345.678901234看着像没做完的作业。在字段属性里把显示精度设成两位小数或者在索引层直接加一个文本型的“面积显示字段”用格式化后的字符串填进去后者更稳因为动态文本对数值格式的支持没那么细。文本元素的位置、字号、字体也在这里定。字体建议用系统里确定存在的字体换台机器打开 mxd 时缺字体导致的文字重排是另一个常见坑。3.4 保存地图文档与路径约定配置完把 mxd 存好路径按项目结构固定下来比如F:\GeoData\项目名\批次\map.mxd导出目录单独放在F:\GeoData\项目名\批次\OutJpg\。路径里尽量不带空格和中文虽然现在大多能兼容但真出问题时排查成本不低。脚本里引用的路径一律用原始字符串rF:\...\map.mxd避免反斜杠被当成转义符。保存之后再打开一次 mxd确认数据源没有变成红色的感叹号——只要有一个图层丢源翻页时地图框就是空的而且不会报错只会安静地导出一堆空白图这种问题最难查。提示mxd 存好之后不要随意挪动它引用的数据。确实要挪用 ArcMap 的“设置数据源”重新指一遍或者用arcpy.mapping.MapDocument配合findAndReplaceWorkspacePaths批量改。挪完不重新指出的图就是空白的。4. 批量导出脚本遍历、命名、导出参数与异常处理索引层和布局都验收过了脚本这层其实很短。但短不代表能随便写翻页范围、命名规则、导出参数、异常捕获每一处都有能让整批任务白跑的分支。4.1 脚本骨架从 mxd 到 pageRow下面这版是在原始示例基础上补全过的加了目录创建、异常捕获、进度输出和失败清单# coding:utf-8 # 按数据驱动页面批量导出 JPG import arcpy, os MXD rF:\GeoData\ChinaArea\ChinaVector.mxd OUT_DIR rF:\GeoData\ChinaArea\OutJpg DPI 200 QUALITY 90 if not os.path.isdir(OUT_DIR): os.makedirs(OUT_DIR) # 目录不存在就建避免第一次跑直接失败 mxd arcpy.mapping.MapDocument(MXD) ddp mxd.dataDrivenPages print total pages: %d % ddp.pageCount ok, fail 0, [] for page_id in range(1, ddp.pageCount 1): # 注意 1否则最后一页永远导不出 ddp.currentPageID page_id # 切到当前页 name ddp.pageRow.getValue(ddp.pageNameField.name) name unicode(name).strip() if name else (uPAGE_%03d % page_id) out os.path.join(OUT_DIR, name .jpg) if os.path.exists(out): # 断点续跑已存在就跳过 ok 1 continue try: arcpy.mapping.ExportToJPEG(mxd, out, resolutionDPI, jpeg_qualityQUALITY) ok 1 except Exception, e: # Python 2 语法 fail.append((page_id, name, str(e))) if page_id % 20 0: print progress %d/%d % (page_id, ddp.pageCount) del mxd print done ok%d fail%d % (ok, len(fail)) for item in fail: print item几个关键行值得单独讲。range(1, ddp.pageCount 1)里的1是必须的——原始示例写的是range(1, pageCount)3 页只会导出第 1、2 页最后一页永远静默丢失而且不报错。ddp.currentPageID page_id是切页的唯一入口切完之后ddp.pageRow才指向当前页对应的那一行要素。ddp.pageNameField.name拿的是名称字段的字段名不是值值要用pageRow.getValue()取。导出函数名是ExportToJPEGExportToPNG、ExportToPDF、ExportToTIFF是同族接口参数大同小异。os.path.exists(out)那三行是后悔药一批跑一半中断了第二次执行会自动跳过已完成的不用清空目录重来。异常捕获用except Exception, e是 Python 2 写法在 Pro 的 Python 3 环境里要改成except Exception as e这一处不兼容是跨版本迁移时最容易漏的。4.2 导出参数分辨率、dpi、质量的取舍导出函数里能调的参数不少真正影响成果的只有三个分辨率、dpi、压缩质量。参数含义常用值影响resolution输出图像每英寸像素数96 / 200 / 300直接决定文件体积和清晰度jpeg_qualityJPEG 压缩质量80 ~ 95低于 80 时图上的细线会出现块状伪影world_file是否同时输出坐标信息文件一般 False只做展示图就不需要A4 幅面在 300 dpi 下大约是 2480×3508 像素单张体积可能到两三兆一百多张就是几百兆。如果只是内部核对和归档200 dpi 完全够用图上的文字和界线都看得清。要印成册或者上交评审再上 300 dpi。质量参数我给的是 90低于 80 时图斑边界这类细线会明显发虚别忘了这是给评审看的图。注意分辨率调高不会让本来模糊的底图变清楚只会把模糊放大。底图本身精度不够时先换底图不要指望调 dpi。4.3 命名与目录组织按批次、按村、按页号页名字段唯一只是底线成果怎么组织还得看交付要求。最省事的是平铺一百多张图全扔一个目录里靠文件名排序。图斑数量上到几百平铺就开始难受了找一张图要在资源管理器里翻半天。按村分目录是个稳妥的折中脚本里再取一次村名字段拼成OUT_DIR/村名/页名.jpg目录不存在就先os.makedirs。这样交付时按村分发、按村核对都方便验收方按目录点进去就能看不用打开索引表对照。文件名统一带页名就行不要在前面加日期前缀。日期放进目录层比如OutJpg\2024批次\文件名保持干净后面如果要按编号排序或者做批量改名都不会被前缀干扰。4.4 断点续跑与日志一批跑几十上百张中途断电、ArcMap 崩、图层锁住任何一种都可能让任务停在半路。断点续跑靠的是导出前判断文件是否已存在前面代码里已经带了。光有续跑还不够得知道哪几张失败了、为什么失败。失败清单打印在控制台只是一次性的窗口一关就没了。稳妥做法是同时写一份日志文件# coding:utf-8 # 失败清单落盘便于二次重跑 import io log_path rF:\GeoData\ChinaArea\OutJpg\export_log.txt with io.open(log_path, w, encodingutf-8) as f: f.write(utotal%d ok%d fail%d\n % (ddp.pageCount, ok, len(fail))) for page_id, name, msg in fail: f.write(upage%d name%s error%s\n % (page_id, name, msg))这里用io.open而不是内置open是为了在 Python 2 下稳妥地写 UTF-8中文页名不会变成乱码。日志里记页号、页名和错误信息三样重跑时按页号定位比盲猜高效得多。4.5 在 ArcMap 外运行脚本的两种姿势脚本写好了执行方式有两条路。第一条是在 ArcMap 的 Python 窗口里直接跑或者用File → Open打开 .py 执行。好处是环境变量已经由 ArcMap 配好arcpy 一定能导入适合调试阶段。第二条是脱离 ArcMap用命令行调对应版本的 python.exeC:\Python27\ArcGIS10.2\python.exe F:\GeoData\ChinaArea\export_jpg.py路径按实际安装版本改。这条命令的关键是那个 python.exe 必须是 ArcGIS 自带的那个用系统里另装的 Python 2.7 会报ImportError: No module named arcpy——arcpy 不是 pip 装的包它跟着 ArcGIS 安装目录走。用命令行跑的好处是可以挂到计划任务里夜间自动出图第二天来看结果。5. 常见问题排查五个最容易翻车的点这套流程跑顺了很省事跑不顺的时候表现都挺迷惑的。下面五个是我在实际项目里反复遇到的每条按现象、原因、解决写对照着查能省不少时间。5.1 出图张数比图斑数少现象图斑图层有 120 个要素导出目录里只有 119 个文件脚本没报错控制台输出ok120。原因循环范围写成了range(1, pageCount)Python 的range是左闭右开最后一页根本没进循环。另一种情况是页名字段有重复值两张图写到了同一个文件名上后一张覆盖前一张数量对不上但脚本认为都成功了。解决循环改成range(1, ddp.pageCount 1)。导出前用os.path.exists判重命中就说明发生了覆盖把页名打印出来。页名去重最好在索引层做掉就是第 3.1 节那段脚本。5.2 文件名重复或者变成乱码现象目录里出现????.jpg、1_.jpg或者两个不同图斑指向同一个文件。原因图斑编号里带斜杠、冒号、问号这类字符Windows 不允许做文件名页名是中文而脚本里用了str()强转Python 2 会按 ASCII 编码处理直接抛异常或变成问号。解决索引层统一清洗非法字符替换成下划线全程用unicode而不是str。写日志和写文件名时统一走io.open(..., encodingutf-8)。5.3 每张图的比例尺都不一样现象翻完成果图斑一会儿占满图幅一会儿缩成一小块比例尺条读数每张都不同。原因数据驱动页面的显示范围设成了 Best Fit每页自动缩放。或者设了 Center and Maintain Scale但索引图层的坐标系和地图框不一致缩放逻辑算偏了。解决图斑大小差异不大就切到 Center and Maintain Scale定一个主比例尺。差异确实大改用 Data Driven Scale比例尺字段必须是数值型且单位要和地图单位一致。5.4 跑一半卡死或者内存一路涨现象前五十张正常后面越来越慢最后 ArcMap 无响应或者跑完不退出进程一直挂着。原因脚本里没有及时释放 mxd 引用每次切页都在内存里留一份布局对象。图层本身数据量大、要素多也会拖慢单张导出。解决导出循环结束后del mxd必要时arcpy.management.ClearWorkspaceCache()。单张耗时超过几秒的先用定义查询把图斑图层裁到当前页范围出图会快很多。5.5 命令行执行报 ImportError: No module named arcpy现象ArcMap 的 Python 窗口里跑得好好的换到 cmd 就报找不到 arcpy。原因调用的 python.exe 不是 ArcGIS 自带的那个。arcpy 是随 ArcGIS 安装的不注册到系统的 Python 环境里。解决命令行里写全路径指向C:\Python27\ArcGIS10.x\python.exe。不确定是哪个版本在 ArcMap 的 Python 窗口里执行import sys; print sys.executable把输出的路径抄过去。6. 验收与答辩怎么证明这套批量出图真的能交付成果出来之后别急着打包发人。交付前我会强制走一遍验收清单五分钟左右能筛掉大部分低级错误。检查项合格标准怎么查数量图片数 图斑要素数资源管理器看文件数或 dir /b *.jpg命名文件名与页名字段一一对应抽 5 张对照索引表内容每张图有编号、村名、面积、类型随机翻 10 张看四项是否齐全比例尺除特殊图斑外保持一致抽 3 张对比比例尺条读数边界图斑完整落在图幅内没被裁掉专门翻最大和最小的两个图斑空白没有全白或全黑的图按文件大小排序最小的几张重点看答辩或者交付演示的时候不要一上来就讲代码。我一般按这个顺序走先打开索引图层让人看到“这一层决定出多少张”再打开 mxd 的布局指着动态文本说明“这一张模板服务所有图斑”然后跑脚本让台下看着页码从 1 跳到 60最后打开导出目录点开第一张和最后一张对照。四步走下来评委对这套方案的理解比讲十分钟原理都清楚。评委最爱问的一个问题是“如果我明天换一批图斑要改多少东西”。答案要能脱口而出换索引图层的要素检查页名字段有没有重复改脚本前两行的路径跑一遍。这套流程之所以值得做成脚本就是因为第二次、第三次的成本几乎为零。如果被问到“为什么不做成工具箱”如实说手动点导出容易错位、ModelBuilder 管不了布局页把选型逻辑讲清楚比讲功能列表更有说服力。从那以后我每次交批量出图的成果都强制自己先跑一遍那张验收表再打包哪怕时间再紧也不跳。少一次返工比多讲十句解释有用。希望这套流程帮到你。本文还有配套的精品资源点击获取