简介:面向自然资源、测绘与GIS数据管理人员的批量标准矢量SHP互转TXT工具,用于将Shapefile中的点、线、面几何对象及属性表转换为通用文本格式,解决后续统计、交换和非GIS环境读取难的问题;适用建设用地报批、设施农用地上图、卫片处理等场景,普通操作人员无需复杂安装,解压即可使用。压缩包共147个文件,约17.12MB,以dll运行库、gfs、csv坐标参数、wkt与xsd格式定义文件为主,另有exe主程序、config配置和少量dxf/dgn样例文件,整体结构便于按功能理解。已有4845人学习下载。除主程序外,包内还带有较完整的坐标系统参数表与字段定义文件,可在转换时处理坐标系统匹配、属性字段映射等关键细节;对需要批量整理矢量数据、在Excel或文本环境中继续分析的从业者,可直接作为日常数据预处理工具使用。
1. 批量 SHP 互转 TXT:为什么业务系统只认文本格式
做过 GIS 数据交付的人都有过这种经历:甲方给过来几百个 SHP 文件,业务系统却只收 TXT,字段顺序、分隔符、坐标格式全按他们的规矩来。ArcGIS 里一个个导出表格再另存,碰上目录深、字段多、文件多的情况,一个下午就耗在重复劳动上,还容易漏字段、改错分隔符。这个批量标准矢量 shp 互转 txt 工具,就是针对这个场景做的:批量遍历目录、按配置输出 TXT,也支持把规范 TXT 反写回 SHP,适合测绘内业、GIS 数据加工、以及需要跟业务系统做文本交换的从业者。下面我把整个转换链路拆开讲,从 SHP 的文件结构到批量命令,再到我踩过的坑,一次说完。
2. shapefile 数据模型:字段、编码、坐标系决定转换成败
2.1 SHP 不是单个文件:主文件、索引、属性表各管一摊
SHP 虽然是 GIS 交换的通用格式,但它的存储方式很特殊——一个完整的 shapefile 是一组文件的集合,缺了任何一个成员都可能出问题。最常见的四个主文件分别是 .shp 存储几何坐标、.shx 存储几何索引、.dbf 存属性字段、.prj 存坐标系定义。此外还有 .cpg 存字符编码声明,这个文件很小,但影响中文属性的读取。
所以做批量转换前,第一件事不是写代码,而是先检查目录里每个 shp 的同名文件是否齐全。我在项目里见过 .prj 文件丢失的 shp,坐标数据本身没坏,但转换工具不知道它是什么坐标系,导出经纬度时直接按无投影处理,整体偏移了几百米。检查方式很简单,在资源管理器里看一眼文件扩展名列表,或者用脚本统计同名文件的数量。
| 文件后缀 | 作用 | 缺失后果 |
|---|---|---|
| .shp | 几何坐标 | 无法读取图形 |
| .shx | 几何索引 | 部分工具无法打开 |
| .dbf | 属性字段 | TXT 里只有坐标没有属性 |
| .prj | 坐标系定义 | 坐标转换无依据,可能偏移 |
| .cpg | 字符编码声明 | 中文属性大概率乱码 |
批量工具在设计时也应该把这些文件纳入检测范围。我一般会先跑一遍完整性检查,把缺 .dbf 和 .prj 的文件单独列出来,而不是直接进入转换流程。这一步能避免后面大量无效输出。
2.2 字段类型与编码是 TXT 乱码的根源
TXT 是纯文本,但 SHP 的属性存储在 dbf 文件里,dbf 是二进制格式,字段类型和编码都必须处理清楚。dbf 常见的字段类型有 C 字符型、N 数值型、F 浮点型、D 日期型。而 GDAL/OGR 读取时会把它们映射成 OFTString、OFTInteger、OFTReal、OFTDate 这些类型。转换时最容易翻车的就是类型映射。
数值型字段就是个典型:dbf 里的面积字段定义为 N(19,3),读取后是 OFTReal,如果你在脚本里直接 str() 输出,可能得到 1987.3000000001 这种带一长串小数的结果。日期字段更麻烦,OFTDate 读出来是日期对象,不格式化直接写进 TXT 会变成一串数字或带时分秒的完整时间。正确做法是按字段类型分别处理,数值控制小数位,日期只取年月日。
编码问题则是中文用户最头疼的。国内生产的 shp 属性大多是 GBK 编码,但 GDAL 新版默认按 UTF-8 尝试读取,读 GBK 就会出现字段名乱码、属性值乱码。解决办法是在转换脚本里显式指定源编码:读 dbf 时设置 GBK,写 TXT 时统一转 UTF-8。第一次转换前先抽一条要素打印字段名和值,确认编码正常后再跑全量,这个习惯能省下大量返工时间。
2.3 坐标系与几何类型先确认再动手
SHP 的坐标参考信息写在 .prj 文件里,但很多 shp 的 .prj 写得并不规范,有的只写了文字描述,有的甚至写的是旧标准。批量工具在转换时需要处理两种坐标系情况:一是 shp 本身是投影坐标(比如 CGCS2000 高斯投影),要转成经纬度输出;二是 shp 已经是经纬度,只需要原样输出。这两种情况的处理逻辑完全不同。
几何类型也会影响 TXT 的字段设计。点要素可以直接输出 X、Y 坐标,线要素和面要素则需要考虑坐标串的顺序和分隔规则。面要素还涉及闭合边:shapefile 里的面环首尾坐标相同,输出到 TXT 时业务系统不一定接受这种闭合表达,需要按需求决定是否去除重复的结束点。多部件面更复杂,一个要素有多个外环,输出时要做好部件分隔标记,否则导入业务系统时图形会乱。
我在实际项目里常用的处理顺序是:先读 .prj 确认坐标系,再遍历要素类型,最后按类型写不同的 TXT 模板。工具内部应该支持按几何类型分组输出,点、线、面分开建目录,这样下游处理更容易对接。这些看起来是细节,但批量跑起来之后,任何一条规则错了都是几百个文件一起错。
3. 批量转换实操:ogr2ogr 命令与 Python 脚本两种路径
3.1 环境准备:GDAL 与 Python 绑定
转换工具的核心引擎是 GDAL/OGR,这是 GIS 领域最常用的开源读写库,支持 shapefile 的完整读写。命令行工具 ogr2ogr 是 GDAL 自带的转换程序,而 Python 绑定 osgeo.ogr 则更适合做批量定制开发。我一般两个都装:命令行用来快速验证参数,Python 脚本用来跑全量目录。
环境检查很简单,打开终端执行 ogrinfo --version 看版本,确保 GDAL 版本在 3.0 以上,太老的版本对 UTF-8 和 EPSG 支持都不好。Python 侧执行 pip install gdal,或者用 osgeo4w 安装包,安装完确认from osgeo import ogr能正常导入。如果只是简单转换,也可以考虑 pyshp 这个纯 Python 库,它轻量但功能不如 OGR 完整,涉及坐标系转换时还是 OGR 更稳。
ogrinfo --version python -c "from osgeo import ogr; print(ogr.GetDriverCount())"第一行命令查看 GDAL 版本,第二行确认 Python 能加载 OGR 驱动。如果第二行能输出一个数字(驱动数量),说明环境正常。这里建议用 conda 或 osgeo4w 安装 GDAL,避免 pip 安装时缺 DLL 的问题,Windows 上尤其明显。
3.2 ogr2ogr 命令行转换与参数设置
ogr2ogr 是一个通用的矢量数据转换工具,支持几十种格式互转。SHP 转 TXT 常见做法是转成 CSV 再加工,但更省事的方式是直接用 CSV 驱动输出,再通过参数控制分隔符、坐标格式。下面是我常用的命令:
ogr2ogr -f CSV output_dir/area_points.txt input.shp \ -lco SEPARATOR=TAB \ -lco GEOMETRY=AS_XY \ -t_srs EPSG:4326 \ -select "code,name,area" \ -where "code LIKE '11%'"这里 -lco GEOMETRY=AS_XY 表示输出几何的 X、Y 坐标列,SEPARATOR=TAB 指定 Tab 作为分隔符,-t_srs EPSG:4326 表示转成 WGS84 经纬度,-select 控制输出字段,-where 做属性过滤。这样一条命令就能把面积字段、名称字段和坐标列一起输出,省去中间环节。但注意 GEOMETRY=AS_XY 只适用于点要素,线和面需要换成 AS_WKT 或 AS_XYZ。
WKT 方式输出的是完整几何表达,形如 POLYGON((x1 y1, x2 y2, ...)),这种格式适合需要完整图形的场景,但如果业务系统只认坐标串,WKT 就不合适了。我一般建议先用命令行跑一个单文件测试,确认列顺序和格式符合要求后,再把命令复制进批量脚本。命令行参数就是用来试错的,速度最快。
3.3 用 Python 脚本批量处理整个目录
当文件数量到几百个时,命令行的复制粘贴就不现实了。这时要用 Python 脚本遍历目录,逐个调用 OGR 转换,并且把转换结果写入日志。下面是一个可用的批量脚本骨架,处理的是最常见的点要素输出场景:
from osgeo import ogr, osr import os, sys src_dir = "D:/shp_input" out_dir = "D:/txt_output" sep = "|" field_list = ["code", "name", "area"] src_epsg = 4490 # CGCS2000 tgt_epsg = 4326 # WGS84经纬度 def build_transform(src_epsg, tgt_epsg): src = osr.SpatialReference() src.ImportFromEPSG(src_epsg) tgt = osr.SpatialReference() tgt.ImportFromEPSG(tgt_epsg) return osr.CoordinateTransformation(src, tgt) def shp_to_txt(shp_path, txt_path, transform, sep, field_list): ds = ogr.Open(shp_path, 0) lyr = ds.GetLayer(0) if lyr is None: return 0 count = 0 with open(txt_path, "w", encoding="utf-8", newline="") as f: f.write(sep.join(field_list + ["x", "y"]) + "\n") for feat in lyr: geom = feat.GetGeometryRef() if geom is None: continue pt = geom.Centroid() pt.Transform(transform) row = [] for name in field_list: field_defn = lyr.GetLayerDefn().GetFieldIndex(name) if field_defn < 0: row.append("") continue value = feat.GetField(field_defn) row.append(str(value).strip() if value is not None else "") row.append(f"{pt.GetX():.6f}") row.append(f"{pt.GetY():.6f}") f.write(sep.join(row) + "\n") count += 1 ds = None return count for root, dirs, files in os.walk(src_dir): for name in files: if name.lower().endswith(".shp"): shp_path = os.path.join(root, name) txt_path = os.path.join(out_dir, name[:-4] + ".txt") transform = build_transform(src_epsg, tgt_epsg) count = shp_to_txt(shp_path, txt_path, transform, sep, field_list) print(f"{name}: {count} rows")这段脚本的逻辑很直白:os.walk 遍历源目录所有 shp,对每个文件构建坐标转换对象,逐要素读取属性字段和几何质心,按分隔符拼行写入 TXT。字段缺失时补空字符串避免行数错位,几何为空时跳过但计数不增加,这样最后能通过行数对比发现异常文件。
参数说明:src_epsg 和 tgt_epsg 是关键变量,shp 是 CGCS2000 投影就写 4490,是 WGS84 就写 4326,实际用多少度带的投影要查 .prj 再定。field_list 的字段名必须和 dbf 里完全一致,大小写不同会导致 GetFieldIndex 返回 -1。编码方面,脚本统一用 UTF-8 写文件,读取 dbf 时如果遇到乱码,需要在 ogr.Open 前设置环境变量GDAL_SHAPE_ENCODING=GBK。
4. 输出 TXT 的字段编排:分隔符、精度与行式设计
4.1 分隔符与字段顺序:先问业务系统再定版式
转换工具能不能落地,一半在格式设计。TXT 看起来简单,但分隔符选错、列顺序对不上,业务系统导入时就会整批报错。最常见的分隔符是逗号、Tab、竖线。逗号的问题是字段内容里如果包含逗号会破坏列结构,Tab 很少出现在属性值里,竖线则最安全,但某些系统不支持。
| 分隔符 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 逗号 | 通用性强 | 属性值含逗号会错列 | 临时查看、Excel 打开 |
| Tab | 不易冲突 | 字段值含宽字符时对不齐 | 数据库导入、CSV 扩展 |
| 竖线 | 几乎不冲突 | 部分系统不识别 | 长文本字段多的业务系统 |
字段顺序优先级应该和业务系统表结构完全一致,而不是按 shp 属性表的默认顺序。我遇到的项目里,业务系统指定了 code、name、area、x、y 的顺序,而原始 shp 的属性顺序是 name、code、area,这时必须在转换脚本里显式按业务顺序取字段,不能直接用 dbf 的原始顺序。批量工具应该把字段顺序做成配置文件,这样每个项目改配置就行,不用改代码。
4.2 坐标精度与日期处理
坐标精度是 TXT 转换里最容易吵起来的参数。经纬度坐标 6 位小数约等于 0.1 米精度,7 位小数达到厘米级;投影坐标一般 2 到 3 位小数即可。输出精度过高不仅文件体积变大,部分业务系统处理浮点字符串还有精度上限。我的习惯是经纬度统一 6 位,投影坐标统一 2 位,在脚本里用格式化表达式控制,比如 f"{x:.6f}"。
日期字段在 dbf 里存的是日期对象,比如 2024-05-06,但通过 OGR 读取后转成字符串时可能变成 2024/05/06 00:00:00。业务系统通常只需要日期部分,所以转换时要做字符串截取。更稳妥的方式是先判断字段类型,再按类型格式化:数值控制小数位,日期只取前 10 个字符,字符型直接原样输出。类型分支是转换脚本必备的逻辑,不是可选项。
def format_field(feat, field_idx, field_type): if field_type == ogr.OFTInteger: return str(feat.GetFieldAsInteger(field_idx)) elif field_type == ogr.OFTReal: return f"{feat.GetFieldAsDouble(field_idx):.2f}" elif field_type == ogr.OFTDate: return feat.GetFieldAsString(field_idx)[:10] else: val = feat.GetFieldAsString(field_idx) return val.strip()这段函数是输出格式化的核心,它避免了对所有字段无脑 str() 导致的精度泛滥和日期串干扰。GetFieldAsInteger 直接取整,GetFieldAsDouble 配合格式化字符串控制小数位,OFTDate 取前 10 个字符得到 YYYY-MM-DD。如果字段类型判断不写,整个输出内容就无法保证稳定性。
4.3 面要素的坐标表达:闭合点、多部件与空洞
面要素转 TXT 比点要素复杂得多。shapefile 中的面环是闭合的,即首尾坐标相同,但业务系统里有的要求保留闭合点,有的要求去掉。判断方法很简单:比较环首尾坐标是否相同,相同就按需求去除,不同则保留。更麻烦的是多部件面,一个要素有多个外环,输出时必须加部件分隔标识。
| 几何情形 | TXT 表达方式 | 注意点 |
|---|---|---|
| 单部件面 | x1,y1,x2,y2,...,x1,y1 | 判断是否保留闭合点 |
| 多部件面 | 部件1#部件2 | 用 # 或空行分隔部件 |
| 带空洞面 | 外环+内环 | 环间用特殊标记区分 |
空洞(内环)的表示更讲究,一个带湖的面要素除了外环还有内环,业务系统如果只支持简单多边形,内环数据会被丢弃。标准工具一般按 WKT 的方式表达,外环和内环之间用逗号分隔、用括号区分,如果对方只接收坐标串,就要在内环前加负面积标识或特定字符。我建议在转换配置里增加一个几何模式参数,点、线、面分别设置输出模板,不要一套格式走天下。
4.4 字段过滤与索引列:只输出业务需要的列
很多原始 shp 动辄三四十个字段,但业务系统只用其中五六个。全量输出会让 TXT 文件巨大,导入也慢。工具应该支持字段过滤配置,只挑需要的字段输出。这类似 json 转 txt 时的掩码思路:先定义保留字段列表,转换时按列表取数,不在列表里的字段一律丢弃。字段过滤还能顺带解决字段名不一致问题,比如原始字段叫 FNAME,业务系统要求 name,在配置里做一次重命名映射。
另一个实用技巧是自动追加行号列。批量转换后要和源数据核对,有行号列可以快速定位某一条记录。行号列一般放在第 1 列,从 1 开始递增,不计入业务字段。这个列在最终交付前可以删掉,也可以在工具里加一个开关控制。有了行号,后面的行数比对和抽查逻辑就非常简单。
5. SHP 互转 TXT 避坑:五个高频翻车点与处理办法
5.1 中文乱码:字段名正常但属性值全是问号
现象:转换出来的 TXT 用记事本打开,中文字段名正常,但每个汉字都变成 ?? 或者乱码字符。
原因:dbf 里的属性是 GBK 编码,脚本以 UTF-8 方式读取,中文全部解析失败。部分 shp 自带 .cpg 文件声明了编码,但 GDAL 不一定按声明读取,尤其是手动改过属性表的文件。
解决:在打开数据源前设置环境变量 GDAL_SHAPE_ENCODING=GBK。这个变量对 OGR 的 dbf 读取逻辑生效,设置后再读属性就是正常中文。如果某些 shp 其实是 UTF-8 编码,则把变量值改成 UTF-8 再跑。最稳的做法是在配置里增加编码字段,批量时如果发现乱码文件,单独调整重跑。
5.2 整数变浮点:面积字段变成 1987.3000000001
现象:dbf 里面积字段值是 1987.3,但 TXT 里输出的是 1987.3000000001,小数位长出一大截。
原因:dbf 的数值字段被 OGR 映射成 OFTReal 浮点型,直接 str() 转换时 Python 把底层二进制浮点完整吐出来,自然带长尾。
解决:按字段类型分支处理,浮点型用 f"{value:.2f}" 或 f"{value:.3f}" 格式化,不要在最后的拼接阶段统一转字符串。日期字段同理,单独用切片取前 10 位。建议把第 4 章里的 format_field 函数直接复用,所有字段过一遍类型判断。
5.3 坐标偏移:TXT 里的图形位置整体漂移几百米
现象:转换出来的坐标点用 GIS 软件验证,位置和原始 shp 对不上,整体偏移几百米甚至几公里。
原因:shp 是投影坐标系(如 CGCS2000 3 度带高斯投影),TXT 按无投影的原始坐标输出,而查看工具按经纬度渲染,导致错位。或者反过来,shp 是经纬度但脚本强加了投影转换。
解决:转换前必须读 .prj 确认坐标系,并在脚本里显式指定源坐标系和目标坐标系。用 EPSG 编号管理坐标参考,转换函数里明确 src_epsg 和 tgt_epsg 两个参数,不写默认值。批量跑完后抽 3 个文件用 ogrinfo 验证坐标范围,经纬度应该在 73 到 135 之间,投影坐标应该带带号特征。
5.4 批量中断不报错:500 个文件转到第 480 个突然停住
现象:脚本跑到中途退出,没有抛出异常,也没有生成日志,后面的文件全部没转换。
原因:某个 shp 文件读取时触发 OGR 内部错误,但错误类型没被捕获,程序直接终止。我在实践中遇到过几何拓扑损坏的 shp、字段类型异常的文件,都会导致这种问题。
解决:脚本的主循环加 try-except,每次转换结果写日志文件,记录文件名、行数、耗时和错误信息。遇到异常文件记录失败原因并继续处理下一个。转换结束后统计成功数和失败数,单独输出失败清单。单个文件出错不应该中断整个批次,这是批量工具的基本素养。
for name in files: try: count = shp_to_txt(shp_path, txt_path, ...) log.write(f"{name}\tOK\t{count}\n") except Exception as e: log.write(f"{name}\tFAIL\t{str(e)}\n")这段代码就是断点续转的最小实现。日志文件先记录 OK/FAIL 状态,跑完看日志就知道哪个文件有问题。加上日志之后,批量处理就不再是黑匣子,出了问题能直接定位到文件级。
5.5 大文件内存炸:转换 2GB 的 shp 时 Python 直接卡死
现象:脚本运行到某个大文件时内存占用飙升,系统变卡,最终进程被杀掉。
原因:脚本一次性把整个图层读入内存处理,或者用 pyshp 的 shapes() 方法全量加载,数据量大时内存消耗成倍增长。
解决:改用 OGR 的流式遍历方式,for feat in lyr 本身就是逐条读取,不会全量驻留内存。如果是属性字段的列表推导式一次性构建所有行,改成逐行写入文件。另外可以按要素数量分片输出,每 10 万条写一个分片文件,最后再合并。对大文件,转换逻辑要在独立进程里跑,避免影响其他任务。
6. 转换结果验证与自动化:行数比对、CRC 校验与增量转换
6.1 行数比对:TX T行数必须等于 shp 要素数
转换完成后第一件验证工作是行数比对。用 ogrinfo 统计源 shp 的要素数,然后统计 TXT 的非空行数,两者相等才算基础合格。这一步可以手工做,但批量场景下必须写成自动化:遍历目录,对每个 shp 调 ogrinfo 获取要素数量,同时读取同名 TXT 的行数,两个数字不一致就标记异常。
ogrinfo -ro -al input.shp | grep "Feature Count" wc -l output.txt第一行输出要素总数,第二行输出 TXT 行数。比较时要注意 TXT 如果有表头要减一行,如果没有表头就直接对比。行数不一致的原因通常是空几何被跳过、或某些字段含换行符导致一行变成多行,处理方式是按字段值中的换行符替换为空格,保证一条要素一行。
6.2 CRC 校验与文件指纹:防止转换过程中内容被篡改
行数比对不过滤内容差异,字段值写错、顺序错乱行数完全一致也可能发生。二次保险是给转换结果生成校验和:转换完成后对每个 TXT 计算 MD5 或 CRC32,输出一个校验清单文件,交付时连同清单一起发。接收方重新计算比对,能确认文件在传递过程中没有被改动。
import hashlib def file_md5(path): h = hashlib.md5() with open(path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): h.update(chunk) return h.hexdigest()这段函数就是生成校验值的最小代码,适合对输出文件做指纹管理。校验清单同时记录文件路径、行数、MD5 值,转换工具跑完自动生成,方便交接。
6.3 定时批量与增量转换
日常项目里 shp 数据经常更新,每次全量转换太浪费。给脚本加上增量处理:记录上次转换时间,只处理 mtime 更新过的 shp。Windows 上用计划任务定时跑,其实就是一条批处理命令调用 python 脚本,加上日志输出。连续跑几次后,整个目录的 TXT 始终和最新 shp 对齐。
python convert_batch.py --src D:/shp_input --out D:/txt_output --inc脚本收到 --inc 参数后读取上次处理时间戳,跳过未修改的文件。这样一个小时内能处理完上千个文件的日常更新。我自己的习惯是把转换、行数比对、校验值生成串成一条完整流程,跑完直接输出三个清单:成功清单、失败清单、校验清单。
说到教训,我吃过一次亏:项目交付时少了一行数据,验收时对方导入系统直接报错,返工了一整天。从那以后,我每次转换完都强制走一遍行数比对和 CRC 校验,再懒也不跳过。这个习惯帮我在后续项目里避免了好几次返工。希望帮到你——工具和脚本都打包好了,拿回去改改配置就能跑。
本文还有配套的精品资源,点击获取