简介:面向参与第三次全国土地调查及日常国土变更调查的GIS从业人员,ArcGIS国土三调VCT工具箱V1.2提供了一套可直接加载使用的数据处理方案,重点解决VCT与File Geodatabase之间转换、地块整理、边界校正和属性赋值等重复操作,适合具备ArcGIS基础、希望减少手工处理环节的测绘与自然资源管理用户。压缩包共7个文件,约18.09MB,以3个xml配置说明、2个zip示例数据包、1个pyt工具箱脚本和1份docx操作说明为主,兼顾工具部署、样例练习与参数理解。该资源已有4663人学习,读者可借助现成脚本快速完成VCT格式转换与数据检查,结合示例数据和说明文档验证流程,并参考工具箱中的处理逻辑排查常见问题,从而降低重复劳动、提升成果整理与上报前的数据规范性。
1. 拿到 VCT 工具箱,先别急着双击
一批 VCT 交换文件摆在面前,ArcGIS 里直接拖进去只会得到一个「无法识别」的对话框——这就是很多做国土三调成果整理的人最熟悉的一幕。VCT 是全国统一的矢量数据交换格式,纯文本,ArcGIS 原生不认;而实际交付时又要你交回 GDB、Shapefile 或者重新导出的 VCT。中间这段搬运工作,手工做不现实,arcgis国土三调 VCT 工具箱 V1.2 这类资源包就是干这个的:批量读取 VCT 段结构、构建要素类、回填属性、校验地类编码、重算面积、再反向导出。它适合手上有几十到上千个 VCT 文件、需要反复做格式往返的作业人员,也适合想搞清楚 VCT 到底怎么被解析出来的开发人员。下面按我实际拆包的顺序讲,从文件结构一路走到导出验证。
2. VCT 段结构拆解:先看懂文件再谈转换
VCT 的全称是地理空间数据交换格式,落到文件上就是一堆用Begin/End包起来的段落。你把它当黑匣子,工具箱一出错就只能干瞪眼;把段结构摸清楚,九成的转换异常都能自己定位。这一章先解决「文件长什么样」和「工具箱能不能跑起来」两件事。
2.1 用三十行脚本把 VCT 切成段
VCT 的骨架是固定的几大段:Head(头文件,放版本、单位、坐标系类型)、FeatureCode(要素代码表)、TableStructure(属性表结构定义)、Point/Line/Polygon/Annotation(几何段)、Attribute(属性段)。几何段里每个要素先占一行要素头,后面跟若干行坐标点。不同版本、不同生产软件导出的文件在细节上会有出入,比如环与环之间用什么分隔、属性值带不带引号。所以第一步不是写转换,而是数一数你手上的文件到底有哪些段、每段多少行。
# vct_sections.py 统计 VCT 各段的有效行数,先摸清文件骨架 import re from collections import Counter # 段起止标记形如 PolygonBegin / PolygonEnd SEC = re.compile(r'^([A-Za-z]+)(Begin|End)$') def scan(path, enc='gbk'): stack = [] # 用栈维护段落嵌套关系 counter = Counter() with open(path, 'r', encoding=enc, errors='replace') as f: for line in f: s = line.strip() m = SEC.match(s) if m: if m.group(2) == 'Begin': stack.append(m.group(1)) else: if stack: stack.pop() continue if stack: counter[stack[-1]] += 1 # 只统计当前最内层段的行数 return counter if __name__ == '__main__': stat = scan(r'D:\work\vct\DK_001.vct') for k, v in stat.most_common(): print(f'{k:<20}{v}')逻辑说明:stack保证嵌套段的归属正确,比如Attribute段如果内部还有子段,行数不会被算到外层;errors='replace'是关键,VCT 里偶尔混进一个非法字节,用严格解码会直接抛UnicodeDecodeError让整个读取中断,而排查阶段我们只需要知道有多少行。
参数说明:enc默认gbk,因为早期生产的交换文件大量使用 GBK;如果打印出来中文全是问号,把它换成utf-8再跑一次。文件名路径不要带中文空格之外的怪字符,arcpy 对路径里的特殊符号很敏感。
跑完之后你会得到一张类似「Polygon 段 12 万行、Attribute 段 8 千行」的清单。这里有个经验判断:如果一个图斑平均 15 个点,12 万行坐标大约对应 8 千个面,跟属性段 8 千行对得上,说明文件是完整的;如果属性段只有 3 千行,那大概率是导出时中断了,这种文件后面转换必然对不上号,趁早退回重导。
2.2 工具箱里哪些文件能动、哪些别碰
VCT 工具箱压缩包解开后,一般能看到这几类东西。动手之前先分清职责,改错文件比不改更麻烦。
| 文件/目录 | 典型作用 | 能不能改 |
|---|---|---|
.tbx工具箱文件 | ArcGIS 里的工具入口,定义参数和调用关系 | 不建议改,改坏后工具直接消失 |
.pytPython 工具箱 | 逻辑主体,多数转换脚本在这里 | 可以改,改前先复制一份 |
*.py独立脚本 | 被.pyt调用或用命令行直接跑 | 可以改,最好保留原始备份 |
doc/或说明.txt | 参数约定、字段对照、版本要求 | 只看,不动 |
code/代码表 | 地类编码、要素代码字典 | 可以增补,不要删减 |
.tbx是二进制格式,用文本编辑器打开是乱码,改动基本靠 ArcGIS 图形界面;.pyt是纯文本 Python,里面通常写着class Toolbox(object)和若干class Tool(object),真正的转换逻辑要么写在execute里,要么import外部.py。我一般先看.pyt里的import语句,能立刻知道它依赖哪些模块、有没有用第三方库(比如pandas、openpyxl),第三方库缺失是工具跑不起来最常见的原因之一。
还有一点:V1.2 这类版本号往往意味着作者修过前版的兼容问题。如果你手上有旧版,注意比对TableStructure段的解析代码——属性字段数量、字段类型缩写(Character/Integer/Float/Date)在不同版本里处理方式不一样,直接混用会出现字段错位,A 字段的值跑进 B 字段,这种错误最阴,因为不报错。
2.3 运行环境与编码的两道前置检查
在双击工具之前,先花两分钟确认两件事,能省掉后面一半的无效排查。
第一件是编码。VCT 里的中文集中在FeatureCode段(地类名称)和Attribute段(属性值)。同一个批次的文件可能 GBK 和 UTF-8 混着来,尤其是不同单位分批提交的情况。判断方法很简单:
# check_enc.py 批量判断 VCT 的文本编码 import os def guess_enc(path, sample=8192): raw = open(path, 'rb').read(sample) for enc in ('utf-8', 'gbk'): try: raw.decode(enc) return enc except UnicodeDecodeError: continue return 'unknown' folder = r'D:\work\vct' for name in os.listdir(folder): if name.lower().endswith('.vct'): p = os.path.join(folder, name) print(f'{name:<30}{guess_enc(p)}')注意这里只读前 8KB 做判断,因为绝大多数 VCT 的头部就有中文。这个方法的局限是:纯 ASCII 的文件用 UTF-8 解码永远成功,会被误判成 UTF-8,但其实它对 GBK 也兼容,不影响后续处理。真正要小心的是被判成unknown的文件,那说明文件头就坏了。
第二件是 arcpy 环境。ArcGIS 桌面版装完之后,Python 有几个版本并存很正常,arcpy只跟着 ArcGIS 自己的 Python 走。命令行里import arcpy失败,说明你用的是系统 Python 而不是 ArcGIS Python。
# env_check.py 确认 arcpy 可用并打印关键环境信息 import sys import arcpy print('Python:', sys.version) print('ArcGIS:', arcpy.GetInstallInfo()['Version']) print('Product:', arcpy.ProductInfo()) print('Scratch GDB:', arcpy.env.scratchGDB) print('Workspace:', arcpy.env.workspace)参数和输出说明:GetInstallInfo()['Version']给出的是产品版本号,用来判断工具是不是为当前版本写的;ProductInfo()会返回ArcInfo/ArcEditor/ArcView,如果是ArcView,部分高级工具(比如拓扑相关的)可能调用失败;scratchGDB是临时地理数据库路径,转换过程的中间要素类默认落在这里,磁盘空间不够时这一步就会报错,提前看一眼有好处。
arcpy.env.workspace默认是空,工具内部一般会自己设,但如果你自己写脚本调用,记得在开头显式赋值,否则ListFeatureClasses()之类的函数会去一个你意想不到的目录里找数据。
3. VCT 转 GDB:面、线、点三类要素的构建
段结构看明白、环境确认没问题,接下来才是真正的转换。VCT 转 GDB 的核心是三件事:把坐标行拼成几何、把要素头里的标识码和要素代码带过去、把Attribute段的属性值按标识码回填。三调交换文件里面要素(地类图斑、行政区)占绝对大头,先把面打通,线和点的逻辑是同一套。
3.1 把 Polygon 段读成中间结构
不建议一边读文件一边往要素类里插数据,因为一旦某条记录格式异常,你已经插进去半批数据,回滚很麻烦。稳妥做法是先全部读进内存,形成一个{标识码: 几何+代码}的字典,检查数量无误之后再一次性建库。
# parse_polygon.py 把 Polygon 段解析成字典结构 def parse_polygon(path, enc='gbk'): """ 返回 {fid: {'code': '0101', 'rings': [[(x, y), ...], ...]}} rings 里每个子列表是一个环,环与环之间用要素内的分隔行切分 """ feats, rings, ring = {}, [], [] fid, code = None, None inside = False with open(path, 'r', encoding=enc, errors='replace') as f: for raw in f: s = raw.strip() if s == 'PolygonBegin': inside = True continue if s == 'PolygonEnd': break if not inside or not s: continue parts = s.split(',') if len(parts) == 4: # 要素头行:标识码,分类码,要素代码,属性表号 if fid is not None: if ring: rings.append(ring) feats[fid] = {'code': code, 'rings': rings} fid, code = int(parts[0]), parts[2].strip() rings, ring = [], [] elif len(parts) == 3: # 坐标行:点号,东坐标,北坐标 ring.append((float(parts[1]), float(parts[2]))) # 其他行(如环分隔行)按你的文件实际格式在此追加处理 if fid is not None: if ring: rings.append(ring) feats[fid] = {'code': code, 'rings': rings} return feats逻辑说明:前一个要素的收尾放在「读到下一个要素头」的时刻,最后一条要素在循环结束后单独补收,这是解析流式文本最常见的漏点,很容易丢掉最后一个图斑。
参数说明:fid是要素标识码,三调里通常是长整型,转int时如果遇到前导零的字符串形式要注意别丢位;code取第 3 个字段(要素代码),它对应地类编码,是后面校验的抓手;坐标顺序是「东坐标,北坐标」,写进arcpy.Point(x, y)时 x 是东坐标,别写反,写反的图斑会定位到地球另一侧,但工具不会报错。
「环分隔行」是这套解析里最看人品的部分。有的文件每个环前会有一行环号,点数只有两个字段,有的文件直接用一个特定的标识行,还有的文件外环内环连在一起靠首尾点闭合来区分。我一般会先head -50打印前 50 行确认,再决定len(parts) == 2分支怎么写。这一步没有通用答案,但一旦确认了格式,后面所有文件都是同一套。
3.2 用 arcpy 构建面要素类
拿到字典之后建库就快了。关键是空间参考要设对,否则后续面积计算全套错。
# build_fc.py 由解析结果构建面要素类 import arcpy def build_polygon_fc(feats, out_fc, sr_code=4490): arcpy.env.overwriteOutput = True sr = arcpy.SpatialReference(sr_code) # 4490=CGCS2000 地理坐标 gdb, name = out_fc.rsplit('\\', 1) arcpy.CreateFeatureclass_management(gdb, name, 'POLYGON', spatial_reference=sr) arcpy.AddField_management(out_fc, 'BSM', 'TEXT', field_length=20) arcpy.AddField_management(out_fc, 'YSDM', 'TEXT', field_length=12) with arcpy.da.InsertCursor(out_fc, ['SHAPE@', 'BSM', 'YSDM']) as cur: for fid, f in feats.items(): arr = arcpy.Array() for r in f['rings']: if r[0] != r[-1]: r.append(r[0]) # 强制闭合,VCT 里多数已闭合但别赌 arr.add(arcpy.Array([arcpy.Point(x, y) for x, y in r])) cur.insertRow([arcpy.Polygon(arr), str(fid), f['code']])逻辑说明:arcpy.Array()里再嵌套Array(),外层数组的每个元素代表一个环,这是 ArcGIS 表达带洞多边形的标准写法;插入前补一次首尾点闭合,是因为少数生产软件的导出在最后一个点上会省掉重复,不闭合的面在后续拓扑检查里会被判成「自相交」。
参数说明:sr_code=4490是 CGCS2000 地理坐标系。如果原始 VCT 存的是投影坐标(三调成果常见 3 度带高斯克吕格,带号 38、39、40 对应 EPSG 4526、4547 之类),这里就必须换成对应的投影坐标系代码,否则坐标被当成经纬度读进去,值域直接超出 ±180 被截断或报错。判断方法:随便挑一个坐标值看数量级,六位数以上(比如 38500000 附近)就是投影坐标。
BSM字段长度给 20 是留余量,实际标识码常见 8 到 19 位;YSDM给 12 位是为了兼容带子类后缀的要素代码。
3.3 属性段回填与字段映射
几何有了,属性还在Attribute段里躺着。这一段的结构是前面TableStructure段定义的字段顺序,一行一条记录,第一个字段是标识码,用来跟几何关联。
# parse_attr.py 解析 Attribute 段并按标识码回填 import arcpy def parse_attr(path, enc='gbk'): rows, inside = [], False with open(path, 'r', encoding=enc, errors='replace') as f: for raw in f: s = raw.strip() if s == 'AttributeBegin': inside = True continue if s == 'AttributeEnd': break if inside and s: rows.append(s.split(',')) return rows def fill_attr(fc, rows, mapping): """ mapping: {VCT字段序号: 要素类字段名},例如 {1: 'DLBM', 5: 'TBMJ'} 第 0 个字段固定是标识码,用于匹配 BSM """ fields = ['BSM'] + list(mapping.values()) with arcpy.da.UpdateCursor(fc, fields) as cur: for rec in cur: hit = next((r for r in rows if r[0] == rec[0]), None) if hit is None: continue for idx, fname in mapping.items(): pos = fields.index(fname) rec[pos] = hit[idx].strip() cur.updateRow(rec)逻辑说明:mapping用字段序号而不是字段名做键,因为 VCT 的属性段是按TableStructure的顺序排列的,没有字段名信息,序号是唯一可靠的对应关系。这个映射表必须对着文件里的TableStructure段一个个抄,抄错一位,整列数据就串了。
参数说明:rows是字符串列表,全量加载进内存,几千条记录没问题,上十万条要改成流式处理或者先写进临时表再JoinField。hit用next()线性查找,在数据量大时是 O(n²),实测十万级会慢到不可接受,那时候应该先建索引字典。
字段映射最容易出错的地方是类型。VCT 里面积、周长都是字符串形式的小数,而要素类里你可能建的是DOUBLE,UpdateCursor会自动转,但如果字符串里带空格或者空值,会抛类型异常。稳妥做法是先.strip()再判空,空值写None而不是空字符串。
4. 地类编码校验与面积重算
转换跑通只是及格线,成果能不能用,取决于两件事:地类编码是不是都在有效字典里、面积是不是按规范口径算的。这两块是质检退件的高发区,也是工具箱里最值得细看的部分。
4.1 地类编码字典校验
三调的地类编码是四级分类,常用的也就那么几十个。校验逻辑本身不复杂,难的是字典要维护对。
| 编码 | 名称 | 编码 | 名称 |
|---|---|---|---|
| 0101 | 水田 | 0301 | 乔木林地 |
| 0102 | 水浇地 | 0302 | 竹林地 |
| 0103 | 旱地 | 0401 | 天然牧草地 |
| 0201 | 果园 | 1101 | 河流水面 |
| 0202 | 茶园 | 1103 | 水库水面 |
| 0203 | 橡胶园 | 1201 | 空闲地 |
| 0204 | 其他园地 | 1006 | 农村道路 |
# check_dlbm.py 校验地类编码是否落在有效集合内 import arcpy VALID = {'0101','0102','0103','0201','0202','0203','0204', '0301','0302','0401','1101','1103','1201','1006'} def check_code(fc, field='DLBM'): bad = [] with arcpy.da.SearchCursor(fc, ['OID@', field]) as cur: for oid, v in cur: v = (v or '').strip() if v not in VALID: bad.append((oid, v)) return bad fc = r'D:\work\out.gdb\DK' result = check_code(fc) print('异常记录数:', len(result)) for oid, v in result[:20]: print(oid, repr(v))逻辑说明:返回 OID 而不是直接改数据,是为了保留现场。作业中经常出现「编码对但名称不对」和「编码整体错位一位」两种情况,前者改字典,后者说明源文件本身有问题,得退回上一环节。
参数说明:VALID集合必须和当前任务的口径一致,不同年份、不同区域的分类细度有差别,直接抄别人的字典很容易误杀。repr(v)打印出来能看见不可见字符,'0101 '和'0101'在屏幕上长得一样,但集合判断会失败,这个坑我踩过不止一次。
还有一类隐蔽问题:编码是数字型字段存进去的,前导零被吃掉,0101变成101。这种情况在SearchCursor里打印出来是整数 101,一眼能看出来。解决办法是建字段时就定成TEXT,别图省事用LONG。
4.2 投影面积和椭球面积差的不是一点点
面积这块是重灾区。ArcGIS 里Calculate Geometry在投影坐标系下算的是平面面积,而成果要求的是椭球面积,两者在中小比例尺下差异可能到千分之几,一个县动辄几万亩的偏差,质检一比对就露馅。
# calc_area.py 同时算出平面面积与椭球面积,方便比对 import arcpy fc = r'D:\work\out.gdb\DK' arcpy.AddField_management(fc, 'PMJ', 'DOUBLE') arcpy.AddField_management(fc, 'QTMJ', 'DOUBLE') # 平面面积:依赖要素类自身的坐标系 arcpy.CalculateGeometryAttributes_management( fc, [['PMJ', 'AREA']], area_unit='SQUARE_METERS') # 椭球面积:用几何对象的测地线面积属性 with arcpy.da.UpdateCursor(fc, ['SHAPE@', 'QTMJ']) as cur: for shp, _ in cur: cur.updateRow([shp, shp.geodesicArea if shp else None])逻辑说明:CalculateGeometryAttributes的AREA走的是平面公式,速度快;geometry.geodesicArea走的是椭球公式,逐要素计算,慢但准。两个值都留着,做差值统计,差值比例超过阈值(比如 0.5%)的记录单独拎出来复查,通常是坐标系或投影带设错了。
参数说明:area_unit='SQUARE_METERS'必须显式指定,默认单位跟着要素类坐标系走,地理坐标系下会返回平方度,那个数没有意义。geodesicArea在空的或退化的几何上会抛异常,所以加了if shp else None的兜底。
还有一种情况:VCT 里本身就带着面积字段,转换过来之后应该跟重算值做比对。差值在 1 平方米以内属于舍入正常,差值几百平方米说明源数据的面积字段没跟着图形更新过,这种记录在汇交前必须重算覆盖,不能留。
4.3 拓扑检查:自相交、重叠、缝隙
图形层面的问题,靠肉眼看是看不完的,必须跑检查。最基础的两步:CheckGeometry找几何非法,Intersect找面重叠。
# topo_check.py 几何合法性检查 + 面重叠检测 import arcpy fc = r'D:\work\out.gdb\DK' # 第一步:几何合法性,输出一张问题表 arcpy.CheckGeometry_management(fc, r'in_memory\geom_err') n = int(arcpy.GetCount_management(r'in_memory\geom_err')[0]) print('几何异常要素数:', n) # 第二步:自重叠检测,同一要素类跟自己求交 arcpy.Intersect_analysis([fc], r'in_memory\overlap', 'ONLY_FID') with arcpy.da.SearchCursor(r'in_memory\overlap', ['FID_' + fc.split('\\')[-1]]) as cur: dup = [r[0] for r in cur] print('存在重叠的要素数:', len(set(dup)))逻辑说明:CheckGeometry抓的是单个要素自身的几何错误(自相交、环方向反、点重复),它不会告诉你两个图斑压在一起;Intersect用ONLY_FID模式只输出要素 ID 不输出几何,速度快很多,专门用来找重叠。缝隙检测更麻烦一点,常见做法是把所有面UnaryUnion之后跟原要素类做对称差,或者转成线之后找悬挂节点。
参数说明:Intersect的第三个参数是join_attributes,用ONLY_FID时输出表里字段名是FID_要素类名,要素类名从路径里截取,路径分隔符在 Windows 下是反斜杠,写脚本时注意转义。这个检查在大数据量下很吃内存,一万个面以上建议先按行政区切片再分批跑。
检查结果不要只看总数,要看分布。如果问题集中在某几个村,多半是那批数据来自不同作业队伍;如果均匀散布,那是转换脚本的问题,回去查环的处理逻辑。
5. 避坑与排查:五个翻车现场
工具箱用起来之后,报错信息往往只有一句「执行失败」,真正的原因藏在数据和环境里。下面五条是我在实际转换中最常撞上的,按「现象 → 原因 → 解决」记下来,遇到了直接对号入座。
第一条:中文全是乱码,属性值变成问号或方块。现象是转换完成后打开属性表,地类名称一列显示????或者一堆生僻字。 原因是编码判断错了。GBK 文件按 UTF-8 读,或者反过来,都会产生这种结果,而且不会抛异常,因为errors='replace'把非法字节替换掉了。 解决办法是先跑一遍check_enc.py确认编码,然后在所有open()调用里显式传encoding参数,不要依赖系统默认。系统区域设置不同,默认编码可能是cp936也可能是utf-8,同一份脚本换台机器结果就不一样。
第二条:面要素画出来是反的,洞里套着外环。现象是图斑显示成一个巨大的多边形,中间挖空,或者本该是洞的地方被填实了。 原因是环的嵌套层级搞错了。VCT 里一个要素可能有多个环,哪个是外环、哪个是内环,靠的是点的排列顺序和环的先后关系。如果解析时把内环当外环塞进Array的第一层,ArcGIS 会按最大环自动判定外边界,结果就是整片区域被填满。 解决办法是建库后抽查几个带洞的图斑(比如林地中间有水面),用!shape.partCount!看环数,再用编辑工具目视确认。批量排查可以算shape.area跟shape.extent.width * shape.extent.height的比值,比值异常小的多半有环问题。
第三条:面积整体偏大或偏小,比例接近一个固定系数。现象是重算面积后,跟源数据的面积字段普遍差百分之几,而且方向一致。 原因是投影带选错了,或者把投影坐标当经纬度读了。3 度带跨带的数据如果只用一个带号,边缘区域会有长度变形;更糟的是坐标系代码直接填了 4490,那所有坐标都会被当成经纬度,值域校验一过就全错。 解决办法是看坐标数量级,六位以上是投影坐标;再确认带号,三调成果一般按县级行政区的中央经线确定,跨带的要分带处理。重算前后做一次差值直方图,能看到明显的系统性偏移还是随机误差。
第四条:字段名截断,两个字段变成同一个名字。现象是建字段时没报错,转换完成后发现TBMJ和TBMJ_1同时存在,或者后一个字段直接覆盖了前一个。 原因是地理数据库的字段名长度限制。Shapefile 只有 10 个字符,GDB 虽然宽松但也有保留字和非法字符。VCT 的TableStructure段里字段名可能是中文全称,直接照搬会被截断,截断后重名的就自动加后缀。 解决办法是转换前先建一张显式的映射表,中文名对英文短名,长度控制在 8 个字符以内,避开OID、Shape、AREA这类保留字。别指望工具自动处理,各家实现不一样。
第五条:工具在 ArcMap 里能跑,换到 ArcGIS Pro 就报找不到模块。现象是同一个.pyt文件,在 ArcMap 的 Python 2.7 环境里正常,到 Pro 的 Python 3 环境里一执行就ModuleNotFoundError,或者语法错误。 原因是 Python 版本差异。print语句写法、dict.iteritems()、字符串与 bytes 的处理,2 和 3 之间的断层很多。V1.2 这类版本如果写在过渡期,很可能只测过其中一边。 解决办法是先看.pyt开头的import和语法风格,能判断目标环境;不兼容的话手动改,重点改print、整除、unicode相关调用。改完在两套环境各跑一个小样本,别拿全量数据试错。
6. 反向导出 VCT 与回读验证
转换只是半程,成果最终往往要重新导回 VCT 交出去。反向导出比正向更容易出问题,因为规范对字段顺序、坐标精度、段落顺序都有要求,差一位就不认。这一章讲怎么把要素类写回 VCT,以及怎么验证写出来的东西是对的。
6.1 按段拼接写回 VCT
导出时顺序不能乱:先Head,再FeatureCode,再TableStructure,然后几何段和属性段。字段顺序必须严格按TableStructure里定义的来,属性段里每一行的字段值也要按同样顺序排。
# export_vct.py 把面要素类写回 VCT 文本 import arcpy def export_polygon(fc, out_path, fields, enc='gbk'): """fields: 属性字段列表,顺序即 TableStructure 中的顺序""" with open(out_path, 'w', encoding=enc, newline='\n') as f: f.write('Head\n') f.write('Version:2.0\nUnit:M\nType:1\nCoordinateSystemType:1\n') f.write('FeatureCodeBegin\n') # 要素代码表按实际使用的地类批量写入 for code in sorted({r[0] for r in arcpy.da.SearchCursor(fc, ['YSDM'])}): f.write(f'1,530101010,{code},\n') f.write('FeatureCodeEnd\n') f.write('TableStructureBegin\n') for i, name in enumerate(fields, 1): f.write(f'1,{i},{i},{name},Character,32\n') f.write('TableStructureEnd\n') f.write('PolygonBegin\n') with arcpy.da.SearchCursor(fc, ['OID@', 'YSDM', 'SHAPE@']) as cur: for oid, code, shp in cur: f.write(f'{oid},1,{code},0\n') for i in range(shp.partCount): part = shp.getPart(i) for j, pt in enumerate(part): f.write(f'{j+1},{pt.X:.3f},{pt.Y:.3f}\n') f.write('PolygonEnd\n') f.write('AttributeBegin\n') with arcpy.da.SearchCursor(fc, ['OID@'] + fields) as cur: for rec in cur: vals = ['' if v is None else str(v) for v in rec[1:]] f.write(','.join(vals) + '\n') f.write('AttributeEnd\n')逻辑说明:坐标格式化用:.3f保留三位小数,对应毫米级精度,三调成果一般按这个来;写多了位数不规范,写少了精度不够。newline='\n'是必须的,Windows 下默认\r\n,某些解析程序按单字节换行切分会多出一个\r,导致最后一个坐标值带上脏字符。
参数说明:fields的顺序必须跟TableStructure段写的顺序完全一致,这是整份文件能否被读取的第一道关。enc要与目标接收方的要求一致,一般随源文件来。要素代码530101010这类前缀在FeatureCode段里要跟实际分类对应,不能所有图层都写同一个。
6.2 回读比对:三个必查项
导出完成不等于交付完成。写出去的文件必须能读回来,而且要跟源数据一致。我固定查三项:要素数量、面积合计、属性非空率。
# verify.py 导出后回读三项核对 import arcpy src_fc = r'D:\work\out.gdb\DK' vct = r'D:\work\out\DK_export.vct' # 1. 要素数量 src_n = int(arcpy.GetCount_management(src_fc)[0]) with open(vct, encoding='gbk') as f: txt = f.read() export_n = txt.count('\n') and sum(1 for ln in txt.split('PolygonBegin')[1].split('PolygonEnd')[0].splitlines() if ln.count(',') == 3) # 2. 面积合计 with arcpy.da.SearchCursor(src_fc, ['QTMJ']) as cur: total = sum(r[0] or 0 for r in cur) print(f'源要素数 {src_n} / 导出行数 {export_n} / 面积合计 {total:.2f}') # 3. 属性非空率 with arcpy.da.SearchCursor(src_fc, ['DLBM', 'TBMJ']) as cur: rows = list(cur) empty = sum(1 for a, b in rows if not a or b is None) print(f'属性空值 {empty} 条,占比 {empty/max(len(rows),1):.2%}')逻辑说明:行数统计用「逗号数量等于 3」来识别要素头行,这是最轻量的办法,不需要再写一遍完整解析器;面积合计拿椭球面积字段比,跟源数据比也不用重新算几何。属性空值率是判断字段映射有没有串位的快捷指标,正常数据空值率应该很低,突然飙到 30% 以上说明有位错。
参数说明:Export_n的统计依赖刚写的文件结构,如果文件很大(几百 MB),一次性read()会吃掉内存,改成逐行遍历。max(len(rows), 1)防零除,空要素类也能安全输出。
三项都对上,基本可以认为往返无损。有一项对不上,从导出脚本的段落顺序倒着查,八成是某个Begin/End配对写错了,或者属性行的字段数量跟TableStructure里声明的不一致。
我现在的习惯是,任何一批 VCT 导出之后,不看到这三行数字对上,就不往交付目录里放。有一次赶工跳过这步,交出去的文件里有三分之一属性行少了一个字段,接收方解析后整列右移,编码全串,返工花了两天。从那以后每次导出都强制走一遍回读比对,哪怕只导了十个文件。希望帮到你。
本文还有配套的精品资源,点击获取