游标对象是什么
在 ArcPy 中检索、更新、修改矢量文件属性表是非常常见的操作。而 ArcPy 也提供了3个专门的方法来实现这些功能。
首先我们需要了解 ArcPy 中的数据访问模块:da(arcpy) 模块,英文:The data access module,是一个数据处理模块。
而就在该模块中有三种游标方法,使用这三种方法可以创建三种不同的游标对象(实例),我们进一步使用游标对象就可以获取、修改、插入属性表中的各种数据。
这三个方法首次是在 ArcGIS10.1 版本中加入:arcpy.da.SearchCursor
、arcpy.da.UpdateCursor
、arcpy.da.InsertCursor
。
需要注意的是,在 ArcGIS10.1 版本前大家使用的是三个旧的游标方法:arcpy.SearchCursor
、arcpy.UpdateCursor
、arcpy.InsertCursor
。
新版的游标比旧版的性能更好,但是为了兼容先前的代码,旧的方法没有被移除,只是不再推荐使用了。也就是说你可以在 ArcGIS10.1 之后的 ArcPy 中同时使用新版游标方法和旧版游标方法。
但是由于新版游标的性能更好,另外新旧游标对象各自的方法都有些许差异,所以没有必要去研究和使用旧版游标对象了。
游标对象是什么?那么现在回答这个问题:游标对象就是通过执行游标方法而获得的游标对象实例,使用游标对象可以获取属性表数据(包括几何属性)。
- 速度比较图表来源:https://gis.stackexchange.com/questions/108807/how-is-the-data-access-cursor-performance-so-enhanced-compared-to-previous-versi
Note:后文提及的游标及其方法均指新版游标和新版方法,请注意。 |
三种游标对象
三种游标对象的使用方法在官方文档里写的很清楚,所以我简单复制一下。除开官方文档外,我会重点讲一下注意事项和游标的本质及其应用。
下面是用于演示的矢量文件的初始属性表。
../NYC.gdb/Boroughs 要素的初始属性表
1.SearchCursor
搜索游标,当然你想叫什么样的中文名都可以,该游标是只读游标,只能查询属性表中信息,不能修改。
创建该游标的语法如下:
cursor = arcpy.da.SearchCursor (in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points},{sql_clause})
该方法前2个如 in_table
和 field_names
是必选参数,后面4个是可选参数(很少使用到)。
第一个参数输入要素类、图层、表或表视图;第二个参数输入字段名称或者令牌,虽然令牌这个说法可能看起来比较怪,其主要用于获取属性表中没有的信息,比如几何的质心、周长、面积等,使用起来也非常简单,和字段名称输入方式一样,加上引号当字符串传进去就好,具体有那些种类的令牌官方文档上写的很清楚。
1.1属性
fields:返回字段名称参数(几乎根本不会使用)。
1.2方法
next ():将下一行作为元组返回。字段将按照创建光标时所指定的顺序返回。
reset ():将光标重置回第一行。
1.3示例代码
示例代码可见文件 ../Chapter5/1.SearchCursor.py:
# -*- coding:utf-8 -*-
import arcpy
import os
arcpy.env.overwriteOutput = True
wk_path = os.path.abspath("../NYC.gdb")
arcpy.env.workspace = wk_path
field = ["BoroName", "SHAPE@AREA"] # ▶注释1◀
with arcpy.da.SearchCursor("Boroughs", field) as cursor:
print cursor.fields
print "Next 1: {}".format(cursor.next()) # ▶注释2◀
print "Next 2: {}".format(cursor.next())
print "Next 3: {}".format(cursor.next())
print "Next 4: {}".format(cursor.next())
print "Next 5: {}".format(cursor.next())
cursor.reset() # ▶注释3◀
print "Next 1: {}".format(cursor.next())
print "Next 2: {}".format(cursor.next())
print "Next 3: {}".format(cursor.next())
print "Next 4: {}".format(cursor.next())
print "Next 5: {}".format(next(cursor)) # ▶注释4◀
输出结果:
(u'BoroName', u'SHAPE@AREA') Next 1: (u'Staten Island', 1601777454.0332422) Next 2: (u'Brooklyn', 1956932802.231596) Next 3: (u'Queens', 3030316015.6541715) Next 4: (u'Bronx', 1171019852.6289911) Next 5: (u'Manhattan', 630673101.7271129) Next 1: (u'Staten Island', 1601777454.0332422) Next 2: (u'Brooklyn', 1956932802.231596) Next 3: (u'Queens', 3030316015.6541715) Next 4: (u'Bronx', 1171019852.6289911) Next 5: (u'Manhattan', 630673101.7271129)
▶注释1◀:使用了令牌 SHAPE@AREA,该令牌可以获取每个要素的面积。
▶注释2◀:使用游标内置的 next() 方法可以依次遍历属性表内容。
▶注释3◀:reset() 重置游标位置,如果再继续使用 next() 方法的话就会报错,因为从上面可以看到Boroughs 属性表中只有5行数据,现在游标已经到了属性表末尾。
▶注释4◀:这里使用了 next(cursor),效果和 cursor.next() 一样,next(obj) 是 Python 的内置方法。具体详情可见下面的游标的本质小结。
- 官方在线文档:https://desktop.arcgis.com/zh-cn/arcmap/latest/analyze/arcpy-data-access/searchcursor-class.htm
2.UpdateCursor
更新游标:该游标是我使用最为频繁的游标,它不仅能遍历属性表,替代搜索(SearchCursor)游标,还能够更新属性表内容。
创建该游标的语法如下:
cursor = arcpy.da.UpdateCursor (in_table, field_names, {where_clause}, {spatial_reference}, {explode_to_points}, {sql_clause})
该游标方法的参数和搜索游标的方法一致,前2个如 in_table
和 field_names
是必选参数,后面4个是可选参数(很少使用到)。
2.1属性
fields:返回字段名称参数(同样几乎不会使用)。
2.2方法
next ():将下一行作为元组返回。字段将按照创建光标时所指定的顺序返回。
reset ():将光标重置回第一行。
除开这两个搜索游标都有的方法外,更新游标有两个自带的独特方法:
deleteRow ():删除当前行。
updateRow (row):更新表中的当前行
2.3示例代码
示例代码可见文件 ../Chapter5/2.UpdateCursor.py:
# -*- coding:utf-8 -*-
import arcpy
import os
arcpy.env.overwriteOutput = True
wk_path = os.path.abspath("../NYC.gdb")
arcpy.env.workspace = wk_path
field = ["BoroName", "SHAPE@AREA"]
with arcpy.da.UpdateCursor("Boroughs", field) as cursor:
for row in cursor:
row[0] = row[0] + "_1" # ▶注释1◀
row[0] = row0
cursor.updateRow(row) # ▶注释2◀
输出结果:
- 官方在线文档:https://desktop.arcgis.com/zh-cn/arcmap/latest/analyze/arcpy-data-access/updatecursor-class.htm
3.InsertCursor
插入游标:该游标与以上两个游标差别较大。使用该游标来添加新行。
创建该游标的语法如下:
cursor = arcpy.da.InsertCursor (in_table, field_names)
参数只有两个必选参数。
3.1属性
fields:返回字段名称参数(同样几乎不会使用)。
3.2方法
insertRow (row):向表中插入一行。
3.3示例代码
示例代码可见文件 ../Chapter5/3.InsertCursor.py:
# -*- coding:utf-8 -*-
import arcpy
import os
arcpy.env.overwriteOutput = True
wk_path = os.path.abspath("../NYC.gdb")
arcpy.env.workspace = wk_path
field = ["BoroName", "SHAPE@"]
cursor = arcpy.da.InsertCursor("Boroughs", field)
array = arcpy.Array([arcpy.Point(993701.189, 232208.219),
arcpy.Point(976940.744, 245402.664),
arcpy.Point(965482.411, 211027.664),
arcpy.Point(974162.966, 191583.219)])
new_polygone = arcpy.Polygon(array) # ▶注释1◀
cursor.insertRow(["test", new_polygone]) # ▶注释2◀
cursor.insertRow(["test2", None]) # ▶注释3◀
del cursor
▶注释1◀:创建了一个几何对象,面 new_polygone;
▶注释2◀:使用 insertRow 方法新添加了一行,同时附带了几何对象,输出的结果如下:
新增加了一个几何(高亮部分)
▶注释3◀:不用几何对象也能添加新的一行,不过这显然就违背了矢量数据的基本要求,即每条属性对应一个几何形状(对象)。
如下图所示,黄色框中的数据行是 ▶注释2◀ 中新增几何形状对应的属性;而紫色框中的数据行则是纯粹的属性,没有任何对应的几何形状。
当然不建议新增没有对应几何的数据行,没有意义,这里只是用于演示。
运行示例代码后,属性表新增了两行
- 官方在线文档:https://desktop.arcgis.com/zh-cn/arcmap/latest/analyze/arcpy-data-access/insertcursor-class.htm
3.游标的本质
3.1本质
好的,众所周知的三种游标终于说完了,现在说说很多人不知道的东西,也是这篇文章的精华所在:游标的本质。
游标的本质是什么?语言是苍白的,直接上代码:
示例代码可见文件../Chapter5/4.cursor.py:
# -*- coding:utf-8 -*-
import arcpy
import os
from collections import Iterable, Iterator, Sequence
arcpy.env.overwriteOutput = True
wk_path = os.path.abspath("../NYC.gdb")
arcpy.env.workspace = wk_path
l = [1, 2, 3, 4]
cursor = arcpy.da.SearchCursor("Boroughs", ["OID@", "SHAPE@"])
abcs = (Iterable, Iterator, Sequence)
print [isinstance(l, abc) for abc in abcs]
[True, False, True] #Iterable, not Iterator, Sequence
print [isinstance(cursor, abc) for abc in abcs] # ▶注释1◀
[True, True, False] #Iterable, Iterator, not Sequence
使用内置函数 iter() # ▶注释2◀
it_l = iter(l)
t1 = type(l) # <type 'list'>
t2 = type(it_l) # <type 'listiterator'>
it_cur = iter(cursor)
t3 = type(cursor) # <type 'da.SearchCursor'>
t4 = type(it_cur) # <type 'da.SearchCursor'>
look at identify of cursor objects
print id(cursor) # 105692320
print id(it_cur) # 105692320
从 ▶注释1◀ 可以看到游标是可迭代对象、也是迭代器、不是序列。
包括后面的 ▶注释2◀ 也说明了游标是一种迭代器:迭代是数据处理的基石,而按照惰性获取数据项的方式,即按需一次获取一个数据项,这叫迭代器模式(Iterator pattern),而迭代器就负责实现这一模式功能。
所以游标拥有迭代器的各种特性,在这个基础之上,我们可以最大化的利用 Python 的优势,写出优美简洁的代码。
3.2本质的应用
灵活运用迭代器能省下大量的功夫,这里使用一些实例来说明。以下所有示例代码见文件 ../Chapter5/5.sample.py。
拆包
元组拆包就是迭代器支持的功能之一。可以简化代码,见下面示例代码的 ▶注释1◀ 。
# ... field = ["BoroName", "SHAPE@AREA"] with arcpy.da.SearchCursor("Boroughs", field) as cursor: for name, area in cursor: # ▶注释1◀ print name
惰性释放
迭代器是惰性:迭代器不会一次性把所有数据拿到“手上”,而是根据需要,一个一个的拿数据。但是如果我们想要一次性获取所有数据呢?难道要老实的遍历一次,把每一次的数据都装进一个列表?
当然不必这样,使用 python 内置函数 list(),可以直接释放惰性。
# 惰性释放
field = ["BoroName", "SHAPE@AREA"]
<<<传统写法>>> ▶注释1◀
new_list = []
with arcpy.da.SearchCursor("Boroughs", field) as cursor:
for name, area in cursor:
new_list.append((name, area))
将全部数据装进一个列表
print new_list
del cursor
<<<<惰性释放写法>>> ▶注释2◀
cursor = arcpy.da.SearchCursor("Boroughs", field)
new_list2 = list(cursor)
print new_list2
del cursor
▶注释1◀ 这里这是普通的写法,需要先创建一个容器(列表),然后一个一个把内容装进去。而 ▶注释2◀ 位置就是直接使用内置的列表构造函数 list(),直接去除惰性,因为一个列表是完全没有惰性的。
当然特定情况下可能需要去除惰性,比如结合 len() 函数快速掌握这个矢量数据有多少个几何形状(属性)。
获取极值
如何获取某一字段的最大值或者最小值是多少呢?一条属性一条属性的遍历然后比较大小嘛?还是使用 ArcGIS 自带的表达式?
其实都不用,一行代码解决问题,
# 获取极值
from operator import itemgetter
field = ["BoroName", "SHAPE@AREA"]
with arcpy.da.SearchCursor("Boroughs", field) as cursor:
print max(cursor, key=itemgetter(1))
输出结果如下,面积最大的是“皇后区”:
(u'Queens', 3030316015.6541715)
降序/升序排列
通过一个字段值的大小进行降序/升序排列。如果不结合迭代器自身的特性你要怎样实现呢?
但是其实也是只需一两行代码就能轻松实现。
from operator import itemgetter field = ["BoroName", "SHAPE@AREA"] with arcpy.da.SearchCursor("Boroughs", field) as cursor: for name, area in sorted(cursor, key=itemgetter(1), reverse=True): print (name, area)
输出结果:
(u'Queens', 3030316015.6541715) (u'Brooklyn', 1956932802.231596) (u'Staten Island', 1601777454.0332422) (u'Bronx', 1171019852.6289911) (u'Manhattan', 630673101.7271129)
最后
为什么 Python 这么流行的原因就是简单、优美,是 Pythonic。
在这里我们不局限于 ArcPy 这一个站点包,视野开阔到整个 Python 语言,在了解了游标的本质后我们能轻车熟路的使用很多 Python 内置的函数或者默认库写出优美、简洁的代码,不仅仅局限于上面简单提到的几个案例。
结束语
看完本章节,你可以知道:
- 三种游标及其对应创建方法;
- 三种游标的使用方法和区别;
- 游标的本质是迭代器;
- 结合迭代器的特性写出优美的代码;
- 希望你跳出 ArcPy 的局限,将视野放大到 Python 上。
使用版本:
- Windows 10
- PyCharm 2020.3.3
- ArcGIS 10.3
- Python 2.7.8