批量SHP转TXT工具:从数据模型到实战避坑指南 简介面向自然资源、测绘与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 SEPARATORTAB \ -lco GEOMETRYAS_XY \ -t_srs EPSG:4326 \ -select code,name,area \ -where code LIKE 11%这里 -lco GEOMETRYAS_XY 表示输出几何的 X、Y 坐标列SEPARATORTAB 指定 Tab 作为分隔符-t_srs EPSG:4326 表示转成 WGS84 经纬度-select 控制输出字段-where 做属性过滤。这样一条命令就能把面积字段、名称字段和坐标列一起输出省去中间环节。但注意 GEOMETRYAS_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, encodingutf-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_ENCODINGGBK。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_ENCODINGGBK。这个变量对 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 校验再懒也不跳过。这个习惯帮我在后续项目里避免了好几次返工。希望帮到你——工具和脚本都打包好了拿回去改改配置就能跑。本文还有配套的精品资源点击获取