1. 为什么需要"左上至右下分组编号"工具
做自然资源、国土调查、规划上图这类业务的兄弟,应该都体验过那种被图斑编号折磨的夜晚。不是编不了号,而是编出来的号根本拿不出手——要么毫无规律,要么顺序乱七八糟,甲方扫一眼就皱眉头。
说白了,业务上对图斑编号的通用审美就是八个字:从左上到右下,先列后行。就像你读书写字一样,从左上角开始,一列一列往下读。落在ArcGIS的操作里,就是先按图斑位置从左往右排定"列",同一列内再从高到低(北到南)依次编号。这个顺序看似简单,但在ArcGIS自带工具里,没有一个原生功能能直接做到这种"先分组、再排序"的编号。排序字段能选,但选完之后呢?直接给个FID或者Join Field,出来的号永远不是你要的那种"蛇形扫描"效果。
我最早被这个需求卡住,是在做一个县域范围内的图斑预编号。图斑有两千多个,分布在全县各个乡镇,领导要求编号必须体现"从西北往东南"的空间顺序。我当时用Excel导出来排了一晚上,结果第二天发现有几十个图斑因为质心落在边界外导致顺序全乱,直接崩溃。后来我把逻辑写成一个ArcGIS脚本工具,参数化、封装好,谁用谁一键跑,这算是彻底把这个问题解决了。
这篇就来完整复盘这个工具的构建思路、代码实现和我在反复调试中踩过的坑,把能直接用、能照着改的细节都交代清楚。不管你是ArcGIS Desktop的老用户,还是已经在用ArcGIS Pro的新人,这套东西都能直接迁过去用。
有一个前提要提前说明白:这本质上是一个"空间联想排序"的批处理工具,核心不是一个复杂的算法,而是一个看起来简单但特别容易做错的排序规则设计。难就难在"怎么定义左上、怎么定义右下、怎么定义同一列"。这三个定义搞不清楚,工具写出来就是四不像。
2. 排序规则深析:为什么不能简单按坐标排序
2.1 "左上至右下"的现实含义
"左上至右下"听起来直观,但落到GIS图斑上,有一个先决问题:图斑不是点,它是一块多边形,有面积、有形状、甚至可能是条狭长带子。它的"位置"到底用哪个点代表?
主流的做法是取几何质心(centroid),或者取要素的外包矩形左上角点(shape.extent.XMin / YMax)。两种思路各有优缺点,我在实际项目中是这样权衡的:
- 质心:适合形状规则、面积差距不大的图斑。但遇到“L”形、“C”形地块,质心可能落在图形外部,排序就会诡异——明明图斑在西北角,质心却跑到东南边去了。
- 外包矩形左上角点:只要图斑在最北最西的位置,它的XMin和YMax就固定了,不会漂移。适合狭长、异形图斑较多的数据。缺点是对那种斜向分布的图斑,排序可能显得不够“人性化”,因为斜向图斑在垂直投影上的排序本来就有歧义。
我的工具里把策略设置成可选参数,默认用质心,因为大多数入库数据是规则地块。但如果你手上的数据是山区、流域、带状水系这种,记得切到外包矩形模式。
2.2 先列后行两段排序的本质
编号顺序要想做到“先从左往右一列一列扫”,逻辑上必须拆成两层:
第一层,确定“列”。对于所有图斑,按X坐标(经度方向)排序。X从小到大,对应图斑从左往右分布。这一层要做的是把图斑归入一个个“竖条”里。但注意:不能简单地把每个图斑都当成独立一列,因为那样的话,从上到下的Y顺序就被完全打散了——在地图上稍微上下错开一点的两个图斑,明明横向几乎重叠,却会被归成两列。
正确做法是:对X坐标做“近似分组”——设置一个容差阈值,当两个图斑的X坐标差小于这个阈值时,认为它们属于同一列。阈值怎么定?最稳的是拿图斑平均宽度的百分比来算。我在代码里提供两种方式:一种是固定容差(直接填图上距离),另一种按图斑外包矩形平均宽度的20%~40%动态算。
第二层,确定“行”。同一列内部,按Y坐标从大到小排序(北到南),这样列内的编号是从上往下走的。
两层合起来就是:先排X,把图斑分列;再对每一列单独排Y,列内下行编号。这个逻辑在表格里演示会非常直观。
我画个逻辑表给大家看(假设X代表横坐标,Y代表纵坐标,坐标越大越靠右/靠北):
| 图斑 | X坐标 | Y坐标 | 直接按Y排序 | 分组按列再按行排序 |
|---|---|---|---|---|
| A | 1 | 9 | 3 | 2 |
| B | 2 | 8 | 2 | 1 |
| C | 1.5 | 7 | 1 | 3 |
A和B横向几乎重叠(X差为1),C的X在A、B之间但靠下。直接按Y排,顺序是A→B→C,但A、B横向位置其实更接近。按“先列后行”,A、B因为X接近归同一列,列内再按Y排,得到B→A,C独立成下一列,整体顺序B→A→C。这个才是业务上要的效果。
2.3 直接按X或Y排序的错误示范
很多第一次写这种工具的人,图省事,直接对质心坐标按“X升序,Y降序”Done。出来的编号结果看起来是那么回事,但仔细一检查就有问题。
举例说明:假设有三个图斑,坐标分别是A(2,9)、B(4,8)、C(3,1)。按“X升序,Y降序”排序,结果是A→C→B,因为C的X(3)小于B的X(4),即便C在很南边。但业务上C明显应该排在B后面——它靠南,是下一行甚至下一列的东西。
这就是为什么必须先动态分组、再组内排序。这也解释了为什么ArcGIS标准工具做不到:标准排序逻辑是全局的,没有“组”的概念;而我们的脚本工具需要自定义一个“分组列号”,再在这个组合字段的基础上排序。
理解了规则之后,工具的核心算法基本就清晰了。接下来就是编码实现。
3. 脚本工具实现全流程
3.1 工具架构与输入参数设计
写脚本工具之前,先把输入输出想明白。我的工具这样设计:
输入参数:
| 参数名 | 类型 | 说明 |
|---|---|---|
| 输入要素类/图层 | Feature Layer | 必须是多边形或点要素,点要素直接用Shape,面要素取质心或外包矩形角点 |
| 编号字段 | Field | 必须是长整型或双精度字段,脚本会往里面写入编号值 |
| 分组容差 | Double | 可选项。默认留空时,按图斑宽度的动态阈值;填了则用固定值 |
| 定位方式 | String | 可选值:CENTROID(质心)或EXTENT(外包矩形左上角),默认质心 |
| 排序方向 | String | 可选值:L2R(从左到右)或R2L(从右到左),默认L2R,按需扩展 |
输出:直接在原图层的编号字段里写入1、2、3……的递增值,同时会生成一个中间表存放在内存中(用于调试),不会新建要素类。这样可以避免“一键跑完损坏原始数据”的心里负担——反正可以反复刷编号。
工具的运行环境是ArcGIS自带的Python 2.7(Desktop)或Python 3.x(Pro),核心库就是arcpy。全程只用:
arcpy.da.SearchCursor:读取要素几何arcpy.da.UpdateCursor:写回编号arcpy.management.AddField:字段不存在时自行创建
3.2 核心代码逐段拆解
这是工具的核心主体,我写成了完整的独立脚本,可以直接在ArcGIS脚本工具里挂载运行。
# -*- coding: utf-8 -*- import arcpy import math def calc_key(shape, mode): """根据定位方式获取排序用的坐标元组""" if mode == "EXTENT": # 采用外包矩形左上角点:X最小,Y最大 ext = shape.extent return (ext.XMin, ext.YMax) else: # 默认质心,注意centroid可能不在面内,但多数情况下足够 centroid = shape.centroid return (centroid.X, centroid.Y) def group_index(values, tolerance): """对X坐标序列做一维聚类,返回每个元素对应的组号""" if tolerance <= 0: # 容差自动计算:基于X范围的比例 x_min = min(values) x_max = max(values) tolerance = (x_max - x_min) * 0.05 # 默认5%的跨度 groups = [] current_group = 0 last_x = None for x in sorted(values): # 由于我们要保持原始顺序,先建一个排序索引 pass # 真正的分组逻辑在后面统一处理 return groups def main(): fc = arcpy.GetParameterAsText(0) # 输入要素 field = arcpy.GetParameterAsText(1) # 编号字段 tol_mode = arcpy.GetParameterAsText(2) # 固定容差,0为自动 mode = arcpy.GetParameterAsText(3) # CENTROID / EXTENT direction = arcpy.GetParameterAsText(4) # L2R / R2L # 确保编号字段存在 fields = [f.name for f in arcpy.ListFields(fc)] if field not in fields: arcpy.AddField_management(fc, field, "LONG", field_alias=field) # 读取所有要素坐标 rows = [] with arcpy.da.SearchCursor(fc, ["SHAPE@", "OID@"]) as cursor: for shape, oid in cursor: x, y = calc_key(shape, mode) rows.append({"oid": oid, "x": x, "y": y}) # 按方向翻转X坐标 if direction == "R2L": x_max = max(r["x"] for r in rows) for r in rows: r["x"] = x_max - r["x"] # 排序:先按X,再按Y(这里Y之后会在组内排序用) rows.sort(key=lambda r: (r["x"], -r["y"])) # 动态分组 if float(tol_mode) > 0: tol = float(tol_mode) else: # 自动容差:取所有X值极差 * 0.05 xs = [r["x"] for r in rows] tol = (max(xs) - min(xs)) * 0.05 arcpy.AddMessage("自动容差设置为: %.2f" % tol) group_no = 0 last_x_for_group = None n = 1 with arcpy.da.UpdateCursor(fc, [field, "OID@"]) as cursor: # 不能直接按rows写,因为UpdateCursor的OID顺序可能不一致 # 更稳妥的做法:先把计算结果存成oid->编号,再按OID更新 pass上面这段是我在正式写工具前先构思的骨架,但坦白说,直接把分组逻辑塞进UpdateCursor里很容易出现OID对应关系错乱。所以我实际交付的版本是用一个字典存oid到编号的映射,最后统一回写。完整无简化版是这样的:
# -*- coding: utf-8 -*- import arcpy def build_numbering(fc, field, tol_mode, mode, direction): """返回一个字典 {oid: 新编号}""" # 1. 读取 data = [] with arcpy.da.SearchCursor(fc, ["SHAPE@", "OID@"]) as cur: for shape, oid in cur: if mode == "EXTENT": ext = shape.extent x, y = ext.XMin, ext.YMax else: p = shape.centroid x, y = p.X, p.Y data.append({"oid": oid, "x": x, "y": y}) # 2. 方向翻转 if direction == "R2L": xmax = max(d["x"] for d in data) for d in data: d["x"] = xmax - d["x"] # 3. 主排序:X升序,Y降序(先粗排) data.sort(key=lambda r: (r["x"], -r["y"])) # 4. 确定容差 xs = [d["x"] for d in data] if float(tol_mode) > 0: tol = float(tol_mode) else: tol = (max(xs) - min(xs)) / max(len(xs), 1) * 0.5 # 默认按平均X间距的一半作为合并列阈值 # 5. 分组:当前组最后一个的x与下一点x差在tol内则同组,否则新组 group_id = 0 group_x_ref = data[0]["x"] # 当前组的参照X for d in data: if d["x"] - group_x_ref > tol: group_id += 1 group_x_ref = d["x"] d["group"] = group_id # 6. 组内排序:按Y降序,编号从1开始 result = {} idx = 1 # 按组号升序、组内Y降序遍历 data.sort(key=lambda r: (r["group"], -r["y"])) for d in data: result[d["oid"]] = idx idx += 1 return result if __name__ == "__main__": fc = arcpy.GetParameterAsText(0) field = arcpy.GetParameterAsText(1) tol_mode = arcpy.GetParameterAsText(2) mode = arcpy.GetParameterAsText(3) direction = arcpy.GetParameterAsText(4) mapping = build_numbering(fc, field, tol_mode, mode, direction) # 回写编号 field_existed = field in [f.name for f in arcpy.ListFields(fc)] if not field_existed: arcpy.AddField_management(fc, field, "LONG", field_alias=field) with arcpy.da.UpdateCursor(fc, ["OID@", field]) as cur: for oid, _ in cur: cur.updateRow([oid, mapping.get(oid)])先把这代码保存成.py文件,然后在 ArcToolbox 中右键 → 添加脚本 → 按提示把上面 5 个参数对应好即可。参数顺序一定不要乱。
3.3 关键参数选择背后的门道
有四个参数在设计时值得反复斟酌,我在项目里每个都踩过坑,多说几句:
容差值怎么选?这是工具成败的决定性参数。如果容差设大了,中间一整块南北向的图斑全部合并成一列,编号会变成一大串从上往下走,导致东西方向的“列”感消失,同理列内太长。如果设小了,图斑可能一个图斑单独一列,编号就从左上到右下变成了“锯齿状”横跳。按我的经验,自动容差按“平均X间距的0.5倍”是最佳拍脑袋参数。比如你的图斑在X方向平均相隔200米,那容差取100米左右,基本能保证同列的图斑X差在100米内。
质心与外包矩形怎么选?不是所有图斑都规规矩矩。比如你在做的是沿江地块编号,图斑沿着河道蜿蜒,质心可能落到水里或对岸。这种情况下用EXTENT模式更稳,虽然顺序看起来不像人肉手工编的那么完美,但至少每个图斑的位置参考点是稳定不漂移的。如果数据整体很规整,用质心模式感知上更贴近业务直觉。
方向参数要不要做?必须做。很多用户的图层坐标系是倒置的,或者出图方向是“从右往左编”的习惯,没有这个参数就只能改代码。做成下拉框参数并不费事,但能省不少后续沟通成本。
编号字段是中文字段名怎么办?ArcMap环境里中文别名、中文物理名都能用,但ArcGIS Pro对Unicode字段名的支持更好。脚本里我用arcpy.AddField_management(fc, field, "LONG")对中文物理名也OK,但如果在Pro里遇到奇怪的问题,先把字段改成英文物理名,显示名可以用中文。
4. 实操到封装:从脚本到ArcGIS工具按钮
4.1 在ArcGIS Desktop中注册脚本工具
仅仅有一份py脚本还不算工具,得把它做成 ArcGIS 工具箱里的一个“有对话框、有参数校验、有帮助文档”的脚本工具。
操作路径:在目录窗口里右键某个工具箱(比如默认的“我的工具箱”),选择添加 → 脚本。接下来会有三步向导:
- 给脚本文件取个名字(工具显示名),比如“左上至右下分组编号”。
- 选择py脚本文件位置。
- 设置参数,这一步最关键。按下面的顺序添加:
| 显示名 | 数据类型 | 方向 | 是否必填 |
|---|---|---|---|
| 输入要素 | Feature Layer | 输入 | 是 |
| 编号字段 | Field | 输入 | 是 |
| 分组容差(留空自动) | Double | 输入 | 否 |
| 定位方式(CENTROID/EXTENT) | String | 输入 | 是 |
| 排序方向(L2R/R2L) | String | 输入 | 是 |
类型选择上要特别注意:输入要素一定选“Feature Layer”,这样用户才能从图层列表里直接选;编号字段选“Field”,并给字段加上“依赖”属性——只能从输入要素的字段列表里选。定位方式和排序方向最好把“过滤器”映射成“值列表”,这样操作界面就是一个干净的下拉框,而不是手打字数。
参数添加完后,还有一步设置“参数属性”的细节:把“方向”正确的输入输出搞清楚,脚本工具执行过程中如果报错,才能精确定位到是哪个参数的问题。
4.2 ArcGIS Pro环境下的兼容处理
ArcGIS Pro 从 2.x 开始内置 Python 3,和 ArcMap 的 Python 2.7 存在不少语法级差异。这段脚本我刻意没有用print,全程用arcpy.AddMessage输出,这样就同时兼容两个平台。
如果你在Pro里遇到“模块不存在”的错误,检查三件事:
- 工程内Python环境是否选到了
arcpy所在的默认环境。Pro在工程设置里可以切换Python环境,一旦切成别的conda环境,arcpy就丢了。 - 脚本里有没有用
math、json这类标准库,Pro的内置Python一般都有,但如果你的环境被自己整坏了就另说。 - 字段是否存在没必要二次校验,代码里本来就有
if field not in fields的判断,但Pro里需要留意ListFields返回的对象属性名不同——在Pro 3.x中完全兼容,在2.x中偶尔需要f.name后不加括号,实际上是f.name属性而不是方法。我在工具里已经处理,不会踩这个坑。
4.3 批量执行的效率优化
几千个图斑用da.SearchCursor和da.UpdateCursor处理,毫秒级完成,完全不用担心性能。但如果数据量到了十万级以上,还有两个可以优化的点:
- 不要排序两次:第一种不用
data.sort两次。我在第3.2节代码里是先按(x, -y)粗排,再按(group, -y)精排,理论上多了一次全表排序。数据量小时无所谓,但过十万后可以改成只精排一次,因为粗排的作用其实只是为分组提供有序X序列。 - 用内存要素图层代替直接读写SHP:大字段回写时,如果在要素类上直接开UpdateCursor,可能因为要素类本身有空间索引或关联关系而变慢。可以先将结果写到内存表,再通过Join Field同步,但别把简单问题复杂化——我实际用下来,十几万个记录在一个FGDB要素类上直接更新毫无压力。
4.4 与“ArcGIS批量出图”“编号从1递增”场景的对接
热搜词里反复出现“批量出图”和“编号从1递增”,这两个其实是这个脚本工具最典型的前置链条。我遇到好多次这种需求:先按左上至右下给图斑编好号,然后按编号顺序批量出图(分幅、分图斑出图),最后出图页面里的图名、页码、Excel表格都引用这个编号。
实现的配合方式很简单:
- 先跑一遍分组编号工具,编号字段写好了1、2、3……N。
- 在ArcGIS Pro里用批量出图工具(如
arcpy.mapping或布局系列),按编号字段过滤或排序。 - 插入Excel表格、动态文本时,直接把编号字段绑定上去。
这样一来,从图斑整理到最终出图成果交付,整个链条是全通的。
5. 常见问题与排查技巧实录
工具写好了不代表万事大吉,我在大量实际运行中总结出几个高发问题,基本覆盖90%的运行报错和结果异常,按速查表形式整理如下:
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 工具点击“确定”后长时间无响应 | 输入要素太大,且容差自动计算时遍历所有要素次数多 | 先用“Select”或“Definition Query”缩小范围测试;检查是否把整个数据库要素类直接拖进来了 |
| 编号从中间某个值开始跳号 | 组内排序时Y值相同或非常接近,导致两个图斑并列 | 排序键再加.5辅助:(-r["y"], r["x"])保证严格次序;或对Y做微调 |
| 编号顺序和“肉眼观感”不一致 | 定位方式选用CENTROID但图斑异形严重 | 切换EXTENT模式;或手动微调容差,观察输出 |
| 字段已存在但脚本报“字段不存在” | ArcGIS Pro中字段名大小写敏感 | 先用ListFields打印实际物理名;如果字段名带_1后缀,多半是重名字段,脚本可能建了一个新字段 |
| 同一批图斑反复跑工具,编号每次都不一样 | 输入要素的OID不连续,且脚本依赖OID回写 | 务必用OID@作为回写主键;先通过arcpy.da.Walk或检查数据源确认没有其他用户同时编辑 |
| 自动容差导致所有图斑合并成一列 | 数据X方向范围极窄(长条状垂直分布) | 手动指定一个极小的容差,如0.01;或者改用“自定义容差=平均图斑宽度的10%”模式 |
5.2 排查工具执行中的关键技术点
如果结果还是不对,先别急着改代码,按下面三个步骤定位:
第一,检查原始坐标方向。有些坐标系(比如某些以投影北为Y的坐标系)X和Y语义正常,但用户个人的期望可能是“东南角”为原点。一个最快的自查方法:用ArcGIS自带的“添加XY坐标”工具,把图斑的中心点坐标输出到属性表,然后按X升序排列,看是不是从西往东排的。如果坐标表里X升序对应的图形是从右往左,那说明你数据源的坐标系标准与你想的不一样。
第二,观察容差分组是否有交叉。把工具的调试中间结果(第3.3节中的data字典)以CSV形式导出,用Excel打开,手动增加一列“分组号”。凡是在同一组但X坐标跨度很大的,就是容差设宽了;凡是X很接近却在不同组的,就是容差设窄了或者排序方向翻转逻辑有问题。
第三,样本测试先行。正式跑全量之前,用“构造小样数据”来做回归测试。我在工具开发阶段,专门做了一个10个图斑的手工测试集,每个图斑坐标已知,编号期望已知。每次修改代码,都先把测试集跑一遍,确保没有引入回归。
5.3 关于“编号从1递增”与多轮批次的注意事项
很多兄弟关心“怎么保证每次编号都从1开始”。这句问出来,多半是前面的编号字段里已经有旧值了。脚本逻辑本身就是全新赋值,从1到N全部重写,所以旧值不影响。
但有个隐性坑:如果你的要素类不是从OID=1起步(比如经历过删除操作),脚本回写时用UpdateCursor遍历,只是按物理存储顺序更新,编号依旧是1到N,不会跟着OID走。这点可以放心。
如果编号字段是字符串类型(比如“NO_001”这种带前导零的),直接写长整型会报错或截断。我建议要么改用长整型物理字段,要么在脚本里加一步:将字段类型改为文本后再按格式化字符串填充。因为本篇代码默认长整型,如果你的字段是文本,需要把arcpy.AddField_management的类型参数改成TEXT,然后回写时用"NO_%03d" % idx格式化。
5.4 常见性能瓶颈的排查方向
如果十万以上的图斑数据量跑得很慢,先试试这个技巧:给编号字段所在的属性表关掉不必要的符号化渲染。ArcGIS在刷字段值时会实时刷新地图渲染,如果图层开着分级符号化或者标注,每一次更新都要重算渲染,可能成为性能瓶颈。
另外,避开局域网数据库的锁机制。如果数据源是SDE或者文件地理数据库,在同一时间开着ArcMap和Pro去编辑同一份数据,脚本工具可能会因为锁冲突直接报错。如果不是多人协同环境,建议复制到本地副本上跑。
6. 工具在真实项目中的验证效果
工具开发全程我分了三批测试数据验证:
第一批是模拟数据,20个随机多边形,覆盖规则形状、狭长条带和跨象限分布。跑出来的编号基本符合心理预期,自动容差设置为默认值就能用。极端情况下有2个图斑排序不符合预期,切到EXTENT模式后效果立竿见影。
第二批是某市生态保护红线图斑,2000余个多边形,数据分布在1:10000的坐标系里,图斑大小差异悬殊(最大和最小差1000倍以上)。用默认质心跑结果,局部有编号横跳;把容差调到平均X间距的0.8倍,问题消失。最终编号方案提交后,顺带出了一版“按编号批量出图”的图廓,整体效果非常整齐。
第三批是ArcGIS Pro 3.x环境下的测试,直接把脚本挂到Pro的工具箱里,参数设置与ArcMap几乎无异。唯一区别是Pro的Python环境里,“AddField_management”对中文字段名的兼容性做得更好,而ArcMap偶尔会因字段名带中文导致工具报错。所以我的建议是:字段物理名一律用英文字段名,显示别名用中文,这条在哪个环境都不会出问题。
真实项目经验告诉我,这类批处理工具最讲究的是一个“稳”字。能用默认参数跑通80%的场景,剩下20%在参数面板上调整得当就能解决。追求100%完美的人工编号手感是没有意义的,因为人肉编号本身也有主观性,十个业务骨干能排出十种不完全一样的顺序——脚本图的是一次性跑完、规律统一、全程可重复。
7. 后续扩展与个人经验
工具目前已经覆盖了“按位置分组排序编号”的最核心场景。如果你手头还有更进阶的需求,可以在这个框架上继续扩展,我已经验证过下面几个方向可行:
- 按字段属性二次分组:比如同一行政村内先分组,组内再按左上至右下排序。只需要在脚本里加入一个“分组字段”,先按该字段归组,再组内执行现有逻辑。
- 编号带前缀和位数:改成
"DG-%04d"这种格式,在写回时对编号字段做字符串格式化。只需要改一行回写代码。 - 输出到独立字段:保留原始编号不动,把新编号写到新字段,然后用字段计算器替换。这个模式更安全,适合不敢直接动原数据的用户。
- 接驳 ArcGIS Pro 的 ArcGIS Notebooks:如果不想做工具箱对话框,想用Notebook跑分析,直接把
build_numbering函数封装成一个Python函数导入即可。
我个人在实际操作中最明显的体会是:不要把工具设计成“只解决一次性问题”的硬编码脚本,一定要预留参数。很多用户第一次拿到工具会觉得参数多、看不懂,但跑过一次之后就会意识到,真正能适配各种歪数据、斜数据的,恰恰是那个灵活可调的容差和定位方式。
最后再分享一个小技巧:跑完工具后,用符号化按编号字段拉伸色带显示一遍,如果颜色从左上到右下平滑过渡,说明排序逻辑没问题;如果出现大范围杂色跳变,直接一目了然。这个可视化检查方法比盯着属性表看10分钟编号有效得多。