简介:面向GIS制图与测绘内业人员,这份文档聚焦ArcGIS按卫片图斑批量导出JPG的实际需求,适合已具备ArcMap基础、希望用数据驱动页面和Python脚本提升出图效率的初中级用户。压缩包仅含1个doc文件,大小约29KB,体量轻便,内容围绕数据驱动页面的图层、名称字段、排序字段与显示范围设置,布局中的动态文本,以及基于arcpy遍历页面并调用ExportToPNG导出图片的脚本示例展开,并提示版本与运行环境要求。目前已有8322人学习。读者可据此理解批量出图的完整组织方式,获得可修改路径、字段和导出参数的脚本模板,并参考动态文本与结果检查思路,将重复手工出图转为自动化流程,适用于卫片执法、图斑核查等大批量制图场景。
1. 卫片图斑批量出图:把一整个下午压成三分钟的活儿
卫片图斑一下来就是三百多条,每条要一张能看清位置的图,图上还得带村名、面积、类型。手动在 ArcMap 里挪数据框、改标题、导出,一个下午基本就交代了。Arcgis 批量出图这件事,组合其实很朴素:Data Driven Pages 负责一页一个图斑,arcpy 负责把几百页一口气导成 JPG,ArcMap 10.x 自带,不装插件、不买扩展模块。这套流程适合手里有图斑面图层、需要按图斑逐个留痕的作业人员,卫片核查、变更调查、临时用地排查都用得上。前提就两条:Desktop 10 以上(10.2 最稳),以及愿意写十来行 Python。下面按配置、脚本、踩坑的顺序拆开讲,能照着复现的那种。
2. 数据驱动页面:索引图层、名称字段和比例尺的三个决定
数据驱动页面(Data Driven Pages)不是什么高级货,机制一句话就能说清:它挂一个"索引图层",按这个图层的要素记录数生成同样多的页面,第 N 页就是把数据框范围挪到第 N 条记录上,仅此而已。所以批量出图的质量,八成在配置阶段就定了,剩下两成才是脚本的事。很多人脚本跑得挺顺,出来的图却一张张歪着、裁着、比例尺乱跳,回头查半天发现是索引图层选错了、名称字段重号了、范围模式没想清楚。
这一章把三个决定讲透:索引图层怎么来、名称字段和排序字段怎么定、范围和比例尺怎么选。这三件事在 ArcMap 的 Data Driven Pages 工具栏里是三个下拉框和一个设置面板,看着简单,实际是整套流程里最容易埋雷的地方。
2.1 索引图层不是随便挑的:三种图斑数据组织的取舍
先明确一点,数据驱动页面的索引图层必须是要素图层,面、线、点都行,卫片图斑场景下就是面图层,不能是栅格、不能是独立属性表。实际拿到的图斑数据通常有三种组织方式,每种的处理方法不一样。
第一种,一个要素类装全部图斑,字段里有批次、图斑编号、村名、面积、类型。这是最省事的,直接把图层加进 ArcMap,工具栏里选中它就行,什么都不用改。字段名随便起,因为有文件地理数据库的 255 字符字段名余量,不怕截断。
第二种,按批次分成好几个图层或要素类,比如第一批、第二批、第三批各一个 shapefile,或者同一个 gdb 里三个要素类。这种情况最常翻车,因为数据驱动页面一次只能挂一个索引图层,三个批次要么合并成一个临时索引图层再出图,要么分三轮改配置、跑三轮脚本。我一般选合并,合并时补一个batch字段写清批次号,名称字段里带上批次前缀,后面出问题时一眼能看出是哪一批的图。
第三种,一个要素类里用批次字段区分,但要求只出其中某一批。这时候有人会想到用定义查询筛一下,省得合并。这条路我不建议走:数据驱动页面的页面列表是在工具栏里点"确定"那一刻定下来的,配置完之后再改定义查询,页数不会跟着变,很容易出现"明明筛出来 120 条,出图还是 300 张"的玄学问题。稳妥做法是先按条件导出一份索引副本,用Feature Class To Feature Class或者直接在图层上右键导出,拿副本去挂数据驱动页面。
合并和拆分这两个动作,用工具箱里的"合并"(Merge)就够,不用写脚本。合并前把三个批次的字段结构对齐,缺的字段补空,字段类型不一致(比如一边是文本一边是长整型)会直接报错,这类报错信息还算友好,看提示改就行。
索引图层还有两个小习惯值得坚持。一是放在本地磁盘或者本地文件地理数据库里,别直接指向网络共享路径,导出时几百次读取,网络一抖就断,而且断在第 180 张的时候最难受。二是索引图层本身可以在内容列表里关掉显示,页面切换不受影响,但千万别删——删了整份配置就失效了。出图时我把索引图层留着但设成空心无填充,这样万一图斑边界需要露出来,临时打开就行。
2.2 名称字段与排序字段:决定输出文件名的两个关键项
名称字段(Page Name Field)是数据驱动页面里最关键的配置项,因为它直接决定脚本里拿到的页面名,也就决定了输出的 JPG 文件名。默认情况下 ArcMap 会让你选一个字段当页名,选错了后面全乱。
三条硬性要求。第一,值必须唯一。页面名重复不会报错,页面照样生成,但导出时同名文件互相覆盖,你以为出了 300 张,实际磁盘上只有 280 张,而且丢的是哪几张完全看不出来——这是最典型的血泪经验。第二,值不能为空。空值在脚本里取出来是None,拼到路径里要么报类型错误,要么生成一个叫None.jpg的文件,还被下一张覆盖。第三,值里不能有 Windows 文件名非法字符:反斜杠、斜杠、冒号、星号、问号、双引号、尖括号、竖线。图斑编号里带个斜杠(比如2024/03/001)看着很正常,落到文件名上直接 IOError。
我一般的做法是不直接拿业务字段当页名,而是在索引图层里新加一个out_name文本字段,规则固定成"批次_四位序号_村名",用字段计算器预计算好。这样页名可控、可读、可复现,出了问题按名字就能定位到具体图斑。字段计算器里贴的是 Python 2.7 的解析器,代码块这样写:
# 前置字段:batch(文本)、no(长整型)、village(文本) # 输出字段:out_name(文本,长度 80) def build(batch, no, village): b = (batch or "B0").strip() n = "%04d" % (no if no is not None else 0) v = (village or "未知").strip().replace(" ", "") return "%s_%s_%s" % (b, n, v)表达式填build(!batch!, !no!, !village!),勾选"Python",把上面这段贴进"预逻辑脚本代码"框里。缩进必须是四个空格,混用 Tab 会直接报语法错误,这个坑我踩过不止一次。
排序字段(Sort Field)决定页面的先后顺序,也就是 pageID 的分配顺序。它影响两件事:出图顺序,以及断点续跑时的分界点。建议用"批次 + 序号"排序,别用 OBJECTID——要素类一重整,OBJECTID 会重排,之前跑的进度记录就全废了。排序字段和名称字段用同一套编号逻辑最省心,页名最后四位就是序号,脚本跑到第几张一目了然。
名称字段定完之后做个唯一性检查,用"频数"(Frequency)工具跑一下out_name字段,输出表里 COUNT 大于 1 的就是重复值。这一步三十秒,能省掉后面两小时的排查。
2.3 范围与比例尺:Best Fit 好用,但不是所有场景都能用
数据驱动页面的"范围"设置里有几档模式,卫片图斑场景下真正会用到的就三种,差别很大。
| 范围模式 | 行为 | 适用场景 | 主要问题 |
|---|---|---|---|
| 最佳匹配要素(Best Fit) | 每页自动缩放,把当前图斑塞满数据框 | 图斑大小接近、只求看清 | 每页比例尺都不同,图与图之间不可比 |
| 中心并保持比例尺 | 中心对准图斑,比例尺固定 | 需要统一比例尺的图册 | 小图斑几乎看不见,大图斑被裁 |
| 固定比例尺 | 比例尺写死 | 严格对比、上报成果 | 必须事先算准最大图斑的外接矩形 |
| 要素宽度百分比 | 按图斑宽度的百分比控制填充 | 需要留出固定标注空间 | 长条形图斑高度方向容易溢出 |
最佳匹配要素是最省事的,但它有个副作用:比例尺动态文本必须用"当前比例尺",不能写死,否则每张图上的比例尺都是错的。另外图斑大小差异大的时候,出图册翻起来很晕——上一张 1:2000,下一张 1:15000,读者没法横向比较。
固定比例尺的做法更专业,代价是要先算最大图斑。写一段脚本遍历索引图层的外接矩形,取宽高最大值,再结合页面绘图区的物理尺寸反推:
# coding:utf-8 import arcpy fc = r"D:\GISWork\weipian\index.gdb\tb_index" max_w = 0.0 max_h = 0.0 with arcpy.da.SearchCursor(fc, ["SHAPE@"]) as cur: for row in cur: ext = row[0].extent max_w = max(max_w, ext.width) max_h = max(max_h, ext.height) print("max width: %.2f, max height: %.2f" % (max_w, max_h)) # 假设页面可用绘图区 18cm x 13cm,基准比例尺 1:2000 对应实地 360m x 260m base_denom = 2000.0 base_w = 360.0 base_h = 260.0 ratio = max(max_w / base_w, max_h / base_h) print("suggested scale denominator >= %d" % int(base_denom * ratio))这段脚本有两个关键点。SHAPE@直接取几何对象、不落盘,比先加面积字段再读快得多。更关键的是单位:如果索引图层是地理坐标系(单位是度),extent.width拿出来就是度,跟米混算会得出一个荒唐的比例尺。跑之前先用"投影"工具转成投影坐标系,或者确认数据框的坐标系是投影坐标系,这一点跳过去,后面全是白算。
页面边距(Margin)也在这块设置,默认是 5% 或 10%,图斑贴着边的时候标注容易被切掉,调到 10% 到 15% 比较稳。图斑特别狭长的,宁可用"要素宽度百分比"配合额外的偏移,也别硬套最佳匹配。
3. 布局里的动态文本:让每张图自己写上村名、面积和图斑类型
配置完数据驱动页面,切换页面时数据框会动,但布局上的文字不会自己变。要让每张图上都带上当前图斑的村名、面积、类型,靠的是动态文本。这一块很多人图省事,用脚本去改文本框内容,改着改着就发现有的元素改了、有的没改,最后放弃。正确的路子是在布局阶段就把动态文本插好,脚本只管导出。
3.1 动态文本的插入方式与字段绑定
ArcMap 的插入菜单里有专门的"动态文本"分组,常用的有几种:页面名称、页码、当前日期、当前比例尺、以及最关键的"数据驱动页面属性"。最后这个是要重点用的,它能把索引图层当前行的任意字段值直接渲染到布局上。
操作路径是:插入 → 动态文本 → 数据驱动页面属性,在弹出的对话框里选字段和格式。插进去之后就是一段普通文本框,位置、字号、颜色随便调,但内容会在导出时按当前页自动替换。这一步做好,脚本里一个字都不用改文本,导出就是对的。
有个细节值得说:动态文本绑定的是"字段",不是"字段别名",但显示时用的是别名。如果属性表里字段别名是空的,图上显示的就是wpmj这种缩写,交出去很难看。出图前把索引图层的关键字段别名都填上中文,这个动作在要素类属性里做,两分钟的事。
字段类型也会影响显示。数值字段默认给一串完整小数,面积那种12345.678901摆在图上很挤,在动态文本的属性里设一下数值格式,保留两位小数加千分位,观感立刻不一样。日期字段默认是yyyy/M/d格式,要别的样式得自己写格式串。空值最麻烦,直接显示<Null>,所以出图前一定把村名、类型这类必填字段的空值补掉,用字段计算器选空值记录批量填"未填"。
还有一个更隐蔽的问题:动态文本绑定的字段如果在脚本运行期间被改了值,文本不会自动刷新。所以索引图层的属性要在出图前定稿,跑脚本的时候别同时开着属性表在编辑。
3.2 图例、比例尺与边距:跟着页面一起变的东西
布局上除了文字,还有几个元素会随页面变化,处理不好就露馅。
图例在数据驱动页面下默认显示整个图层的全部符号,和当前图斑无关。如果图例里放了分类符号(比如图斑类型有五种颜色),每张图上都显示五种,勉强能看;但如果放了索引图层本身,图例会显示"图斑"这一个符号,实际图上画的是底图,读者会懵。我的处理是把图例简化成只保留底图关键图层,索引图层不放进图例,图斑类型用动态文本以文字形式写在角落,比塞一个五色图例干净得多。
比例尺的情况前面提过:用最佳匹配要素时,比例尺文本必须是动态的"当前比例尺",写死的数字一定是错的。用固定比例尺时可以写死,但我还是习惯用动态文本,省得改了设置忘了改字。
指北针不用管,它不随页面变。真正的麻烦是标注的参考比例尺。ArcMap 的地图标注有一个"参考比例尺"的概念,标注大小是相对这个比例尺计算的。如果参考比例尺设成 1:50000,而出图比例尺是 1:2000,标注会变成巨无霸。解决方法是把图层属性的标注选项卡里参考比例尺设成最常用的出图比例尺,或者干脆勾选"以地图单位标注",让字号跟地图锁定。这个设置不做出图的时候不明显,一出图册就全暴露了。
3.3 模板 mxd 的保存习惯:给脚本一个稳定的输入
脚本读的是一个 .mxd 文件,这个文件的状态直接决定出图结果。我的习惯是专门存一份output_template.mxd,所有出图相关的配置都在这份文件里,日常编辑地图另存一份,两者不混。
几个必须做的动作。第一,在地图文档属性里勾上"存储相对路径",这样整个工程目录拷到另一台机器上还能跑,不会因为盘符变了满屏红叹号。第二,数据框重命名成固定名字,比如MAIN,脚本里的数据框参数就可以写死,不用每次去猜默认数据框叫什么。第三,把页面和打印设置里的纸张尺寸、方向、边距都调好并保存,导出函数的宽高参数是覆盖这些设置的,但边距和元素相对位置还是按页面来的。第四,关闭后台地理处理,地理处理菜单下的环境设置里有个"后台地理处理"开关,开着的时候脚本和 ArcMap 会抢资源,跑大批量时容易莫名卡住。
还有一条容易忽略:模板 mxd 里别留任何正在编辑的会话。属性表开着、编辑会话开着、图层在渲染大数据量栅格,都会拖慢导出。跑脚本之前把 ArcMap 关掉最干净——脚本是独立进程,不需要 ArcMap 开着。
4. arcpy 循环跑页面:一个 for 循环,三行核心代码
到这一步,配置都在 mxd 里躺着了,脚本要做的事只有三件:打开 mxd、逐页切换、导出。核心代码真的只有三行,剩下的是路径处理、文件名清洗和异常兜底。
4.1 运行环境:为什么必须用 ArcGIS 自带的 Python 2.7
ArcGIS Desktop 10.x 自带一个 Python 2.7 解释器,位置在C:\Python27\ArcGIS10.2\python.exe,arcpy 就装在这个环境里。系统里如果另外装了 Python 3,直接用python xxx.py跑,会得到ModuleNotFoundError: No module named 'arcpy',或者更早一步的SyntaxError: Missing parentheses in call to 'print'——因为 10.x 时代的 arcpy 是 32 位、绑定 2.7 的编译扩展,跟 3.x 完全不兼容。
用 Notepad++ 跑脚本比较顺手,配置方法是:菜单里选"运行"→"运行",输入下面这行命令,点保存并给个快捷键,比如 Ctrl+F9:
"C:\Python27\ArcGIS10.2\python.exe" "$(FULL_CURRENT_PATH)"$(FULL_CURRENT_PATH)是 Notepad++ 的变量,会被替换成当前文件的完整路径,带空格也能正确处理。命令里的引号不能省,Program Files 路径带空格是常见翻车点。
编码上有两个硬要求。文件第一行写# coding:utf-8,并且文件必须存成 UTF-8 无 BOM。带 BOM 的 UTF-8 在 Python 2.7 里会在第一行报SyntaxError: invalid syntax,报错位置还指向一个看不见的字符,第一次遇到能干瞪眼半小时。
许可证方面,Desktop 基础许可就能跑 arcpy.mapping 的导出功能,不需要任何扩展模块。但如果同一台机器上 ArcMap 正开着并占用了单机许可,脚本启动时可能拿不到许可,表现为RuntimeError: ERROR 000824之类的许可错误。跑批之前把 ArcMap 关掉,省事。
4.2 核心脚本:逐页切换、命名、导出
下面这份是能直接改路径就用的版本,Python 2.7 语法,跑之前把三个路径换成自己的。
# coding:utf-8 import os import re import arcpy # ------- 三个必须改的路径,统一加 u 前缀避免中文路径报错 ------- MXD_PATH = u"D:\\GISWork\\weipian\\output_template.mxd" OUT_DIR = u"D:\\GISWork\\weipian\\jpg" LOG_PATH = u"D:\\GISWork\\weipian\\export_log.txt" # ------- 导出参数 ------- DPI = 200 # 输出分辨率 IMG_W = 1600 # 输出像素宽 IMG_H = 1200 # 输出像素高 START_PAGE = 1 # 断点续跑时改这里 def safe_name(raw): """把页面名清洗成合法文件名""" if raw is None: return u"unnamed" if not isinstance(raw, unicode): raw = raw.decode("utf-8", "ignore") raw = raw.strip().replace(u" ", u"_") raw = re.sub(ur'[\\/:*?"<>|]', u"_", raw) return raw[:120] # 给路径总长留余量 mxd = arcpy.mapping.MapDocument(MXD_PATH) ddp = mxd.dataDrivenPages name_field = ddp.pageNameField.name total = ddp.pageCount print("total pages: %d" % total) if not os.path.isdir(OUT_DIR): os.makedirs(OUT_DIR) log = open(LOG_PATH, "w") # 注意 range 的右开区间,末尾要 +1,否则最后一张不出 for idx in range(START_PAGE, total + 1): ddp.currentPageID = idx # 切到第 idx 页 raw_name = ddp.pageRow.getValue(name_field) # 取当前页名称字段的值 jpg_name = safe_name(raw_name) + u".jpg" out_path = os.path.join(OUT_DIR, jpg_name) # 两边都是 unicode,不会乱码 try: arcpy.mapping.ExportToJPEG(mxd, out_path, "", IMG_W, IMG_H, DPI) print("[OK] %d/%d %s" % (idx, total, jpg_name)) log.write("[OK] %d %s\n" % (idx, jpg_name)) except Exception as e: print("[FAIL] page %d: %s" % (idx, e)) log.write("[FAIL] %d %s\n" % (idx, e)) log.close() del mxd print("done")逻辑上分四段。第一段是路径常量,全部用u"..."的 unicode 字面量,反斜杠写成双反斜杠,这是 Python 2.7 下处理中文路径最省心的写法,后面 5.2 会讲为什么。第二段safe_name干两件事:把非字符串的值转成字符串、把 Windows 非法字符替换成下划线,最后截断到 120 字符,给盘符和目录留出路径长度余量。第三段打开 mxd、拿到dataDrivenPages对象、读页面总数和名称字段名,这几个属性在循环外取一次就行,放循环里重复调用会慢。第四段是主循环,currentPageID赋值完成页面切换,pageRow.getValue()取当前页对应记录的名称字段值,然后拼路径导出。
currentPageID从 1 开始编号,到pageCount结束,没有 0 号页。range(START_PAGE, total + 1)的右边界必须加一,这是最容易漏的一行——原始写法range(1, pageCount)会稳定地少出最后一张图,而且因为前面都成功,很难发现。START_PAGE留着是为了断点续跑,跑到第 180 张断了,改成 181 重跑,前面的不用重来,日志文件配合着看。
try包在导出那一层,是为了让单张失败不中断整批。某一张图斑的字段值特别怪、或者磁盘瞬间写满,异常被捕获记进日志,循环继续往下跑,跑完回头查日志补那几张,比整批重来划算得多。
导出的中文字段值如果没有正常显示,先确认safe_name里decode的编码跟系统一致——中文 Windows 上一般是 GBK,但 mxd 里取出来的字段值通常是 unicode,走isinstance判断那一支就绕过去了。
4.3 导出函数参数:像素、DPI 和质量怎么配
ExportToJPEG是 arcpy.mapping 里导出位图的函数,参数顺序是:地图文档、输出路径、数据框、像素宽、像素高、分辨率、是否输出坐标文件、色彩模式、压缩质量。日常只需要关心前六个,后三个用默认值。
| 参数 | 说明 | 常用值 |
|---|---|---|
| 输出路径 | 带扩展名的完整路径 | unicode 字符串 |
| 数据框 | 空字符串表示使用默认数据框 | "" |
| 像素宽 / 像素高 | 输出图像的像素尺寸,覆盖页面尺寸 | 1200 到 2400 |
| 分辨率 | DPI,与像素尺寸共同决定打印尺寸 | 150 到 300 |
| 坐标文件 | 传"WORLD_FILE"时额外生成 .jgw | 一般省略 |
| 色彩模式 | 真彩色 / 调色板 / 灰度 | 默认真彩色 |
| 压缩质量 | 0 到 100,默认 86 | 80 到 90 |
像素尺寸和 DPI 的关系要理清楚,不然出来的图要么糊要么大得离谱。像素尺寸决定文件里有多少个点,DPI 只影响打印时的物理尺寸标注。同样是 1600×1200 像素,200 DPI 下打印出来大约 20×15 厘米,300 DPI 下只有 13×10 厘米。做内部核对用 150 DPI 就够,报告附图 200 到 250,真要打印出图才上 300。
宽高比最好跟页面设置一致,不然布局元素会被拉伸。页面是横向 A4(297×210),比例接近 1.414,导出宽高给 1600×1131 比较合适,给 1600×1200 会有一点纵向压缩,肉眼能看出来但通常不影响判读。图省事的话直接省略宽高参数,让函数按页面尺寸乘以分辨率来算,这样永远不会变形。
同一套代码换个函数名就能出别的格式:ExportToPNG参数一样,透明背景更好用;ExportToPDF适合要矢量标注的正式成果,文件小、文字不糊;ExportToTIFF一般用于需要地理配准的场景。格式选择放到最后一章再说。
5. 避坑与排查:少出几张、名字乱码、目录被锁的现场记录
前面配置和脚本都顺下来,还是会在实际批次上撞墙。这一章记的都是跑批时真实遇到过的现场,按"现象 → 原因 → 解决"整理,遇到问题照着对。
5.1 最后一张图死活不出来:range 的右开区间
现象:索引图层 312 条图斑,脚本跑完,输出目录里 311 张 JPG,缺的正好是排序最后一条。日志里也没有 FAIL 记录,一切看起来都很正常。
原因:range(1, mxd.dataDrivenPages.pageCount)的右边界是开区间,pageCount等于 312 时,循环变量最大只到 311。这个写法在很多流传的示例代码里都有,抄过来直接用就会少一张,而且少的是最后一页,最容易在"差不多齐了"的心态下被放过。
解决:改成range(1, total + 1),或者用range(0, total)配合currentPageID = idx + 1,两种都行,关键是别让边界差一。跑完之后拿目录里的文件数和pageCount对一遍,数不对就是没跑全,别靠肉眼翻。
5.2 中文文件名报 UnicodeDecodeError 或变成问号
现象:脚本跑到第一张就停,报UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position ...。或者更诡异的情况,不报错,但磁盘上生成的文件名是一串问号,或者文件名只有前半截。
原因:Python 2.7 里str和unicode混用时的隐式转换。OUT_DIR如果是普通字符串(GBK 编码的字节串),jpg_name是从 mxd 里取出的 unicode,os.path.join(OUT_DIR, jpg_name)会尝试用 ASCII 去解码OUT_DIR,遇到中文立刻炸。文件名变成问号则是另一头的问题:脚本里的路径字面量编码跟文件系统编码不一致,写出去就被替换了。
解决:路径全部用 unicode 字面量,也就是u"D:\\GISWork\\..."这种写法,两边都是 unicode 时os.path.join不会触发隐式解码。文件头写# coding:utf-8并保存为无 BOM 的 UTF-8,保证源码里的中文常量本身不乱。实在绕不开字节串,就显式decode("gbk")转成 unicode 再拼。测试方法很简单:先拿一条带中文的图斑单独跑一次,通了再上全量。
5.3 图斑数量对不上:名称字段重号、空值、类型不对
现象:索引图层 300 条,脚本日志显示 300 次 OK,但目录里只有 287 个文件,差了 13 个。或者日志里出现TypeError: cannot concatenate 'str' and 'int' objects。
原因:三种情况混在一起。一是名称字段有重复值,后出的图把先出的覆盖了,日志照样记 OK,因为写文件本身没报错。二是名称字段有空值,取出来是None,清洗函数兜底成unnamed,多个空值互相覆盖。三是名称字段是数值类型(比如图斑编号存成了长整型),getValue返回 int,直接拼字符串就报类型错误。
解决:出图前跑一次"频数"统计,COUNT 大于 1 的挑出来处理;空值用字段计算器批量补;数值字段要么在索引图层里转成文本,要么在脚本里统一str()一次。最稳的做法还是 2.2 里说的,专门建一个out_name文本字段,规则固定、唯一、无空值,脚本只管取用。
5.4 跑到一半卡死或报 IOError:目录被锁、网络盘、同步盘
现象:前 100 多张很顺,突然一张报IOError: [Errno 13] Permission denied,或者整个进程卡住不动,任务管理器里 python.exe 占着 CPU 但没进展。
原因:目标目录里的文件正被别的程序占用。最常见的是看图软件开着上一批成果、资源管理器正在生成缩略图、同步盘客户端正在上传刚写出的文件。如果输出目录还在网络共享或者同步盘目录下,每次写文件都要走一遍网络或同步逻辑,几百张小文件累积起来非常慢,中途断网就直接失败。
解决:输出目录放到本地磁盘,跑完再整体拷贝到共享位置。跑批之前把看图软件、同步盘客户端关掉,资源管理器窗口别停在那几个目录上。真要放在同步目录里,先暂停同步。这类问题没有技术含量,但排查起来很耗时间,因为报错信息指向的是"权限",人会往权限设置上想很久。
5.5 图斑被裁、标注忽大忽小、比例尺对不上
现象:图册翻出来,几张图斑的边缘被切掉一截,标注压在边框外;或者同一批图里,有的标注巨大有的很小;或者图上写的比例尺跟实际量出来的对不上。
原因:三个设置没对齐。图斑被裁一般是范围模式选了"中心并保持比例尺",但比例尺定得太大,超出大图斑的外接矩形,或者页面边距设得太小。标注忽大忽小是图层的参考比例尺跟出图比例尺差太远,标注按参考比例尺缩放。比例尺对不上多半是布局上的比例尺文字是手打的静态文本,而范围模式是动态的。
解决:范围模式和图斑尺寸分布要匹配,差异大的用最佳匹配要素,追求统一的先算最大外接矩形再定固定比例尺。边距调到 10% 到 15%。标注的参考比例尺设成常用出图比例尺。比例尺文字一律用动态文本,不手打。
6. 出图之后的质检与提速:从一堆 JPG 到能交的成果
跑完脚本不等于活干完了,几百张图摊在目录里,得先证明它们齐、对、能看,再谈交付。我的习惯是跑完立刻做一次机器比对,把"缺哪张、多哪张"这个问题用脚本回答,而不是靠数文件。
# coding:utf-8 import os import arcpy FC = u"D:\\GISWork\\weipian\\index.gdb\\tb_index" OUT_DIR = u"D:\\GISWork\\weipian\\jpg" FIELD = "out_name" expect = set() with arcpy.da.SearchCursor(FC, [FIELD]) as cur: for row in cur: if row[0]: expect.add(row[0]) actual = set() for f in os.listdir(OUT_DIR): if f.lower().endswith(".jpg"): actual.add(os.path.splitext(f)[0]) print("expect %d, actual %d" % (len(expect), len(actual))) print("missing: %s" % list(expect - actual)[:10]) print("extra: %s" % list(actual - expect)[:10])比对逻辑是拿索引图层的out_name集合和目录里的文件名集合做差集,输出数量、缺失清单、多余清单。跑一遍十秒钟,比人工翻目录靠谱得多。差额出来之后,改脚本的START_PAGE补跑缺失的那几张就行,不用整批重来。
机器比对只能证明数量对,还得抽几张看质量。我一般按批次抽头、中、尾各两张,重点看三处:动态文本有没有正常替换、比例尺文字和实际是否一致、图斑有没有贴边被裁。这三处对了,整批基本没问题。
提速方面最有效的不是优化代码,而是砍掉不必要的工作量。分辨率从 300 降到 200,单张渲染时间能少三成,文件体积少一半;底图如果是影像服务,提前转成本地栅格并建好金字塔,比每次实时拉取快一个量级;索引图层建空间索引,页面切换时的范围计算会更利索;脚本跑的时候把 ArcMap 关掉,别让两个进程抢同一个许可和同一份数据。
| 输出用途 | 建议 DPI | 1600×1200 像素对应打印尺寸 | 单张体积参考 |
|---|---|---|---|
| 内部核对、快速翻看 | 96 到 150 | 约 42×32 厘米 | 300 到 600 KB |
| 报告附图、成果附件 | 200 | 约 20×15 厘米 | 1 到 2 MB |
| 打印出图、归档 | 300 | 约 13×10 厘米 | 2 到 4 MB |
分批出图的时候还有个提速技巧:先在脚本里加一个批次过滤,只处理当前批次的页,跑完再换下一批。这样每轮的数据量可控,中途出问题影响面小,日志也好读。页码范围加批次过滤两个参数配合,基本能覆盖断点续跑和分批出图两种场景。
最后说个习惯。从那以后我每次跑批量出图,都强制走一遍"三条路径先改、单张先试、日志必开"的流程:路径常量换成当次的、拿一条图斑单独跑通再上全量、日志文件一定要写。这三步加起来五分钟,能挡掉后面百分之九十的返工。踩过的坑多了就知道,GIS 这行的效率提升,一半靠脚本,一半靠不重复犯错。希望帮到你。
本文还有配套的精品资源,点击获取