ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

txt转ASC格式转换:用Start_Program脚本实现文本与栅格数据互转

txt转ASC格式转换:用Start_Program脚本实现文本与栅格数据互转 简介面向CAN总线测试、嵌入式开发与车载电子工程师的一份Python工具脚本解决Kvaser设备生成的txt格式CAN日志无法直接导入CANoe做深度分析的问题。Kvaser与CANoe是CAN总线开发中常见的硬件与软件组合txt偏重可读性ASC则含时间戳等结构化报文信息以往靠人工整理效率低且易出错这份脚本将转换过程完整自动化。压缩包为rar格式内部只有1个py源码文件整体约2KB代码精简、依赖少既方便现场部署也适合二次修改已有965人学习下载。脚本核心功能包括读取纯文本txt日志、按分隔符解析时间戳与CAN报文数据、生成符合CANoe导入规范的ASC结构并自动完成毫秒到微秒的时间换算也处理了文件不存在、格式错误等异常。对于需要批量转换历史日志并在CANoe中做总线仿真与故障复现的工程师来说这个工具能明显减少手工整理工作量适合汽车电子、工业控制等场景入门者也可借此理解TXT与ASC两种格式的差异。1. Start_Program 格式转换 txt 与 ASC一个入口脚本解决的不只是加个头如果你手头有一批 txt 原始数据要喂给模型或者挂到 GIS 里看空间分布你会发现模型和地图软件对同一份数据的表达方式完全不一样。最常见的诉求就是把普通 txt 文本转成带文件头的 ASCASCII Grid栅格或者反过来把 ASC 还原成 txt这时候一个叫 Start_Program 的格式转换入口脚本就能省掉大量重复劳动。它不是某个商业软件的隐藏功能而是一个参数化的命令行工具输入文件路径、坐标和像元大小输出能被 ArcGIS、QGIS、GDAL 直接识别的 ASC 文件。这个方向适合经常处理 DEM、气温网格、浓度栅格以及从 shp 导出属性表又需要网格化的数据工程师。下面的内容全部按可复现的命令行流程来写。2. 认识 ASC 文件txt 与 ASCII Grid 的边界不在扩展名ASC 本质上是一份“有规矩的 txt”。很多人把 .txt 直接改成 .asc 就以为转换完成结果在 GIS 里打开直接报错因为软件不知道如何把一长串数值排列成二维矩阵。ASC 的规矩是文件头必须有 6 行关键信息缺一行或者顺序不对数据就变成一堆没有空间含义的数字。这个章节把 ASC 格式拆开讲清楚顺便把热词里常出现的“asc 和 desc 以及 null”一次说透ASC 是格式名和排序方向没有关系但数据从 txt 进入 ASC 前的排列顺序确实会直接影响输出结果null 则对应文件头里的 NODATA_value。2.1 六行头字段与数据区ncols、nrows、xllcorner、NODATA_value先看一份最小可用的 ASC 文件内容共 8 行前 6 行是头后面是 2 行数据ncols 4 nrows 2 xllcorner 452000.0 yllcorner 3540000.0 cellsize 10.0 NODATA_value -9999.0 12.3 11.8 10.5 -9999 9.2 8.7 7.1 6.6我把这 6 行头的含义整理成一个参数表方便写转换脚本时对照头字段含义常见取值注意事项ncols每行数值个数即栅格列数整数必须与数据区每行的 token 数一致nrows数据行数即栅格行数整数必须与数据区实际行数一致xllcorner栅格左下角 X 坐标投影坐标或经度也可写成 xllcenter二选一yllcorner栅格左下角 Y 坐标投影坐标或纬度与 xllcorner 配对出现cellsize像元大小正数要和坐标单位一致米还是度NODATA_value无数据标记值通常为 -9999 或 -999别用 0除非 0 真的不参与计算文件头之后就是数据区nrows 行每行 ncols 个数值数值之间用空格分隔换行符按你的系统习惯即可。ArcGIS 和 GDAL 对字段名大小写不敏感但对顺序敏感所以我建议永远按 ncols、nrows、xllcorner、yllcorner、cellsize、NODATA_value 的固定顺序输出这样所有工具都能认。这里有个很容易混淆的概念如果你在一个 txt 里看到类似 “asc 和 desc 以及 null” 的描述asc 可能指 ASCII 文件也可能指排序方向。在格式转换场景里文件头里的 ASC 是 ASCII Grid 格式与数据按 asc 还是 desc 排序没有任何关系。null 则用来表示空缺值写到 ASC 里就变成 NODATA_value。搞清楚这一点后面调参数时才不会把排序逻辑和无效值逻辑搅在一起。2.2 txt 转 ASC 的三个数据来源网格文本、坐标点表、shp 导出的表txt 转 ASC不是所有 txt 都能直接加个文件头就完事。我一般把输入数据分成三类处理方式完全不同。第一类是规则网格文本这是最常见也最简单的输入。txt 里本身就是 nrows 行、ncols 列的数值矩阵比如模型导出的浓度矩阵、经过处理的降水网格。这类数据转换时只需要读矩阵、补文件头几乎不涉及重排逻辑。很多用 Python 处理数据的场景里从 txt 导入后在程序里按行列调用几列最后写回一个 txt本质上就是在做这件事。第二类是坐标点表常见格式是每行三列x、y、value。这类 txt 可能来自 shp 转 txt或者 GPS 采集点、气象站点数据。点表不能直接加文件头必须先网格化也就是把散点映射到规则网格上。最简单的方法是最近邻分配先确定 xllcorner、yllcorner 和 cellsize然后反算每个点落在哪个行列。行列换算公式是col int((x - xllcorner) / cellsize) row int((yllcorner nrows * cellsize - y) / cellsize)注意这个公式假定 ASC 数据区第一行是北侧最大 y 值所以公式里用 yllcorner 加上整个栅格高度再减去当前 y得到的 row 才是从上往下数的行号。如果点落在栅格外直接丢弃或记录到日志里否则后面写 ASC 时数组下标会越界。第三类是从已有 DEM 或 ASC 反推出来的 txt。比如你在 GIS 里把 ASC 导出成文本再用脚本做裁剪、重采样最后还要写回 ASC。这类 txt 往往保留了 ASC 的矩阵结构但文件头已经被剥掉。处理时按第一类方式读入即可但必须确认原始坐标系、像元大小和 NODATA 约定没有被中间步骤破坏。3. 用 start_program.py 跑通 txt 到 ASC 的最小转换清楚了 ASC 的结构之后剩下的事就是写一个入口脚本名字就叫 start_program.py。我一般把 Start_Program 理解为工作目录里的启动文件它负责读入 txt、按参数写 ASC并且支持反向转换。下面的代码只依赖 Python 标准库不需要安装第三方包在 Python 3.8 以上环境都能直接跑。3.1 读取 txt 网格过滤中文与空行按列构造矩阵先实现读取部分。这段代码要解决两个问题一是 txt 里可能存在空行、注释行、中文单位等垃圾行二是数据行之间列数可能不一致。读取时用正则匹配数值 token匹配不到数值的行直接跳过或报警匹配到的行必须校验列数。#!/usr/bin/env python3 # start_program.py —— txt 与 ASC 互转的入口脚本 import argparse import re from pathlib import Path NUMBER_RE re.compile(r^[-]?(\d\.?\d*|\.\d)([eE][-]?\d)?$) def read_txt_to_rows(path, delimiterNone): 读取 txt 网格数据跳过空行、注释行和无法解析的行。 返回二维列表 rows每个子列表是一行数值。 delimiter 为 None 时按连续空白字符切分。 rows [] with open(path, r, encodingutf-8, errorsreplace) as f: for raw_line in f: line raw_line.strip() if not line or line.startswith(#): continue tokens line.split(delimiter) if delimiter else line.split() nums [t for t in tokens if NUMBER_RE.match(t)] if not nums: print(f[warn] 跳过无法解析的行: {line[:60]}) continue rows.append([float(n) for n in nums]) if not rows: raise ValueError(f文件 {path} 中没有解析到任何数值行) ncols len(rows[0]) for idx, row in enumerate(rows, 1): if len(row) ! ncols: raise ValueError( f第 {idx} 行列数为 {len(row)}与首行列数 {ncols} 不一致 ) return rows这段代码里最关键的是 NUMBER_RE 正则它允许整数、小数和科学计数法比如 1e-05、-9999.0 都能被正确识别。errorsreplace 用来兜底编码问题遇到 GBK 和 UTF-8 混用的文件时非法字节会被替换成占位符而不会导致程序崩溃。如果你发现某些行里混了中文比如“12.3 11.8 单位”这里的做法是把非数值 token 过滤掉只保留能转成 float 的部分。这是处理“txt 文件清除中文怎么办”这类问题最省事的方案。3.2 写出 ASC六行头与数据区的拼接顺序读取完成后写 ASC 就是机械活先写文件头再逐行写数据区。文件头里的 nrows 和 ncols 从 rows 的形状推导避免手工填写导致矩阵维度对不上。def write_asc(path, rows, xllcorner, yllcorner, cellsize, nodata): 将二维数值列表写成 ASC 文件。 xllcorner 和 yllcorner 是栅格左下角坐标 cellsize 为像元大小nodata 为无数据标记值。 nrows len(rows) ncols len(rows[0]) with open(path, w, encodingascii) as f: f.write(fncols {ncols}\n) f.write(fnrows {nrows}\n) f.write(fxllcorner {xllcorner}\n) f.write(fyllcorner {yllcorner}\n) f.write(fcellsize {cellsize}\n) f.write(fNODATA_value {nodata}\n) for row in rows: f.write( .join(str(v) for v in row) \n)注意写文件头和写数据区用的是同一个文件句柄顺序就是文件头在前、数据区在后。所有数值统一用 str() 转换默认保留 Python 浮点的最短表示形式不会写出“12.300000000000001”这种长尾。如果你希望固定保留两位小数可以把 str(v) 改成 f{v:.2f}但要注意这让随后读入 ASC 的软件按字符串解析并不会损失更多精度。3.3 反向转换 ASC 到 txt解析头文件并保留小数点反向转换的思路是把 ASC 前 6 行头解析成字典数据区按行写出为普通 txt。这里有一个小坑ASC 头字段名不一定是小写GDAL 导出的文件可能写成 NODATA_value 或 NODATA_VALUE解析时必须统一转成小写再存。def read_asc_to_rows(path): 读取 ASC 文件返回 (header_dict, data_rows)。 header_dict 的 key 全部转为小写方便后续统一处理。 header {} data_rows [] with open(path, r, encodingutf-8) as f: for _ in range(6): line f.readline().strip() parts line.split(None, 1) if len(parts) ! 2: raise ValueError(fASC 文件头格式错误: {line}) header[parts[0].lower()] parts[1] for line in f: line line.strip() if not line: continue data_rows.append([float(v) for v in line.split()]) return header, data_rows def write_txt(path, data_rows, delimiter\t): 将二维数值列表写成 txt默认用 tab 分隔。 with open(path, w, encodingutf-8) as f: for row in data_rows: f.write(delimiter.join(str(v) for v in row) \n)反向转换时不需要关心 xllcorner 是 center 还是 corner因为这些信息已经固化在文件头里你只需要原样保留或者按后续需求重新组织 txt 的列顺序。常见场景是把 ASC 转成 CSV 表格喂给 Pandas这时可以把 delimiter 改成 ,, 并配合表头行一起输出。3.4 入口函数与命令行参数入口函数用 argparse 接收参数核心参数包括输入文件、输出文件、转换模式、坐标和像元大小。这里给出完整调用方式python start_program.py --mode txt2asc \ --input grid.txt --output grid.asc \ --xllcorner 452000 --yllcorner 3540000 \ --cellsize 10 --nodata -9999参数说明如下参数默认值含义--modetxt2asctxt2asc 或 asc2txt--input必填输入文件路径--output必填输出文件路径--xllcorner0.0栅格左下角 X--yllcorner0.0栅格左下角 Y--cellsize1.0像元大小--nodata-9999.0无数据标记值--delimiterNonetxt 输入或输出分隔符None 表示空格/tab 自适应这里有一个设计上的选择入口函数永远要求显式传入 xllcorner、yllcorner 和 cellsize即使默认值是 0 和 1。这样做的目的是避免生成一个没有地理参考的 ASC因为坐标信息一旦错了后续所有空间分析都会跟着错。如果确实不需要地理参考保留默认值即可但你自己要清楚这份 ASC 是“行列号有意义、坐标无意义”的中间文件。4. 参数与语义处理cellsize、corner/center、asc/desc 与 null 的取舍txt 转 ASC 不是把数值填进模板就完事坐标原点的写法、行顺序的方向、无效值的统一这三个点最容易让输出结果“看起来对实际上偏”。下面分别说明。4.1 xllcorner 与 xllcenter半个像元的玄学与换算ASC 文件头里坐标原点的字段名有两种合法写法xllcorner 表示左下角像元的左下角坐标xllcenter 表示左下角像元的中心坐标。很多模型导出的 txt 里给的是网格中心点坐标比如 WRF、CMAQ 这类大气模型输出网格时习惯用中心点定位而 GIS 栅格最常用的方式是 corner 定位。如果你不换算直接把中心点填进 xllcorner整个栅格会向西北方向偏移半个像元叠加到底图上时边界永远对不上。换算公式很简单xllcorner xcenter - cellsize / 2 yllcorner ycenter - cellsize / 2注意这里默认 y 轴向上栅格没有旋转。如果你的数据涉及投影坐标且 y 方向是北方向公式直接用。如果涉及经纬度cellsize 单位是度公式不变但要注意同一像元在经度和纬度方向上的实际地面距离不同栅格不会发生形变因为每个像元都是等经度、等纬度。实际写脚本时我习惯在参数里加一个 --origin-type 开关允许用户显式声明输入坐标是 corner 还是 centerparser.add_argument(--origin-type, choices[corner, center], defaultcorner)当 origin-type 为 center 时入口函数内部自动把传进来的 xllcorner、yllcorner 当作中心点减去半个 cellsize 再写入文件头。这样一个开关就能避免用户拿着模型输出坐标直接填 corner 的翻车操作。4.2 进 ASC 前处理 asc/desc 排序与 null 值三件小事第一件是行顺序。ASC 数据区严格遵循“从西北角开始逐行向南”的排列方式也就是第一行是 y 最大的那一行最后一行是 y 最小的那一行。如果你的 txt 是按 y 升序排列的也就是从南往北一行一行写直接转成 ASC 后地图上的栅格会上下颠倒看起来像镜像翻转。解决办法是在写入前判断一下数据源语义通常带坐标的 txt 每行会有 y 值按 y 从大到小重排不带坐标的纯矩阵文本只能靠你确认来源约定。这也是热词里“asc 和 desc”在转换场景的真正含义如果你把 txt 按 desc 排序的数据交给 ASC方向就对了如果是 asc 排序就要先逆序。我一般不建议在入口脚本里做强反转因为方向错了往往是上游数据本身的问题最好在上游修正。第二件是 null 的统一。txt 里的缺失值表示方式五花八门空字段、NaN、NULL、-9999、-999甚至有人用字符串 None。进入 ASC 之前必须统一成一个 NODATA_value否则文件头写了 -9999数据区里却混着 NaNGIS 加载时会把 NaN 当异常值处理。常见做法是在读取阶段加一个无效值映射表NULL_ALIASES {nan: None, null: None, none: None, -9999: -9999}然后对每个 token 做归一化先把字符串转小写再看是否命中空值别名命中就替换成 nodata 参数指定的值。第三件是 0 值。很多真实栅格数据里0 是有效值比如降水为 0、浓度低于检出限为 0。这时候 NODATA_value 绝对不能设成 0否则真实 0 和空洞会被一并剔除。如果源数据里空洞本来就是 0你需要先区分“真 0”和“假 0”常见的做法是把探测不到的位置先标记为 -9999再做后续统计。这一步没有统一答案但你应该在转换前先确认好语义而不是让脚本替你猜。5. 常见问题排查txt 转 ASC 最容易翻车的 5 个位置5.1 用 Excel 打开并另存过的 txt编码乱了导致解析失败现象txt 原本是 UTF-8 编码中间经过 Excel 打开再另存变成 GBK 或带 BOM 的 UTF-8脚本读取时出现乱码数值行被跳过最终输出 ASC 只有文件头没有数据。原因Excel 在中文环境下默认按 GBK 处理文本另存时还可能写入 BOM 头。Python 用 utf-8 读取 GBK 文件时遇到中文字节会抛 UnicodeDecodeError即使用了 errorsreplace也可能把数字附近的中文单位替换成占位符导致整行跳过。解决读取 txt 时先尝试 utf-8-sig失败再尝试 gb18030或者直接加 errorsreplace。更可靠的方案是要求上游不要用 Excel 改 txt统一用 VS Code、Notepad 或 Python 脚本处理。如果你必须接收 Excel 导出的文件建议先用十六进制查看器确认前三个字节是不是 EF BB BF。5.2 数据行里混入中文单位或描述词“txt 文件清除中文怎么办”现象txt 数据行末尾带了“米”“ppm”“正常”等中文描述读入后 float() 转换失败程序把整行跳过导致 nrows 变少输出 ASC 与原始范围不符。原因读取逻辑里把每个 token 都当成数值解析遇到非数值 token 就抛异常整行跳过虽然格式上正确但丢数据。解决保留数值 token、忽略非数值 token而不是跳过整行。上面 read_txt_to_rows 里已经用 NUMBER_RE 做了过滤但要注意过滤后如果一行里只剩下 2 个数值而首行是 3 个列数校验会报错。更好的做法是明确你的 txt 中哪些列是有效列把中文所在列直接排除。如果你只是想快速清除中文可以写一个单独的正则替换re.sub(r[^\d.-eE\s], , line)再用 split 切分。5.3 NODATA 值被写成 0后续统计偏低现象转换后的 ASC 在 GIS 里显示正常但用 zonal statistics 或模型读取时所有云覆盖区域都参与了计算平均值被拉低。原因源 txt 里的空洞本来就是 0写入 ASC 时没有统一替换成 NODATA_value文件头写了 -9999但数据区仍然是 0。解决转换前先扫描一遍数据把所有等于 0 且来源为空洞的值替换成指定的 nodata。这里最怕的是空洞 0 和真实 0 混在一起所以需要在数据源头标记。如果数据源已经用 -9999 表示空洞只需要把文件头写成 -9999 即可。这条血的教训是转换脚本不负责判断业务语义但必须提供替换机制让我能在命令行里指定 --null-aliases 参数把源文件里的指定字符串统一换成 NODATA_value。5.4 ncols 与实际列数不符GIS 打开后数据斜切现象ASC 文件在 QGIS 里能打开但图像呈斜线错位或者显示的行数与实际数据不匹配。原因txt 最后一行可能有一个多余的空行或者某行末尾多了一个分隔符导致 split 后多出一个空字符串。如果这个空字符串被转换成 float 失败并跳过该行就少一列如果没被跳过列数就和前面不一致。解决读取阶段做列数校验这是最有效的手段。我通常在写 ASC 前先断言 len(row) ncols一旦发现不一致就终止程序并打印行号。不要尝试“自动修复列数”因为修复策略可能掩盖上游问题。另外如果你的 txt 是 CSV 格式且用逗号分隔记得把 delimiter 参数显式指定为逗号否则按空格切分会把整行当成一个 token。5.5 xllcorner 与 xllcenter 混用栅格整体平移半个像元现象转换后的 ASC 与底图叠加时地物轮廓整体错位偏移量约为 cellsize 的一半放大后尤其明显。原因源 txt 的坐标是网格中心点坐标但写入 ASC 文件头时用了 xllcorner 字段没有减去半个 cellsize。解决接到一个 txt 时先问清楚坐标是角点还是中心点。如果对方也不清楚可以用两个已知地物点做交叉验证。上面 4.1 节的 --origin-type 开关就是为此设计的设为 center 后脚本自动换算从源头杜绝手算偏移。6. 不是转完就交差用自检与批处理把转换入口变成日常工具每次转换完成后我建议至少做两步验证。第一步是直接看文件头确认 ncols、nrows 和坐标没有写成 0head -6 output.asc第二步是用 GDAL 读回 ASC检查栅格大小、仿射变换参数和 NODATA 值是否和预期一致python - PY from osgeo import gdal ds gdal.Open(output.asc) gt ds.GetGeoTransform() print(size:, ds.RasterXSize, ds.RasterYSize) print(origin:, gt[0], gt[3]) print(cellsize:, gt[1], gt[5]) print(nodata:, ds.GetRasterBand(1).GetNoDataValue()) PY这段脚本会打印栅格实际的地理变换参数。origin 应该等于你的 xllcorner 和 yllcornercellsize 应该是正数或负数的像元大小nodata 必须是你指定的标记值。如果 origin 和你预期差了一半 cellsize说明 corner/center 又搞混了如果 nodata 是 None说明文件头里的 NODATA_value 没被识别。验证通过后就可以把这个入口脚本接到批处理流程里。常见做法是写一个简单的 shell 循环遍历目录下所有 txt 文件逐个转成 ASCfor f in ./input_dir/*.txt; do python start_program.py --mode txt2asc \ --input $f --output ./output_dir/$(basename $f .txt).asc \ --xllcorner 452000 --yllcorner 3540000 \ --cellsize 10 --nodata -9999 done如果你手里的 txt 是从 shp 导出的点表先用 ogr2ogr 把属性表导成 csv 或 txt再做网格化最后交给 start_program.py。直接拿点表转 ASC 是没有意义的因为散点数据没有行列结构必须先落到网格上。这个入口脚本的价值就是把“读文本、写头、控制坐标”这些琐事固定下来让我每次处理新数据时只需要检查业务语义不需要重新调试格式。有一次我因为偷懒没做 NODATA 检查输出的栅格在后续插值里把空洞当真实极大值参与计算整片结果出现异常高值排查了一整个下午。后来我给自己定了个规矩任何 txt 转 ASC转完必须跑一遍验证脚本nodata 和 origin 都要打印到屏幕上确认。希望帮到你。本文还有配套的精品资源点击获取
返回列表