简介:面向ArcGIS初学者的Python脚本入门文档,适合缺少编程基础、希望用脚本自动化地理处理任务的地理信息从业者与学生阅读。内容从数学运算符、字符串、模块导入、def函数、if/while/for流程控制与输入输出讲起,再过渡到arcpy地理处理对象创建、帮助查询、列表参数、工具箱与别名调用,并延伸到ModelBuilder中嵌入脚本、PythonWin断点调试、Describe与Exists获取数据信息及地图代数应用,形成由语法到GIS实操的连贯学习路径。压缩包共1个docx文件,约490KB,目录按章节组织,便于按基础语法与ArcGIS集成两部分检索。已有1494人学习,适合作为入门自学与课堂辅助材料,帮助读者理解脚本自动化处理空间数据的基本思路。
1. ArcGIS 里 Python 脚本的使用:先搞清楚它替你省掉的是哪一段手工活
一个目录下躺着几十个 shapefile,要做的是同一套动作:转投影、按研究区裁剪、统一输出到一个工作目录。手工点,一个下午就没了,而且点到第十几个的时候,大概率会漏掉某个参数。这份材料讲的就是 ArcGIS 里 Python 脚本的使用,把这类“步骤固定、对象成批”的活交给脚本去跑。它面向的是刚接触脚本的 GIS 从业者:会用 ArcToolbox,但还没写过几行代码,或者写过却总在路径、覆盖输出、游标这些地方翻车。整份内容分两段,前半段是能用得上的 Python 基本功,后半段是地理处理对象、工具别名、Describe 与游标这些真正跟数据打交道的部分。读完要能做到:拿到一个目录,写一个脚本,跑完批处理,出错时知道去哪看消息。
2. 创建地理处理对象:arcgisscripting、win32com 与 arcpy 三条路的边界
脚本能不能跑起来,第一件事不是语法,是能不能拿到那个“地理处理对象(geoprocessor object)”。它相当于把 ArcToolbox 装进了一个变量里,后面所有工具调用都挂在这个变量上。这一点没搞清,后面写多少行都是白搭。
2.1 三种入口写法与选型理由
不同版本的桌面软件给出的入口不一样,语法长得像,但依赖完全不同。先把三者的差别摆出来,再决定自己的脚本该走哪条路。
| 入口写法 | 依赖 | 特点 | 适用场景 |
|---|---|---|---|
arcgisscripting.create(9.3) | 随桌面安装的 Python 解释器 | 与安装版本绑定,接口与版本一一对应 | 维护老工程里的存量脚本 |
win32com.client.Dispatch("esriGeoprocessing.GpDispatch.1") | 需要 win32com 组件 | 可以在非自带解释器里运行,走的是组件注册 | 想用别的编辑器或解释器调试 |
import arcpy | 随桌面或 Pro 安装的环境 | 工具名、参数顺序与前者一致 | 新写的脚本 |
第一种最常见,也最容易出问题。注意create()里的那个数字是版本标记,不是随手写的浮点数,它决定对象暴露哪一套接口;填错或者跨版本复制脚本,往往表现为工具方法突然不存在。第二种走的是组件分发,好处是不绑定具体解释器版本,坏处是多一层依赖,机器上组件没注册就会在Dispatch那一行直接报错。第三种是后来版本的主线,脚本里把gp.xxx换成arcpy.xxx,工具名和参数顺序基本不用动。
# -*- coding: utf-8 -*- # 方式一:9.x 时代随桌面安装的解释器,入口是 arcgisscripting import arcgisscripting gp = arcgisscripting.create(9.3) # 括号里的版本标记要与安装版本对应 gp.workspace = r"C:\prog\hmbarea" # 方式二:走组件分发,在另一套解释器里也能拿到同一个地理处理对象 import win32com.client gp = win32com.client.Dispatch("esriGeoprocessing.GpDispatch.1") gp.workspace = r"C:\prog\hmbarea" # 方式三:10.x 之后的主线写法,工具名与参数顺序同上 import arcpy arcpy.env.workspace = r"C:\prog\hmbarea" arcpy.env.overwriteOutput = True代码里第一段和第二段不能同时出现在一个脚本里,选一种。r"C:\prog\hmbarea"前面的r表示原始字符串,让反斜杠按字面意思解释,这是路径写法里最省事的一招;不加r就得写成C:\\prog\\hmbarea。工作空间设成文件夹时,脚本里的相对名字会在这个文件夹下解析;设成文件地理数据库时,解析目标就变成库里的要素类,两种情况的路径行为不一致,这是后面很多“找不到数据”问题的根源。
2.2 工作空间、覆盖输出与前缀参数
gp.workspace = r"C:\prog\hmbarea" gp.overwriteoutput = 1这两行几乎是所有地理处理脚本的开头。workspace告诉对象“相对名字去哪找”,overwriteoutput决定同名结果已经存在时是报错还是直接覆盖。调试阶段建议开成 1,否则你会反复手动删中间结果,效率极低;交付脚本时建议关掉或者提前做存在性判断,避免误覆盖别人的成果。
路径写法上有几个约定值得记住:正斜杠和反斜杠在大多数工具里都能识别,混着写通常不出问题,但拼字符串时容易出事,所以统一用一斜杠再加r前缀最稳。工作空间结尾不要多写一个斜杠,某些版本会把它拼成双斜杠导致解析失败。输出路径尽量写全,不要依赖“当前目录”,脚本被别的工具调用时,当前目录未必是你以为的那个。
2.3 第一个能跑完整的批量脚本
把上面这些拼起来,给一个真正能干活的骨架:遍历工作空间里的面要素,逐个做缓冲,把结果写进另一个目录。
# -*- coding: utf-8 -*- import arcgisscripting gp = arcgisscripting.create(9.3) gp.workspace = r"C:\prog\hmbarea" # 输入数据所在文件夹 gp.overwriteoutput = 1 # 调试期允许覆盖 out_ws = r"C:\prog\out" # 输出目录,必须提前建好 buf_dist = "200 Meters" # 缓冲距离,单位跟随输入数据的坐标系 fcs = gp.ListFeatureClasses("", "polygon") # 第二个参数限定只返回面要素 fc = fcs.Next() while fc: name = fc.split(".")[0] # 去掉扩展名,做输出文件名 buf = out_ws + "\\" + name + "_buf.shp" gp.Buffer_analysis(fc, buf, buf_dist) # 工具名 = 名称 + 下划线 + 别名 gp.AddMessage("done: " + buf) # 写进工具消息,界面上能看到 fc = fcs.Next()逐段说明。ListFeatureClasses("", "polygon")的第一个参数是通配符,空字符串表示不过滤;第二个参数限定要素类型,取值不区分大小写。它返回的不是普通列表,而是一个枚举器,必须用Next()一个个取,取到空值表示结束——直接拿它去for循环,在老版本里会报类型错误,这是新手最常见的翻车点之一。
Buffer_analysis的命名规则是“工具名 + 下划线 + 工具箱别名”。别名是为了区分同名工具,后面会专门讲。缓冲距离写成带单位的字符串,是因为输入数据可能用地理坐标系,这时候不带单位的数字会被当成十进制度,结果差出几百公里。AddMessage与print的区别很关键:脚本作为工具箱工具运行时,print的输出不会进结果窗口,只有AddMessage写进去的消息才看得见。调试期用哪个都行,一旦打包成工具,必须换成AddMessage。
3. 把 Python 基本功落到 GIS 上:数值、字符串、目录遍历与参数入口
语言基础那一部分,单独看没什么意思,落到 GIS 场景里才有价值。这一章挑四类真正天天用到的写法:数值运算里的小陷阱、字符串与路径处理、目录遍历、以及脚本接收外部参数的两套入口。
3.1 整型浮点、取模与方位角
GIS 里最常见的数值问题出在类型上。两个整数相除,老版本 Python 得到的是整数,5/2结果是2而不是2.5;只要参与运算的任意一边是浮点,结果才是浮点。距离、面积、比例这类计算,输入数据往往来自字段读取,字段值可能是整型,一不小心就把小数截了。习惯做法是:只要涉及除法,先float()一下。
取模在方位角计算里非常好用。方位角加 180 度表示反向,但加法之后可能超过 360,取模运算正好用来把角度折回有效范围。
az = 270 # 原始方位角,正北为 0,顺时针增加 new_az = (az + 180) % 360 # 反向方位角,结果 90 print new_az # 两点间平面距离:math.hypot 比手写平方根更省事 import math x1, y1 = 520382, 4152373 x2, y2 = 520475, 4152963 dist = math.hypot(x2 - x1, y2 - y1) # 结果约 597.28 print distmath.hypot(dx, dy)等价于math.sqrt(dx**2 + dy**2),但内部做了溢出保护,坐标值很大时更稳。注意math里的三角函数用的是弧度,输入经纬度或者度分秒之前,必须先乘math.pi/180转一下,这类错误不会报错,只会让结果安静地错掉。计算太阳高度角这种场景也一样:sunangle = 90 - lat + decl,南半球纬度取负值,算完如果超过 90,惯常做法是把它折成180 - sunangle同时把方位角翻到 0。
3.2 字符串切片、路径拆分与文本文件定长读取
字符串在 GIS 脚本里承担两件事:拼路径和拆路径。拼的时候用+或者os.path.join,拆的时候用split,取扩展名用endswith或find。下标从 0 开始,s[1:3]表示从下标 1 取到下标 3 之前、不含 3,s[-1]是最后一个字符,s[2:]是截到末尾。
有个场景能说明定长读取的用处:早期某种 DEM 文本格式把高程单位藏在文件开头固定位置的某个字符上,1表示英尺,2表示米。如果不做这个判断,把英尺当成米算坡度,结果会整体偏大。这种活只能靠按位置取字符。
# 读取文本格式数据里固定位置的标记字符,判断高程单位 infile = r"C:\prog\dem\sample.dem" f = open(infile, "r") firstline = f.readline() unitchar = firstline[539] # 下标 539 对应第 540 个字符 if unitchar == "1": unit = "feet" elif unitchar == "2": unit = "meters" else: unit = "unknown" f.close() print "Elevation in " + unitreadline()只读第一行,避免把整个文本文件读进内存。切片位置直接写数字是脆的,换一种数据源就失效,实务里的做法是把位置写成一个命名常量,再加一句断言检查行长度,读不到就报明确的错,而不是悄悄返回空字符。路径拆分则更常用:p.split("/")得到一个片段列表,p.find(".")得到扩展名前的点位置,os.path.splitext(p)一步到位返回名字和扩展名。
3.3 遍历目录:os.listdir 加条件筛选
批处理的入口基本都是“把目录里符合条件的文件都找出来”。标准写法是三层:os.listdir拿全部名字,for走一遍,if加筛选条件。
import os ws = r"C:\prog\hmbarea" all_names = os.listdir(ws) # 返回文件名列表,不含完整路径 shps = [] for n in all_names: if n.endswith(".shp"): # 只保留 shapefile shps.append(n) for s in shps: print sos.listdir返回的是名字,不是路径,后续要拼上ws。endswith区分大小写,遇到.SHP会漏掉,严谨一点写成n.lower().endswith(".shp")。这段代码在 9.x 的脚本窗口里可以直接跑,但如果你在工具箱工具的上下文里跑,注意当前工作目录不一定是脚本所在目录,所有相对路径都可能失效,所以目录从一开始就写成绝对路径。
3.4 参数入口:sys.argv 与 GetParameterAsText
脚本有两种被调用的方式,对应的参数入口不一样。从命令行或者脚本窗口带参数运行时,用sys.argv,第 0 位是脚本名,从第 1 位开始才是参数;把它做成工具箱里的工具运行时,用工具的GetParameterAsText,按参数在工具属性里排的顺序取,从 0 开始。
# 入口一:命令行 / 脚本窗口带参数运行 import sys lat = float(sys.argv[1]) # 输入是字符串,必须显式转浮点 decl = float(sys.argv[2]) sunangle = 90 - lat + decl print "sunangle=" + str(sunangle) # 入口二:作为脚本工具运行 import arcpy lat = float(arcpy.GetParameterAsText(0)) decl = float(arcpy.GetParameterAsText(1)) arcpy.AddMessage("sunangle=" + str(90 - lat + decl))sys.argv拿到的永远是字符串,不做类型转换,后面凡是参与算术都会报类型错误——这是参数脚本最常见的崩溃原因。GetParameterAsText同理,返回值也是字符串,即使工具属性里把参数类型设置成数值。另一件事是索引顺序:工具属性里的参数列表怎么排,代码里就怎么取,中间插一个参数而代码没改,取到的就是错的值,而且不会报错,只会跑出奇怪结果。
4. 从工具箱到数据:工具别名、列表方法、Describe 与游标
到这一章才是真正跟数据打交道。脚本要做的判断大多围绕三个问题:这个路径下有没有数据、这个数据是什么类型、这个数据里有什么字段和记录。对应的三套接口分别是exists、describe、List*与游标。
4.1 工具别名的必要性
同一个名字的工具在不同工具箱里都有。裁剪在矢量分析里有,在栅格处理里也有,参数含义完全不同。脚本里没有右键菜单可选,必须靠别名区分。命名规则是“工具名_别名”,别名固定在工具属性里。
| 别名 | 对应工具箱 | 典型工具 |
|---|---|---|
| analysis | 分析工具 | Buffer、Clip、Intersect |
| management | 数据管理工具 | CopyFeatures、Delete、Project |
| conversion | 转换工具 | FeatureClassToShapefile |
| arc | Coverage 工具 | Coverage 专用处理 |
| sa | 空间分析 | 栅格计算、表面分析 |
| cartography | 制图工具 | 制图综合、符号化 |
gp.workspace = r"C:\prog\hmbarea" gp.overwriteoutput = 1 # 全名写法:工具名 + 下划线 + 别名,最不容易出错 gp.CopyFeatures_management("streams/arc", "streams.shp") gp.Buffer_analysis("streams.shp", "stbuff200.shp", "200 Meters") gp.Clip_analysis("geol.shp", "stbuff200.shp", "geolstr200.shp") # 同一工具箱连续调用时,可以设一次工具箱省掉别名 gp.Toolbox = "analysis" gp.Buffer("streams.shp", "stbuff200.shp", "200 Meters") gp.Clip("geol.shp", "stbuff200.shp", "geolstr200.shp")两种写法效果一样,第二种省字但有个前提:中间不能切到别的工具箱,切了就得重设。团队协作里我更倾向全名写法,因为看代码的人不需要知道Toolbox那行在哪里设过。另外注意矢量裁剪和栅格裁剪是两个不同的工具,名字相近但别名不同,参数里一个接受要素类、一个接受栅格,混用报的错往往指向参数类型而不是工具选错,容易查偏。
4.2 List 系列与 Describe、Exists 的组合
ListFeatureClasses、ListFields、ListDatasets、ListRasters这一组方法用来“问数据有什么”。describe用来“问数据是什么”,返回对象的DataType属性是最有用的一项,能区分 shapefile、栅格数据集、地理数据库要素类等。exists用来“问数据在不在”,返回布尔值。
ListDatasets和ListFeatureClasses的区别值得单独说:同一个工作空间里,coverage 和 shapefile 的组织方式不同,遍历 coverage 要用ListDatasets,用错方法会返回空列表而不是报错。
gp.workspace = r"C:\prog\hmbarea" # 先问类型,再决定走哪条处理分支 dsc = gp.describe("landuse") print dsc.DataType # 例如 RasterDataset / Shapefile print dsc.ShapeType # 点、线、面,栅格则为空 # 拿字段清单:第二个参数是通配符,第三个参数控制字段类型 fields = gp.ListFields("landuse", "*", "all") f = fields.Next() while f: print f.Name, f.Type, f.Length f = f.Next() # 输出前先判断存在性,避免覆盖或者报错 out_fc = r"C:\prog\out\result.shp" if gp.exists(out_fc): gp.Delete_management(out_fc)describe的返回值是只读快照,取完就丢,别拿去改数据。DataType的判断是很多脚本的分支入口:同一个目录里既有栅格又有矢量,得先判断再分别调用不同的工具集。ListFields返回的字段对象带Name、Type、Length、Precision、Scale等属性,字段名在 shapefile 里会被截断到 10 个字符,写代码前先看一眼实际字段名,别照着设计文档写。
4.3 游标:读、改、写的三条通道
游标是脚本跟属性表打交道的唯一通道。SearchCursor只读,UpdateCursor可读可改,InsertCursor只写。三者的使用范式一致:创建、循环取值、处理、写回、释放。
# 读取并修改:把长度字段从英尺换算成米 rows = gp.UpdateCursor("roads.shp") row = rows.Next() while row: ft = row.getValue("LEN_FT") # 按字段名取值 row.setValue("LEN_M", ft * 0.3048) # 按字段名写值 rows.UpdateRow(row) # 提交这一行,漏掉这句等于没改 row = row.Next() del row, rows # 显式释放,否则文件可能被锁getValue和setValue的参数是字段名,字段不存在会直接抛错而不是返回空。UpdateRow必须调用,只setValue不提交,循环跑完数据一点没变,这类问题很难从结果倒查。del两行看着像仪式,其实是必须的:游标不释放,shapefile 会一直处于被占用状态,后面想删或者覆盖就会失败。释放顺序是先释放行对象再释放游标。字段名大小写不敏感,但拼写必须完全一致,LEN_FT和LENFT是两个字段。
5. 避坑与排查:五个把脚本跑挂的具体位置
这一章记录的是踩过的坑。每一条都按“现象—原因—解决”写,看到相似症状可以直接对号入座。
5.1 路径、编码与运行环境
现象:脚本在编辑器里跑得好好的,做成工具箱工具一运行就报编码错误,或者第一行注释里有中文就崩。
原因:老版本解释器默认按 ASCII 解析源文件,脚本里出现非 ASCII 字符又没有声明编码,解析就停在那一行。打包成工具后,运行环境的默认编码可能跟编辑器不一样,问题才暴露出来。
解决:每个脚本第一行或第二行加编码声明# -*- coding: utf-8 -*-,文件本身存成 UTF-8 无 BOM。数据路径尽量不用中文和空格,输出目录单独建一个纯英文路径。这不是迷信,是减少变量——报错时你至少能排除路径编码这一层。
现象:脚本里用print打了一堆调试信息,从工具箱运行后结果窗口一片空白。
原因:print写到标准输出,工具运行时标准输出没有接到结果窗口上。只有AddMessage、AddWarning、AddError写进的消息才会显示。
解决:调试阶段可以继续用print,交付前把关键节点换成AddMessage,异常分支用AddError——它会把工具标记为失败,而不是带着错误结果继续往下跑。
现象:换了一台机器,脚本第一行import arcgisscripting就失败,或者提示找不到模块。
原因:自带的解释器被手动升级过,或者用了一个系统里另装的解释器去跑脚本。地理处理模块是随桌面一起装的,跟版本强绑定。
解决:不要动随桌面安装的那个解释器。需要另外的环境时,用组件分发那条路,或者在脚本里显式指定解释器路径。这条属于血泪经验,升上去容易,退回来要重装。
5.2 列表对象与数据状态
现象:for fc in gp.ListFeatureClasses():报类型错误,提示对象不可迭代。
原因:老版本的 List 方法返回的是枚举器,不是列表。它只有Next()方法,没有迭代协议。
解决:用Next()循环,取到空值退出;或者先用循环把结果全部塞进一个普通列表再遍历。后者更稳,因为枚举器只能用一次,用完得重新拿。
现象:处理完的数据删不掉,提示被占用;或者明明setValue了,重新打开属性表数值没变。
原因:游标没释放。文件被游标持有,操作系统层面就是锁定状态。
解决:每个游标用完后del掉行对象和游标对象,最好放在try/finally里,异常退出也能释放。批量处理时,一个循环里创建几十个游标不释放,跑到后面必崩。
6. 脚本工具化与验证:让脚本被调用前先过三道检查
脚本自己跑通,跟“能被别人当工具用”,中间隔着一层参数契约。把脚本加进工具箱时,工具属性里的参数列表要和代码里的GetParameterAsText索引严格对齐,数据类型、方向(输入还是输出)、是否必填都要设。输出参数尤其容易被忽略:不设输出参数,脚本在 ModelBuilder 里就没法作为中间步骤串起来,下游拿不到结果。惯常做法是脚本最后用SetParameterAsText把输出路径回填,模型里就能自动接上派生数据。
import arcpy in_fc = arcpy.GetParameterAsText(0) # 输入要素,工具属性里排第 1 out_fc = arcpy.GetParameterAsText(1) # 输出要素,排第 2,方向设为输出 dist = arcpy.GetParameterAsText(2) # 缓冲距离,带单位 arcpy.Buffer_analysis(in_fc, out_fc, dist) arcpy.SetParameterAsText(1, out_fc) # 回填输出,供模型下游使用验证分三层,从便宜到贵。第一层,拿一条已知答案的小数据跑,比如缓冲 200 米之后量一下实际距离对不对;第二层,describe输出的DataType和范围(extent)跟源数据比对,确认没跑错数据;第三层,看消息窗口里的工具消息,每一步的输入输出路径是不是你预期的那个。这三层走完,比反复肉眼检查结果图层靠谱得多。
有个习惯我一直保留:脚本开头加一个dry_run开关,打开时只打印将要执行的工具和参数,不写任何文件。批处理脚本最怕跑到一半发现参数错了,几十个结果全废还得清目录,一个开关能省掉大量清理工作。从那以后我每次把脚本交给别人之前,都强制走一遍空跑加小样本这两个动作,宁可多花十分钟。希望帮到你。
本文还有配套的精品资源,点击获取