第06章 - 数据格式转换实战

第06章 - 数据格式转换实战

6.1 格式转换概述

6.1.1 支持的格式

OGU4Net通过GDAL支持多种矢量数据格式:

格式 枚举值 扩展名 说明
Shapefile SHP .shp 最常用的矢量格式
GeoJSON GEOJSON .geojson/.json Web GIS标准格式
GeoPackage GEOPACKAGE .gpkg OGC SQLite标准
File Geodatabase FILEGDB .gdb Esri文件地理数据库
PostGIS POSTGIS - PostgreSQL空间扩展
KML KML .kml Google Earth格式
DXF DXF .dxf AutoCAD交换格式
国土TXT TXT .txt 自然资源部门格式

6.1.2 转换原理

格式转换的基本流程:

源格式文件 → 读取器 → OguLayer → 写入器 → 目标格式文件
     ↓            ↓           ↓           ↓
  GDAL/OGR    GdalReader   统一模型   GdalWriter

核心代码:

// 基本转换
public static void ConvertFormat(
    string inputPath,
    DataFormatType inputFormat,
    string outputPath,
    DataFormatType outputFormat)
{
    // 1. 读取
    var layer = OguLayerUtil.ReadLayer(inputFormat, inputPath);
    
    // 2. 写入
    OguLayerUtil.WriteLayer(outputFormat, layer, outputPath);
}

6.2 Shapefile格式

6.2.1 读取Shapefile

using OpenGIS.Utils.DataSource;
using OpenGIS.Utils.Engine.Enums;
using OpenGIS.Utils.Engine.Util;

// 基本读取
var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, "data/cities.shp");

Console.WriteLine($"图层名: {layer.Name}");
Console.WriteLine($"几何类型: {layer.GeometryType}");
Console.WriteLine($"要素数: {layer.GetFeatureCount()}");
Console.WriteLine($"字段数: {layer.Fields.Count}");

// 使用ShpUtil读取(自动处理编码)
var layerWithEncoding = ShpUtil.ReadShapefile("data/cities.shp");

// 指定编码读取
var gbkLayer = ShpUtil.ReadShapefile("data/cities.shp", Encoding.GetEncoding("GBK"));

6.2.2 写入Shapefile

// 基本写入
OguLayerUtil.WriteLayer(DataFormatType.SHP, layer, "output/cities.shp");

// 使用ShpUtil写入(自动创建CPG文件)
ShpUtil.WriteShapefile(layer, "output/cities.shp", Encoding.UTF8);

6.2.3 编码处理

Shapefile的中文编码是常见问题:

using OpenGIS.Utils.Utils;

// 检测Shapefile编码
var encoding = ShpUtil.GetShapefileEncoding("data.shp");
Console.WriteLine($"检测到编码: {encoding.WebName}");

// 创建CPG文件(指定编码)
ShpUtil.CreateCpgFile("output.shp", Encoding.UTF8);

// 转换编码
EncodingUtil.ConvertFileEncoding("data.dbf", Encoding.UTF8);

6.2.4 Shapefile附属文件

// Shapefile包含多个文件
// .shp  - 几何数据
// .shx  - 几何索引
// .dbf  - 属性数据
// .prj  - 坐标系定义
// .cpg  - 编码定义(可选)

// 检查Shapefile是否完整
bool IsShapefileComplete(string shpPath)
{
    var baseName = Path.GetFileNameWithoutExtension(shpPath);
    var dir = Path.GetDirectoryName(shpPath) ?? ".";
    
    var requiredExtensions = new[] { ".shp", ".shx", ".dbf" };
    
    return requiredExtensions.All(ext => 
        File.Exists(Path.Combine(dir, baseName + ext)));
}

6.3 GeoJSON格式

6.3.1 读取GeoJSON

// 读取GeoJSON文件
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.GEOJSON,
    "data/cities.geojson"
);

// 遍历要素
foreach (var feature in layer.Features)
{
    Console.WriteLine($"FID: {feature.Fid}");
    Console.WriteLine($"Geometry: {feature.Wkt}");
    
    foreach (var attr in feature.Attributes)
    {
        Console.WriteLine($"  {attr.Key}: {attr.Value.Value}");
    }
}

6.3.2 写入GeoJSON

// 写入GeoJSON
OguLayerUtil.WriteLayer(
    DataFormatType.GEOJSON,
    layer,
    "output/cities.geojson"
);

6.3.3 GeoJSON特点

GeoJSON格式的特点:

  • 纯文本,易于阅读和编辑
  • Web友好,JavaScript直接解析
  • 自描述,包含完整的属性信息
  • 默认使用WGS84坐标系(EPSG:4326)
// GeoJSON示例结构
/*
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [116.404, 39.915]
      },
      "properties": {
        "name": "北京",
        "population": 21540000
      }
    }
  ]
}
*/

6.3.4 单个几何转GeoJSON

using OpenGIS.Utils.Geometry;

// WKT转GeoJSON
var wkt = "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))";
var geojson = GeometryUtil.Wkt2Geojson(wkt);
Console.WriteLine(geojson);

// 输出:{ "type": "Polygon", "coordinates": [ [ [ 0.0, 0.0 ], [ 10.0, 0.0 ], ... ] ] }

6.4 GeoPackage格式

6.4.1 读取GeoPackage

// 读取GeoPackage
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.GEOPACKAGE,
    "data/cities.gpkg",
    layerName: "cities"  // GeoPackage可包含多个图层
);

// 获取GeoPackage中的所有图层名
var layerNames = OguLayerUtil.GetLayerNames(
    DataFormatType.GEOPACKAGE,
    "data/cities.gpkg"
);

Console.WriteLine("GeoPackage中的图层:");
foreach (var name in layerNames)
{
    Console.WriteLine($"  - {name}");
}

6.4.2 写入GeoPackage

// 写入GeoPackage
OguLayerUtil.WriteLayer(
    DataFormatType.GEOPACKAGE,
    layer,
    "output/cities.gpkg",
    layerName: "my_cities"
);

// GeoPackage可以包含多个图层
// 需要使用追加模式(当前未实现)

6.4.3 GeoPackage特点

  • OGC标准的SQLite数据库格式
  • 支持矢量和栅格数据
  • 单文件,便于分发
  • 支持多图层
  • 支持元数据和样式

6.5 FileGDB格式

6.5.1 读取FileGDB

// 检查驱动可用性
var hasFileGDB = GdalConfiguration.IsDriverAvailable("FileGDB");
var hasOpenFileGDB = GdalConfiguration.IsDriverAvailable("OpenFileGDB");

if (hasFileGDB || hasOpenFileGDB)
{
    // 读取FileGDB
    var layer = OguLayerUtil.ReadLayer(
        DataFormatType.FILEGDB,
        "data/sample.gdb",
        layerName: "Cities"
    );
    
    Console.WriteLine($"读取到 {layer.GetFeatureCount()} 个要素");
}
else
{
    Console.WriteLine("FileGDB驱动不可用");
}

// 获取FileGDB中的图层列表
var layers = OguLayerUtil.GetLayerNames(
    DataFormatType.FILEGDB,
    "data/sample.gdb"
);

6.5.2 写入FileGDB

// 注意:写入FileGDB需要FileGDB驱动,OpenFileGDB只支持读取

if (GdalConfiguration.IsDriverAvailable("FileGDB"))
{
    OguLayerUtil.WriteLayer(
        DataFormatType.FILEGDB,
        layer,
        "output/sample.gdb",
        layerName: "MyLayer"
    );
}
else
{
    Console.WriteLine("FileGDB驱动不可用,无法写入");
    // 可以考虑转换为其他格式
    OguLayerUtil.WriteLayer(
        DataFormatType.GEOPACKAGE,
        layer,
        "output/sample.gpkg"
    );
}

6.6 PostGIS格式

6.6.1 连接PostGIS

// PostGIS连接字符串格式
// PG:host=<host> dbname=<database> user=<user> password=<password> [port=<port>]

string connStr = "PG:host=localhost dbname=mydb user=postgres password=secret";

// 读取PostGIS表
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.POSTGIS,
    connStr,
    layerName: "public.cities"  // schema.table
);

Console.WriteLine($"读取到 {layer.GetFeatureCount()} 个要素");

6.6.2 PostGIS查询

// 带过滤条件读取
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.POSTGIS,
    connStr,
    layerName: "public.cities",
    attributeFilter: "population > 1000000"
);

// 空间过滤
var bbox = "POLYGON ((116.0 39.0, 117.0 39.0, 117.0 40.0, 116.0 40.0, 116.0 39.0))";
var layerFiltered = OguLayerUtil.ReadLayer(
    DataFormatType.POSTGIS,
    connStr,
    layerName: "public.cities",
    spatialFilterWkt: bbox
);

6.6.3 写入PostGIS

// 写入到PostGIS(会创建新表)
OguLayerUtil.WriteLayer(
    DataFormatType.POSTGIS,
    layer,
    connStr,
    layerName: "public.new_cities"
);

6.6.4 PostgisUtil工具类

using OpenGIS.Utils.Engine.Util;

// PostgisUtil提供更多PostGIS专用功能
// 例如:连接池管理、批量操作等

6.7 KML格式

6.7.1 读取KML

// 读取KML文件
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.KML,
    "data/places.kml"
);

// 遍历要素
foreach (var feature in layer.Features)
{
    var name = feature.GetValue("Name");
    var description = feature.GetValue("description");
    Console.WriteLine($"{name}: {description}");
}

6.7.2 写入KML

// 写入KML
OguLayerUtil.WriteLayer(
    DataFormatType.KML,
    layer,
    "output/places.kml"
);

6.7.3 KML特点

  • Google Earth和Google Maps使用的格式
  • 基于XML
  • 支持样式和3D属性
  • 适合地图展示和分享

6.8 DXF格式

6.8.1 读取DXF

// 读取DXF文件
var layer = OguLayerUtil.ReadLayer(
    DataFormatType.DXF,
    "data/drawing.dxf"
);

// DXF通常包含多个图层(CAD图层)
var layerNames = OguLayerUtil.GetLayerNames(
    DataFormatType.DXF,
    "data/drawing.dxf"
);

foreach (var name in layerNames)
{
    Console.WriteLine($"CAD图层: {name}");
}

6.8.2 写入DXF

// 写入DXF
OguLayerUtil.WriteLayer(
    DataFormatType.DXF,
    layer,
    "output/drawing.dxf"
);

6.9 国土TXT格式

6.9.1 格式说明

国土TXT格式是中国自然资源部门使用的坐标文件格式:

数据来源: 自然资源部
坐标系: CGCS2000
分带: 3度带
投影类型: 高斯-克吕格投影
单位: 米

点号	圈号	X	Y	Z	备注
J1	1	456789.123	4321098.456		界址点1
J2	1	456790.234	4321099.567		界址点2
...

6.9.2 读取TXT

using OpenGIS.Utils.DataSource;

// 读取国土TXT文件
var layer = GtTxtUtil.LoadTxt("data/boundary.txt");

Console.WriteLine($"图层名: {layer.Name}");
Console.WriteLine($"要素数: {layer.GetFeatureCount()}");

// 读取元数据
if (layer.Metadata != null)
{
    Console.WriteLine($"坐标系: {layer.Metadata.CoordinateSystemName}");
    Console.WriteLine($"分带: {layer.Metadata.ZoneDivision}");
    Console.WriteLine($"单位: {layer.Metadata.MeasureUnit}");
}

// 遍历坐标点
foreach (var feature in layer.Features)
{
    var pointNumber = feature.GetValue("点号");
    var ringNumber = feature.GetValue("圈号");
    var x = feature.GetAttribute("X")?.GetDoubleValue();
    var y = feature.GetAttribute("Y")?.GetDoubleValue();
    
    Console.WriteLine($"{pointNumber} ({ringNumber}): X={x}, Y={y}");
}

6.9.3 写入TXT

// 创建元数据
var metadata = new OguLayerMetadata
{
    DataSource = "测量队",
    CoordinateSystemName = "CGCS2000",
    ZoneDivision = "3度带",
    ProjectionType = "高斯-克吕格投影",
    MeasureUnit = "米"
};

// 写入TXT文件
GtTxtUtil.SaveTxt(layer, "output/boundary.txt", metadata);

6.9.4 TXT转Shapefile

// 1. 读取TXT
var txtLayer = GtTxtUtil.LoadTxt("boundary.txt");

// 2. TXT是点数据,如果需要转为面,需要重组几何
// 根据圈号分组点
var rings = txtLayer.Features
    .GroupBy(f => f.GetValue("圈号")?.ToString() ?? "1")
    .OrderBy(g => g.Key)
    .ToList();

// 3. 创建多边形图层
var polyLayer = new OguLayer
{
    Name = "Boundary",
    GeometryType = GeometryType.POLYGON,
    Wkid = 4490  // CGCS2000
};

polyLayer.AddField(new OguField { Name = "Name", DataType = FieldDataType.STRING, Length = 50 });

// 4. 构建多边形WKT
var coordinates = new List<string>();
foreach (var ring in rings)
{
    var points = ring.OrderBy(f => f.GetValue("点号")?.ToString())
        .Select(f =>
        {
            var x = f.GetAttribute("X")?.GetDoubleValue() ?? 0;
            var y = f.GetAttribute("Y")?.GetDoubleValue() ?? 0;
            return $"{x} {y}";
        })
        .ToList();
    
    // 闭合环
    if (points.First() != points.Last())
        points.Add(points.First());
    
    coordinates.Add($"({string.Join(", ", points)})");
}

var wkt = $"POLYGON ({string.Join(", ", coordinates)})";

var feature = new OguFeature { Fid = 1, Wkt = wkt };
feature.SetValue("Name", txtLayer.Name);
polyLayer.AddFeature(feature);

// 5. 写入Shapefile
OguLayerUtil.WriteLayer(DataFormatType.SHP, polyLayer, "output/boundary.shp");

6.10 批量转换

6.10.1 目录批量转换

public static void BatchConvert(
    string inputDir,
    string outputDir,
    DataFormatType inputFormat,
    DataFormatType outputFormat,
    string inputExtension,
    string outputExtension)
{
    // 确保输出目录存在
    Directory.CreateDirectory(outputDir);
    
    // 获取所有输入文件
    var inputFiles = Directory.GetFiles(inputDir, $"*{inputExtension}");
    
    Console.WriteLine($"找到 {inputFiles.Length} 个文件需要转换");
    
    int success = 0, failed = 0;
    
    foreach (var inputFile in inputFiles)
    {
        var fileName = Path.GetFileNameWithoutExtension(inputFile);
        var outputFile = Path.Combine(outputDir, $"{fileName}{outputExtension}");
        
        try
        {
            Console.Write($"转换: {fileName}...");
            
            OguLayerUtil.ConvertFormat(
                inputFile, inputFormat,
                outputFile, outputFormat
            );
            
            Console.WriteLine(" 成功");
            success++;
        }
        catch (Exception ex)
        {
            Console.WriteLine($" 失败: {ex.Message}");
            failed++;
        }
    }
    
    Console.WriteLine($"\n转换完成: 成功 {success}, 失败 {failed}");
}

// 使用示例
BatchConvert(
    "input/",
    "output/",
    DataFormatType.SHP,
    DataFormatType.GEOJSON,
    ".shp",
    ".geojson"
);

6.10.2 异步批量转换

public static async Task BatchConvertAsync(
    string[] inputFiles,
    string outputDir,
    DataFormatType inputFormat,
    DataFormatType outputFormat,
    string outputExtension,
    int maxParallel = 4)
{
    Directory.CreateDirectory(outputDir);
    
    var semaphore = new SemaphoreSlim(maxParallel);
    var tasks = new List<Task>();
    
    foreach (var inputFile in inputFiles)
    {
        await semaphore.WaitAsync();
        
        var task = Task.Run(async () =>
        {
            try
            {
                var fileName = Path.GetFileNameWithoutExtension(inputFile);
                var outputFile = Path.Combine(outputDir, $"{fileName}{outputExtension}");
                
                await OguLayerUtil.ConvertFormatAsync(
                    inputFile, inputFormat,
                    outputFile, outputFormat
                );
                
                Console.WriteLine($"完成: {fileName}");
            }
            finally
            {
                semaphore.Release();
            }
        });
        
        tasks.Add(task);
    }
    
    await Task.WhenAll(tasks);
}

6.11 转换中的数据处理

6.11.1 坐标转换

// 读取WGS84坐标数据
var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, "wgs84.shp");

// 转换为CGCS2000
foreach (var feature in layer.Features)
{
    if (!string.IsNullOrEmpty(feature.Wkt))
    {
        feature.Wkt = CrsUtil.Transform(feature.Wkt, 4326, 4490);
    }
}
layer.Wkid = 4490;

// 保存
OguLayerUtil.WriteLayer(DataFormatType.SHP, layer, "cgcs2000.shp");

6.11.2 字段筛选

// 读取原始数据
var srcLayer = OguLayerUtil.ReadLayer(DataFormatType.SHP, "source.shp");

// 创建只包含指定字段的新图层
var dstLayer = new OguLayer
{
    Name = srcLayer.Name,
    GeometryType = srcLayer.GeometryType,
    Wkid = srcLayer.Wkid
};

// 只保留需要的字段
var keepFields = new[] { "ID", "Name", "Type" };
foreach (var fieldName in keepFields)
{
    var field = srcLayer.GetField(fieldName);
    if (field != null)
    {
        dstLayer.AddField(field.Clone());
    }
}

// 复制要素,只保留指定字段的值
foreach (var srcFeature in srcLayer.Features)
{
    var dstFeature = new OguFeature
    {
        Fid = srcFeature.Fid,
        Wkt = srcFeature.Wkt
    };
    
    foreach (var fieldName in keepFields)
    {
        if (srcFeature.HasAttribute(fieldName))
        {
            dstFeature.SetValue(fieldName, srcFeature.GetValue(fieldName));
        }
    }
    
    dstLayer.AddFeature(dstFeature);
}

// 保存
OguLayerUtil.WriteLayer(DataFormatType.GEOJSON, dstLayer, "filtered.geojson");

6.11.3 属性计算

var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, "polygons.shp");

// 添加面积字段
layer.AddField(new OguField
{
    Name = "CalcArea",
    DataType = FieldDataType.DOUBLE
});

// 计算面积
foreach (var feature in layer.Features)
{
    if (!string.IsNullOrEmpty(feature.Wkt))
    {
        var area = GeometryUtil.AreaWkt(feature.Wkt);
        feature.SetValue("CalcArea", area);
    }
}

OguLayerUtil.WriteLayer(DataFormatType.SHP, layer, "with_area.shp");

6.12 小结

本章介绍了OGU4Net支持的各种数据格式转换:

  1. Shapefile:最常用格式,注意编码处理
  2. GeoJSON:Web友好,纯文本格式
  3. GeoPackage:OGC标准,单文件多图层
  4. FileGDB:Esri格式,需要特定驱动
  5. PostGIS:数据库存储,支持SQL查询
  6. KML:Google Earth格式
  7. DXF:CAD交换格式
  8. 国土TXT:中国自然资源部门格式

转换技巧:

  • 使用OguLayerUtil简化转换操作
  • 注意编码问题(尤其是Shapefile)
  • 转换时可以进行坐标转换、字段筛选等处理
  • 批量转换时考虑并行处理
posted @ 2025-12-03 15:51  我才是银古  阅读(0)  评论(0)    收藏  举报