惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
国土TXT格式
我才是银古 · 2026-06-21 · via 博客园 - 我才是银古

国土TXT格式

概述

国土TXT格式是中国自然资源部门使用的地块坐标文本格式,主要用于土地调查、不动产登记等业务场景。理解并正确处理该格式是从事国土相关GIS开发的必备技能。

文件格式

基本结构

国土TXT文件由多个部分组成:

[扩展信息](可选)
...

[属性描述]
格式版本号=
数据产生单位=自然资源部
数据产生日期=2024-01-01
坐标系=2000国家大地坐标系
几度分带=3
投影类型=高斯克吕格
计量单位=米
带号=38
精度=0.01
转换参数=0,0,0,0,0,0,0

[地块坐标]
13,1234.56,地块001,土地名称,面,图幅号,住宅用地,0101,,1,B,C,备注,@
J1,1,3456789.12,123456.78
J2,1,3456790.12,123457.78
...
13,1234.56,地块002,土地名称,面,...,@
...

属性描述说明

字段 说明 示例
格式版本号 文件格式版本 空或版本号
数据产生单位 数据生产单位名称 自然资源部
数据产生日期 数据生产日期 2024-01-01
坐标系 使用的坐标系 2000国家大地坐标系
几度分带 3度或6度分带 3
投影类型 投影方式 高斯克吕格
计量单位 坐标单位
带号 投影带号 38
精度 坐标精度 0.01
转换参数 七参数转换值 0,0,0,0,0,0,0

地块数据结构

地块数据行格式:属性值1,属性值2,...,@

默认字段顺序:

  1. JZDS - 界址点数
  2. DKMJ - 地块面积(公顷)
  3. DKBH - 地块编号
  4. DKMC - 地块名称
  5. JLTXSX - 记录图形属性(面/线/点)
  6. TFH - 图幅号
  7. DKYT - 地块用途
  8. DLBM - 地类编码
  9. TBLX - 图斑类型
  10. DL - 地类
  11. GZQ - 改造前质量等别
  12. GZH - 改造后质量等别
  13. BZ - 备注

坐标点格式

点号,圈号,Y坐标,X坐标

  • 点号:点的序号,J1、J2或1、2等
  • 圈号:环的序号,1为外环,2及以上为内环(岛洞)
  • Y坐标:北方向坐标(通常是大数)
  • X坐标:东方向坐标(带号+坐标值)

注意:国土TXT中Y/X与常见GIS坐标系的X/Y是反的!

数据模型

TxtLayer

/**
 * TXT图层
 */
@Data
public class TxtLayer implements Serializable {
    private String name;          // 图层名称
    private String gsbbh;         // 格式版本号
    private String sjscdw;        // 数据产生单位
    private String sjscrq;        // 数据产生日期
    private String zbx;           // 坐标系
    private String jdfd;          // 几度分带
    private String tylx;          // 投影类型
    private String jldw;          // 计量单位
    private String dh;            // 带号
    private String jd;            // 精度
    private String zhcs;          // 转换参数
    private List<TxtExtInfo> exts;      // 扩展信息
    private List<TxtFeature> features;  // 要素列表
}

TxtFeature

/**
 * TXT要素
 */
@Data
public class TxtFeature implements Serializable {
    private List<String> values;              // 属性值列表
    private List<TxtCoordinate> coordinates;  // 坐标点列表
}

TxtCoordinate

/**
 * TXT坐标点
 */
@Data
public class TxtCoordinate implements Serializable {
    private String dh;    // 点号
    private Integer qh;   // 圈号
    private Double y;     // Y坐标
    private Double x;     // X坐标
}

文件读取

解析TXT文件

/**
 * 加载TXT图层
 */
public static TxtLayer loadTxt(String txtPath) {
    File file = new java.io.File(txtPath);
    Charset encoding = EncodingUtil.getFileEncoding(file);
    
    // 读取并清理空行
    List<String> txtLines = new ArrayList<>();
    for (String line : FileUtil.readLines(file, encoding)) {
        String trim = line.trim();
        if (CharSequenceUtil.isBlank(trim)) continue;
        
        // 处理BOM头
        if (trim.startsWith("\uFEFF")) {
            trim = trim.substring(1);
        }
        txtLines.add(trim);
    }
    
    // 检查必要部分
    if (txtLines.stream().noneMatch("[属性描述]"::equals)) {
        throw new RuntimeException("缺少[属性描述]");
    }
    if (txtLines.stream().noneMatch("[地块坐标]"::equals)) {
        throw new RuntimeException("缺少[地块坐标]");
    }
    
    // 按模块分组
    LinkedHashMap<String, List<String>> txtMap = new LinkedHashMap<>();
    String currKey = null;
    for (String txtLine : txtLines) {
        if (txtLine.startsWith("[") && txtLine.endsWith("]")) {
            currKey = txtLine;
            txtMap.put(txtLine, new ArrayList<>());
        } else {
            txtMap.get(currKey).add(txtLine);
        }
    }
    
    // 解析各模块
    TxtLayer txtLayer = new TxtLayer();
    txtLayer.setName(FileUtil.getName(txtPath));
    
    parseAttributeDescription(txtLayer, txtMap.get("[属性描述]"));
    parseCoordinates(txtLayer, txtMap.get("[地块坐标]"));
    parseExtInfo(txtLayer, txtMap);
    
    return txtLayer;
}

解析属性描述

private static void parseAttributeDescription(TxtLayer txtLayer, List<String> lines) {
    for (String line : lines) {
        String[] split = line.split("=");
        if (split.length == 0 || split.length > 2) {
            throw new RuntimeException("txt文件格式不正确");
        }
        
        String key = split[0].trim();
        String value = split.length == 2 ? split[1].trim() : null;
        
        switch (key) {
            case "格式版本号": txtLayer.setGsbbh(value); break;
            case "数据产生单位": txtLayer.setSjscdw(value); break;
            case "数据产生日期": txtLayer.setSjscrq(value); break;
            case "坐标系": txtLayer.setZbx(value); break;
            case "几度分带": txtLayer.setJdfd(value); break;
            case "投影类型": txtLayer.setTylx(value); break;
            case "计量单位": txtLayer.setJldw(value); break;
            case "带号": txtLayer.setDh(value); break;
            case "精度": txtLayer.setJd(value); break;
            case "转换参数": txtLayer.setZhcs(value); break;
            default:
                throw new RuntimeException("txt文件格式不正确,未知字段:" + key);
        }
    }
}

解析坐标数据

private static void parseCoordinates(TxtLayer txtLayer, List<String> lines) {
    List<TxtFeature> features = new ArrayList<>();
    
    // 按地块分组(以@结尾的行是地块属性行)
    LinkedHashMap<String, List<String>> zbMap = new LinkedHashMap<>();
    String currZbKey = null;
    for (String line : lines) {
        if (line.endsWith("@")) {
            currZbKey = line;
            zbMap.put(currZbKey, new ArrayList<>());
        } else {
            zbMap.get(currZbKey).add(line);
        }
    }
    
    // 解析每个地块
    zbMap.forEach((zbKey, zbLines) -> {
        TxtFeature txtFeature = new TxtFeature();
        
        // 解析属性值
        List<String> fields = CharSequenceUtil.split(zbKey, ",");
        List<String> values = fields.subList(0, fields.size() - 1);
        txtFeature.setValues(values);
        
        // 解析坐标点
        List<TxtCoordinate> coordinates = new ArrayList<>();
        zbLines.forEach(zbLine -> {
            List<String> zbLineList = new ArrayList<>(CharSequenceUtil.split(zbLine, ","));
            if (zbLineList.size() != 4) {
                throw new RuntimeException("txt坐标点格式不正确:" + zbLine);
            }
            
            TxtCoordinate txtCoordinate = new TxtCoordinate();
            txtCoordinate.setDh(zbLineList.get(0));
            txtCoordinate.setQh(NumberUtil.parseInt(zbLineList.get(1)));
            txtCoordinate.setY(NumberUtil.parseDouble(zbLineList.get(2)));
            txtCoordinate.setX(NumberUtil.parseDouble(zbLineList.get(3)));
            coordinates.add(txtCoordinate);
        });
        
        txtFeature.setCoordinates(coordinates);
        features.add(txtFeature);
    });
    
    txtLayer.setFeatures(features);
}

TXT转WktLayer

/**
 * TxtLayer转WktLayer
 */
public static WktLayer parseTxtLayerToWktLayer(TxtLayer txtLayer, List<WktField> wktFields) {
    // 默认字段定义
    if (wktFields == null) {
        wktFields = getDefaultFields();
    }
    
    WktLayer wktLayer = new WktLayer();
    wktLayer.setYwName("TXT");
    wktLayer.setZwName(txtLayer.getName());
    
    // 根据带号确定坐标系
    int wkid = 4488 + NumberUtil.parseInt(txtLayer.getDh());
    wktLayer.setWkid(wkid);
    wktLayer.setTolerance(CrsUtil.getTolerance(wkid));
    wktLayer.setGeometryType(GeometryType.MULTIPOLYGON);
    wktLayer.setFields(wktFields);
    
    // 转换要素
    List<WktFeature> wktFeatures = new ArrayList<>();
    for (TxtFeature txtFeature : txtLayer.getFeatures()) {
        WktFeature wktFeature = convertFeature(txtFeature, wktFields, wkid);
        wktFeatures.add(wktFeature);
    }
    
    wktLayer.setFeatures(wktFeatures);
    wktLayer.check();
    return wktLayer;
}

/**
 * 转换单个要素
 */
private static WktFeature convertFeature(TxtFeature txtFeature, 
        List<WktField> wktFields, int wkid) {
    // 按圈号分组坐标点
    LinkedHashMap<Integer, List<Coordinate>> coordinateMap = new LinkedHashMap<>();
    for (TxtCoordinate zb : txtFeature.getCoordinates()) {
        int qh = zb.getQh();
        List<Coordinate> coordinates = coordinateMap.computeIfAbsent(qh, 
            k -> new ArrayList<>());
        // 注意:TXT中Y是北方向,X是东方向
        // JTS中X是东方向,Y是北方向
        coordinates.add(new Coordinate(zb.getX(), zb.getY()));
    }
    
    // 构建多边形
    GeometryFactory factory = new GeometryFactory(
        new PrecisionModel(PrecisionModel.FLOATING));
    LinearRing shell = null;
    LinearRing[] holes = null;
    if (coordinateMap.size() > 1) {
        holes = new LinearRing[coordinateMap.size() - 1];
    }
    
    int index = 0;
    for (Map.Entry<Integer, List<Coordinate>> entry : coordinateMap.entrySet()) {
        List<Coordinate> coordinates = entry.getValue();
        // 确保闭合
        if (!coordinates.get(0).equals2D(coordinates.get(coordinates.size() - 1))) {
            coordinates.add(coordinates.get(0));
        }
        
        LinearRing ring = factory.createLinearRing(
            ArrayUtil.toArray(coordinates, Coordinate.class));
        if (index == 0) {
            shell = ring;
        } else if (holes != null) {
            holes[index - 1] = ring;
        }
        index++;
    }
    
    Polygon polygon = factory.createPolygon(shell, holes);
    
    // 设置属性
    List<WktFieldValue> wktFieldValues = new ArrayList<>();
    for (int i = 0; i < txtFeature.getValues().size() && i < wktFields.size(); i++) {
        WktFieldValue fv = new WktFieldValue();
        fv.setField(wktFields.get(i));
        fv.setValue(txtFeature.getValues().get(i));
        wktFieldValues.add(fv);
    }
    
    WktFeature wktFeature = new WktFeature();
    wktFeature.setFieldValues(wktFieldValues);
    wktFeature.setWfId(IdUtil.simpleUUID());
    wktFeature.setWkt(ESRIGeometryUtil.simplify(polygon.toText(), wkid));
    
    return wktFeature;
}

WktLayer转TXT

/**
 * WktLayer转TxtLayer
 */
public static TxtLayer parseWktLayerToTxtLayer(WktLayer wktLayer, 
        TxtLayer msAndExtInfo, List<String> fieldNames, Integer dh) {
    
    TxtLayer txtLayer = new TxtLayer();
    txtLayer.setName(wktLayer.getZwName() != null ? 
        wktLayer.getZwName() : wktLayer.getYwName());
    
    // 设置属性描述(从模板或默认值)
    txtLayer.setGsbbh(getOrDefault(msAndExtInfo, TxtLayer::getGsbbh, ""));
    txtLayer.setSjscdw(getOrDefault(msAndExtInfo, TxtLayer::getSjscdw, "自然资源部"));
    txtLayer.setSjscrq(getOrDefault(msAndExtInfo, TxtLayer::getSjscrq, 
        DatePattern.NORM_DATE_FORMAT.format(DateUtil.date())));
    txtLayer.setZbx(getOrDefault(msAndExtInfo, TxtLayer::getZbx, "2000国家大地坐标系"));
    txtLayer.setJdfd(getOrDefault(msAndExtInfo, TxtLayer::getJdfd, "3"));
    txtLayer.setTylx(getOrDefault(msAndExtInfo, TxtLayer::getTylx, "高斯克吕格"));
    txtLayer.setJldw(getOrDefault(msAndExtInfo, TxtLayer::getJldw, "米"));
    txtLayer.setJd(getOrDefault(msAndExtInfo, TxtLayer::getJd, "0.01"));
    txtLayer.setZhcs(getOrDefault(msAndExtInfo, TxtLayer::getZhcs, "0,0,0,0,0,0,0"));
    
    // 默认字段
    if (fieldNames == null || fieldNames.isEmpty()) {
        fieldNames = getDefaultFieldNames();
    }
    
    // 确定坐标系
    Integer wkid = null;
    if (dh != null) {
        wkid = CrsUtil.getProjectedWkid(dh);
    }
    
    // 转换要素
    List<TxtFeature> txtFeatures = new ArrayList<>();
    for (WktFeature wktFeature : wktLayer.getFeatures()) {
        TxtFeature txtFeature = convertToTxtFeature(wktFeature, wktLayer, 
            fieldNames, wkid);
        txtFeatures.add(txtFeature);
        
        // 更新带号
        if (dh == null) {
            dh = CrsUtil.getDh(GeometryConverter.wkt2Geometry(wktFeature.getWkt()));
            wkid = CrsUtil.getProjectedWkid(dh);
        }
    }
    
    txtLayer.setFeatures(txtFeatures);
    txtLayer.setDh(String.valueOf(dh));
    
    return txtLayer;
}

文件写入

/**
 * 保存TXT图层
 */
public static void saveTxt(TxtLayer txtLayer, String txtPath) {
    List<String> txtLines = new ArrayList<>();
    
    // 写入扩展信息
    if (txtLayer.getExts() != null && !txtLayer.getExts().isEmpty()) {
        for (TxtExtInfo extInfo : txtLayer.getExts()) {
            txtLines.add("[" + extInfo.getName() + "]");
            for (Map.Entry<String, String> entry : extInfo.getFields().entrySet()) {
                txtLines.add(entry.getKey() + "=" + entry.getValue());
            }
        }
    }
    
    // 写入属性描述
    txtLines.add("[属性描述]");
    txtLines.add("格式版本号=" + txtLayer.getGsbbh());
    txtLines.add("数据产生单位=" + txtLayer.getSjscdw());
    txtLines.add("数据产生日期=" + txtLayer.getSjscrq());
    txtLines.add("坐标系=" + txtLayer.getZbx());
    txtLines.add("几度分带=" + txtLayer.getJdfd());
    txtLines.add("投影类型=" + txtLayer.getTylx());
    txtLines.add("计量单位=" + txtLayer.getJldw());
    txtLines.add("带号=" + txtLayer.getDh());
    txtLines.add("精度=" + txtLayer.getJd());
    txtLines.add("转换参数=" + txtLayer.getZhcs());
    
    // 写入地块坐标
    txtLines.add("[地块坐标]");
    for (TxtFeature txtFeature : txtLayer.getFeatures()) {
        // 属性行
        txtLines.add(CharSequenceUtil.join(",", txtFeature.getValues()) + ",@");
        // 坐标点
        for (TxtCoordinate coord : txtFeature.getCoordinates()) {
            txtLines.add(CharSequenceUtil.join(",", 
                coord.getDh(), coord.getQh(),
                NumUtil.getPlainString(coord.getY()),
                NumUtil.getPlainString(coord.getX())));
        }
    }
    
    FileUtil.writeLines(txtLines, txtPath, StandardCharsets.UTF_8);
}

实践案例

案例1:TXT转Shapefile

/**
 * 国土TXT转Shapefile
 */
public void txt2Shp(String txtPath, String shpPath) {
    // 读取TXT
    TxtLayer txtLayer = TxtLayerUtil.loadTxt(txtPath);
    
    // 转换为WktLayer
    WktLayer wktLayer = TxtLayerUtil.parseTxtLayerToWktLayer(txtLayer, null);
    
    // 写入Shapefile
    WktLayerConverter.toShapefile(wktLayer, shpPath, GisEngineType.GEOTOOLS);
}

案例2:Shapefile转TXT

/**
 * Shapefile转国土TXT
 */
public void shp2Txt(String shpPath, String txtPath, Integer targetDh) {
    // 读取Shapefile
    WktLayer wktLayer = WktLayerConverter.fromShapefile(
        shpPath, null, null, GisEngineType.GEOTOOLS);
    
    // 转换为TxtLayer
    TxtLayer txtLayer = TxtLayerUtil.parseWktLayerToTxtLayer(
        wktLayer, null, null, targetDh);
    
    // 保存TXT
    TxtLayerUtil.saveTxt(txtLayer, txtPath);
}

案例3:批量转换

/**
 * 批量TXT转Shapefile
 */
public void batchConvert(String inputDir, String outputDir) {
    File dir = new File(inputDir);
    File[] txtFiles = dir.listFiles((d, name) -> 
        name.toLowerCase().endsWith(".txt"));
    
    if (txtFiles == null) return;
    
    for (File txtFile : txtFiles) {
        try {
            String shpName = FileUtil.mainName(txtFile) + ".shp";
            String shpPath = outputDir + File.separator + shpName;
            
            txt2Shp(txtFile.getAbsolutePath(), shpPath);
            System.out.println("转换成功: " + txtFile.getName());
        } catch (Exception e) {
            System.err.println("转换失败: " + txtFile.getName() + " - " + e.getMessage());
        }
    }
}

案例4:TXT数据验证

/**
 * 验证TXT数据
 */
public List<String> validateTxt(String txtPath) {
    List<String> errors = new ArrayList<>();
    
    try {
        TxtLayer txtLayer = TxtLayerUtil.loadTxt(txtPath);
        WktLayer wktLayer = TxtLayerUtil.parseTxtLayerToWktLayer(txtLayer, null);
        
        for (WktFeature feature : wktLayer.getFeatures()) {
            Geometry geom = GeometryConverter.wkt2Geometry(feature.getWkt());
            
            // 检查几何有效性
            if (!geom.isValid()) {
                errors.add("地块 " + feature.getWfId() + " 几何无效");
            }
            
            // 检查面积
            double area = geom.getArea();
            if (area <= 0) {
                errors.add("地块 " + feature.getWfId() + " 面积为0或负数");
            }
        }
    } catch (Exception e) {
        errors.add("文件解析错误: " + e.getMessage());
    }
    
    return errors;
}

案例5:坐标系转换

/**
 * TXT坐标系转换
 */
public void transformTxt(String inputPath, String outputPath, 
        Integer sourceDh, Integer targetDh) {
    // 读取并转换为WktLayer
    TxtLayer txtLayer = TxtLayerUtil.loadTxt(inputPath);
    WktLayer wktLayer = TxtLayerUtil.parseTxtLayerToWktLayer(txtLayer, null);
    
    // 坐标转换
    int targetWkid = CrsUtil.getProjectedWkid(targetDh);
    wktLayer = CrsUtil.reproject(wktLayer, targetWkid);
    
    // 转回TXT格式
    TxtLayer newTxtLayer = TxtLayerUtil.parseWktLayerToTxtLayer(
        wktLayer, txtLayer, null, targetDh);
    
    // 保存
    TxtLayerUtil.saveTxt(newTxtLayer, outputPath);
}

常见问题

1. 坐标顺序问题

问题:TXT中Y是北方向,X是东方向,与常见GIS软件相反

解决:转换时注意坐标对应关系

// TXT → JTS
new Coordinate(txtCoord.getX(), txtCoord.getY())  // X是东,Y是北

// JTS → TXT  
txtCoord.setX(coordinate.getX());  // 东方向
txtCoord.setY(coordinate.getY());  // 北方向

2. 内环方向

问题:多边形内环(岛洞)方向错误导致几何无效

解决:确保外环逆时针,内环顺时针,或使用几何修复

3. 编码问题

问题:中文乱码

解决:自动检测文件编码,输出使用UTF-8

4. 精度损失

问题:坐标精度在转换过程中损失

解决:使用足够的小数位数(建议保留2-3位)

小结

本章介绍了国土TXT格式的核心内容:

  1. 文件结构:属性描述、扩展信息、地块坐标三个部分
  2. 数据模型:TxtLayer、TxtFeature、TxtCoordinate
  3. 格式转换:TXT与WktLayer互转
  4. 读写操作:解析和生成TXT文件
  5. 实践案例:格式转换、数据验证、坐标转换

下一章将介绍不同数据格式之间的转换方法总结。