第11章 - 开发实战案例

第11章 - 开发实战案例

11.1 案例概述

本章通过实际案例展示OGU4Net的综合应用,涵盖:

  1. 数据格式批量转换工具
  2. 空间数据查询服务
  3. 国土数据处理系统
  4. 面积计算与统计工具
  5. 数据质量检查工具

11.2 案例一:数据格式批量转换工具

11.2.1 需求描述

开发一个命令行工具,支持:

  • 批量转换Shapefile到GeoJSON
  • 自动处理编码问题
  • 可选的坐标转换
  • 进度显示和错误报告

11.2.2 实现代码

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

namespace GisConverter;

public class BatchConverter
{
    private readonly ConvertOptions _options;
    private int _successCount;
    private int _failCount;
    private readonly List<string> _errors = new();
    
    public BatchConverter(ConvertOptions options)
    {
        _options = options;
    }
    
    public void Convert()
    {
        // 验证输入目录
        if (!Directory.Exists(_options.InputDir))
        {
            Console.Error.WriteLine($"输入目录不存在: {_options.InputDir}");
            return;
        }
        
        // 创建输出目录
        Directory.CreateDirectory(_options.OutputDir);
        
        // 获取文件列表
        var files = Directory.GetFiles(_options.InputDir, "*.shp");
        var sortedFiles = SortUtil.NaturalSort(files, Path.GetFileName);
        
        Console.WriteLine($"找到 {files.Length} 个Shapefile");
        Console.WriteLine($"输出格式: {_options.OutputFormat}");
        
        if (_options.TargetWkid.HasValue)
        {
            Console.WriteLine($"目标坐标系: EPSG:{_options.TargetWkid}");
        }
        
        Console.WriteLine();
        
        // 处理每个文件
        int index = 0;
        foreach (var file in sortedFiles)
        {
            index++;
            var fileName = Path.GetFileName(file);
            Console.Write($"[{index}/{files.Length}] {fileName}... ");
            
            try
            {
                ConvertFile(file);
                _successCount++;
                Console.WriteLine("成功");
            }
            catch (Exception ex)
            {
                _failCount++;
                _errors.Add($"{fileName}: {ex.Message}");
                Console.WriteLine($"失败 - {ex.Message}");
            }
        }
        
        // 输出统计
        Console.WriteLine();
        Console.WriteLine($"转换完成: 成功 {_successCount}, 失败 {_failCount}");
        
        if (_errors.Any())
        {
            Console.WriteLine("\n错误详情:");
            foreach (var error in _errors)
            {
                Console.WriteLine($"  - {error}");
            }
        }
    }
    
    private void ConvertFile(string inputPath)
    {
        // 读取Shapefile
        var encoding = ShpUtil.GetShapefileEncoding(inputPath);
        var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, inputPath);
        
        // 坐标转换
        if (_options.TargetWkid.HasValue && layer.Wkid.HasValue)
        {
            foreach (var feature in layer.Features)
            {
                if (!string.IsNullOrEmpty(feature.Wkt))
                {
                    feature.Wkt = CrsUtil.Transform(
                        feature.Wkt, 
                        layer.Wkid.Value, 
                        _options.TargetWkid.Value);
                }
            }
            layer.Wkid = _options.TargetWkid.Value;
        }
        
        // 确定输出路径和格式
        var baseName = Path.GetFileNameWithoutExtension(inputPath);
        string outputPath;
        DataFormatType outputFormat;
        
        switch (_options.OutputFormat.ToLower())
        {
            case "geojson":
                outputPath = Path.Combine(_options.OutputDir, baseName + ".geojson");
                outputFormat = DataFormatType.GEOJSON;
                break;
            case "gpkg":
                outputPath = Path.Combine(_options.OutputDir, baseName + ".gpkg");
                outputFormat = DataFormatType.GEOPACKAGE;
                break;
            case "kml":
                outputPath = Path.Combine(_options.OutputDir, baseName + ".kml");
                outputFormat = DataFormatType.KML;
                break;
            default:
                throw new ArgumentException($"不支持的输出格式: {_options.OutputFormat}");
        }
        
        // 写入
        OguLayerUtil.WriteLayer(outputFormat, layer, outputPath);
    }
}

public class ConvertOptions
{
    public string InputDir { get; set; } = "";
    public string OutputDir { get; set; } = "";
    public string OutputFormat { get; set; } = "geojson";
    public int? TargetWkid { get; set; }
}

// 程序入口
class Program
{
    static void Main(string[] args)
    {
        if (args.Length < 2)
        {
            Console.WriteLine("用法: GisConverter <输入目录> <输出目录> [选项]");
            Console.WriteLine("选项:");
            Console.WriteLine("  -f <格式>    输出格式 (geojson, gpkg, kml)");
            Console.WriteLine("  -t <WKID>    目标坐标系EPSG代码");
            return;
        }
        
        var options = new ConvertOptions
        {
            InputDir = args[0],
            OutputDir = args[1]
        };
        
        // 解析可选参数
        for (int i = 2; i < args.Length; i++)
        {
            if (args[i] == "-f" && i + 1 < args.Length)
            {
                options.OutputFormat = args[++i];
            }
            else if (args[i] == "-t" && i + 1 < args.Length)
            {
                options.TargetWkid = int.Parse(args[++i]);
            }
        }
        
        var converter = new BatchConverter(options);
        converter.Convert();
    }
}

11.3 案例二:空间数据查询服务

11.3.1 需求描述

开发一个REST API服务,提供:

  • 点查询(查询点所在的区域)
  • 范围查询(查询指定范围内的要素)
  • 缓冲区查询

11.3.2 实现代码

using Microsoft.AspNetCore.Mvc;
using OpenGIS.Utils.DataSource;
using OpenGIS.Utils.Engine.Enums;
using OpenGIS.Utils.Geometry;

namespace GisQueryService.Controllers;

[ApiController]
[Route("api/[controller]")]
public class SpatialQueryController : ControllerBase
{
    private readonly string _dataPath;
    
    public SpatialQueryController(IConfiguration config)
    {
        _dataPath = config["DataPath"] ?? "data/";
    }
    
    /// <summary>
    /// 点查询 - 查询点所在的区域
    /// </summary>
    [HttpGet("point")]
    public IActionResult PointQuery(
        [FromQuery] double x, 
        [FromQuery] double y, 
        [FromQuery] string layer)
    {
        try
        {
            var layerPath = Path.Combine(_dataPath, layer + ".shp");
            if (!System.IO.File.Exists(layerPath))
            {
                return NotFound(new { error = $"图层不存在: {layer}" });
            }
            
            var pointWkt = $"POINT ({x} {y})";
            var data = OguLayerUtil.ReadLayer(DataFormatType.SHP, layerPath);
            
            var results = new List<object>();
            
            foreach (var feature in data.Features)
            {
                if (string.IsNullOrEmpty(feature.Wkt)) continue;
                
                if (GeometryUtil.ContainsWkt(feature.Wkt, pointWkt))
                {
                    results.Add(new
                    {
                        fid = feature.Fid,
                        attributes = feature.Attributes.ToDictionary(
                            a => a.Key,
                            a => a.Value.Value)
                    });
                }
            }
            
            return Ok(new
            {
                query = new { x, y },
                count = results.Count,
                features = results
            });
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
    
    /// <summary>
    /// 范围查询 - 查询矩形范围内的要素
    /// </summary>
    [HttpGet("bbox")]
    public IActionResult BboxQuery(
        [FromQuery] double minX,
        [FromQuery] double minY,
        [FromQuery] double maxX,
        [FromQuery] double maxY,
        [FromQuery] string layer)
    {
        try
        {
            var layerPath = Path.Combine(_dataPath, layer + ".shp");
            if (!System.IO.File.Exists(layerPath))
            {
                return NotFound(new { error = $"图层不存在: {layer}" });
            }
            
            var bboxWkt = $"POLYGON (({minX} {minY}, {maxX} {minY}, {maxX} {maxY}, {minX} {maxY}, {minX} {minY}))";
            
            // 使用空间过滤读取
            var data = OguLayerUtil.ReadLayer(
                DataFormatType.SHP,
                layerPath,
                spatialFilterWkt: bboxWkt);
            
            var features = data.Features.Select(f => new
            {
                fid = f.Fid,
                geometry = f.Wkt,
                attributes = f.Attributes.ToDictionary(
                    a => a.Key,
                    a => a.Value.Value)
            });
            
            return Ok(new
            {
                query = new { minX, minY, maxX, maxY },
                count = data.GetFeatureCount(),
                features
            });
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
    
    /// <summary>
    /// 缓冲区查询 - 查询指定距离内的要素
    /// </summary>
    [HttpGet("buffer")]
    public IActionResult BufferQuery(
        [FromQuery] double x,
        [FromQuery] double y,
        [FromQuery] double distance,
        [FromQuery] string layer)
    {
        try
        {
            var layerPath = Path.Combine(_dataPath, layer + ".shp");
            if (!System.IO.File.Exists(layerPath))
            {
                return NotFound(new { error = $"图层不存在: {layer}" });
            }
            
            var pointWkt = $"POINT ({x} {y})";
            var bufferWkt = GeometryUtil.BufferWkt(pointWkt, distance);
            
            var data = OguLayerUtil.ReadLayer(
                DataFormatType.SHP,
                layerPath,
                spatialFilterWkt: bufferWkt);
            
            var features = data.Features.Select(f => new
            {
                fid = f.Fid,
                geometry = f.Wkt,
                attributes = f.Attributes.ToDictionary(
                    a => a.Key,
                    a => a.Value.Value)
            });
            
            return Ok(new
            {
                query = new { x, y, distance },
                bufferGeometry = bufferWkt,
                count = data.GetFeatureCount(),
                features
            });
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
    
    /// <summary>
    /// 几何转换接口
    /// </summary>
    [HttpPost("geometry/convert")]
    public IActionResult ConvertGeometry([FromBody] GeometryConvertRequest request)
    {
        try
        {
            string result = request.TargetFormat.ToLower() switch
            {
                "geojson" => GeometryUtil.Wkt2Geojson(request.Wkt),
                "wkt" => request.Wkt,  // 已经是WKT
                _ => throw new ArgumentException($"不支持的目标格式: {request.TargetFormat}")
            };
            
            return Ok(new { result });
        }
        catch (Exception ex)
        {
            return BadRequest(new { error = ex.Message });
        }
    }
}

public class GeometryConvertRequest
{
    public string Wkt { get; set; } = "";
    public string TargetFormat { get; set; } = "geojson";
}

11.4 案例三:国土数据处理系统

11.4.1 需求描述

处理国土部门的TXT坐标文件:

  • 读取TXT坐标文件
  • 构建宗地多边形
  • 计算面积
  • 输出为Shapefile

11.4.2 实现代码

using OpenGIS.Utils.DataSource;
using OpenGIS.Utils.Engine.Enums;
using OpenGIS.Utils.Engine.Model.Layer;
using OpenGIS.Utils.Engine.Util;
using OpenGIS.Utils.Geometry;

namespace LandProcessor;

public class ParcelProcessor
{
    /// <summary>
    /// 处理TXT坐标文件,生成宗地Shapefile
    /// </summary>
    public OguLayer ProcessTxtToParcel(string txtPath, int targetWkid = 4490)
    {
        // 1. 读取TXT文件
        Console.WriteLine($"读取TXT文件: {txtPath}");
        var txtLayer = GtTxtUtil.LoadTxt(txtPath);
        
        Console.WriteLine($"元数据: {txtLayer.Metadata?.CoordinateSystemName}");
        Console.WriteLine($"读取到 {txtLayer.GetFeatureCount()} 个坐标点");
        
        // 2. 按圈号分组
        var featuresByRing = txtLayer.Features
            .GroupBy(f => f.GetValue("圈号")?.ToString() ?? "1")
            .OrderBy(g => g.Key)
            .ToList();
        
        Console.WriteLine($"共 {featuresByRing.Count} 个环");
        
        // 3. 创建多边形图层
        var parcelLayer = new OguLayer
        {
            Name = Path.GetFileNameWithoutExtension(txtPath),
            GeometryType = GeometryType.POLYGON,
            Wkid = targetWkid
        };
        
        // 添加字段
        parcelLayer.AddField(new OguField { Name = "Name", DataType = FieldDataType.STRING, Length = 100 });
        parcelLayer.AddField(new OguField { Name = "Area", DataType = FieldDataType.DOUBLE });
        parcelLayer.AddField(new OguField { Name = "Perimeter", DataType = FieldDataType.DOUBLE });
        parcelLayer.AddField(new OguField { Name = "PointCount", DataType = FieldDataType.INTEGER });
        
        // 4. 构建多边形
        var rings = new List<string>();
        
        foreach (var group in featuresByRing)
        {
            var points = group
                .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.Count < 3)
            {
                Console.WriteLine($"警告: 环 {group.Key} 点数不足,跳过");
                continue;
            }
            
            // 闭合环
            if (points.First() != points.Last())
            {
                points.Add(points.First());
            }
            
            rings.Add($"({string.Join(", ", points)})");
        }
        
        if (rings.Count == 0)
        {
            throw new Exception("没有有效的环");
        }
        
        // 构建多边形WKT
        string wkt;
        if (rings.Count == 1)
        {
            wkt = $"POLYGON ({rings[0]})";
        }
        else
        {
            // 第一个环是外环,其余是内环(孔洞)
            wkt = $"POLYGON ({string.Join(", ", rings)})";
        }
        
        Console.WriteLine("验证几何...");
        var geom = GeometryUtil.Wkt2Geometry(wkt);
        var validResult = GeometryUtil.IsValid(geom);
        
        if (!validResult.IsValid)
        {
            Console.WriteLine($"警告: 几何无效 - {validResult.ErrorMessage}");
        }
        
        // 5. 计算属性
        // 先转到投影坐标系计算面积
        int zone = CrsUtil.GetDh(geom);
        int projWkid = CrsUtil.GetProjectedWkid(zone);
        string projWkt = CrsUtil.Transform(wkt, targetWkid, projWkid);
        
        double area = GeometryUtil.AreaWkt(projWkt);
        double perimeter = GeometryUtil.LengthWkt(projWkt);
        int pointCount = txtLayer.GetFeatureCount();
        
        // 6. 创建要素
        var feature = new OguFeature
        {
            Fid = 1,
            Wkt = wkt
        };
        
        feature.SetValue("Name", parcelLayer.Name);
        feature.SetValue("Area", area);
        feature.SetValue("Perimeter", perimeter);
        feature.SetValue("PointCount", pointCount);
        
        parcelLayer.AddFeature(feature);
        
        Console.WriteLine($"面积: {area / 10000:F4} 公顷");
        Console.WriteLine($"周长: {perimeter:F2} 米");
        
        return parcelLayer;
    }
    
    /// <summary>
    /// 批量处理TXT文件
    /// </summary>
    public void BatchProcess(string inputDir, string outputDir)
    {
        Directory.CreateDirectory(outputDir);
        
        var txtFiles = Directory.GetFiles(inputDir, "*.txt");
        Console.WriteLine($"找到 {txtFiles.Length} 个TXT文件\n");
        
        var allParcels = new OguLayer
        {
            Name = "AllParcels",
            GeometryType = GeometryType.POLYGON,
            Wkid = 4490
        };
        
        allParcels.AddField(new OguField { Name = "Name", DataType = FieldDataType.STRING, Length = 100 });
        allParcels.AddField(new OguField { Name = "Area", DataType = FieldDataType.DOUBLE });
        allParcels.AddField(new OguField { Name = "Perimeter", DataType = FieldDataType.DOUBLE });
        allParcels.AddField(new OguField { Name = "SourceFile", DataType = FieldDataType.STRING, Length = 200 });
        
        int fid = 1;
        double totalArea = 0;
        
        foreach (var txtFile in txtFiles)
        {
            Console.WriteLine($"========== {Path.GetFileName(txtFile)} ==========");
            
            try
            {
                var parcel = ProcessTxtToParcel(txtFile);
                var feature = parcel.Features.First();
                
                var newFeature = new OguFeature
                {
                    Fid = fid++,
                    Wkt = feature.Wkt
                };
                
                newFeature.SetValue("Name", feature.GetValue("Name"));
                newFeature.SetValue("Area", feature.GetValue("Area"));
                newFeature.SetValue("Perimeter", feature.GetValue("Perimeter"));
                newFeature.SetValue("SourceFile", Path.GetFileName(txtFile));
                
                allParcels.AddFeature(newFeature);
                
                var area = feature.GetAttribute("Area")?.GetDoubleValue() ?? 0;
                totalArea += area;
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine($"处理失败: {ex.Message}");
            }
            
            Console.WriteLine();
        }
        
        // 保存合并结果
        var outputPath = Path.Combine(outputDir, "all_parcels.shp");
        OguLayerUtil.WriteLayer(DataFormatType.SHP, allParcels, outputPath);
        
        Console.WriteLine($"\n======== 汇总 ========");
        Console.WriteLine($"成功处理: {allParcels.GetFeatureCount()} 个宗地");
        Console.WriteLine($"总面积: {totalArea / 10000:F4} 公顷");
        Console.WriteLine($"输出文件: {outputPath}");
    }
}

11.5 案例四:面积计算与统计工具

11.5.1 实现代码

using OpenGIS.Utils.DataSource;
using OpenGIS.Utils.Engine.Enums;
using OpenGIS.Utils.Engine.Model.Layer;
using OpenGIS.Utils.Engine.Util;
using OpenGIS.Utils.Geometry;

namespace AreaCalculator;

public class AreaStatistics
{
    /// <summary>
    /// 计算图层面积统计
    /// </summary>
    public AreaReport Calculate(string shpPath, string groupField = null)
    {
        var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, shpPath);
        
        var report = new AreaReport
        {
            LayerName = layer.Name,
            FeatureCount = layer.GetFeatureCount(),
            GroupField = groupField
        };
        
        foreach (var feature in layer.Features)
        {
            if (string.IsNullOrEmpty(feature.Wkt)) continue;
            
            // 计算面积
            double area = CalculateAreaInSquareMeters(feature.Wkt, layer.Wkid ?? 4326);
            
            report.TotalArea += area;
            
            // 分组统计
            if (!string.IsNullOrEmpty(groupField))
            {
                var groupValue = feature.GetValue(groupField)?.ToString() ?? "未分类";
                
                if (!report.GroupAreas.ContainsKey(groupValue))
                {
                    report.GroupAreas[groupValue] = 0;
                }
                report.GroupAreas[groupValue] += area;
            }
        }
        
        return report;
    }
    
    private double CalculateAreaInSquareMeters(string wkt, int wkid)
    {
        if (CrsUtil.IsProjectedCRS(wkid))
        {
            // 已经是投影坐标系
            return GeometryUtil.AreaWkt(wkt);
        }
        else
        {
            // 转换到投影坐标系
            var geom = GeometryUtil.Wkt2Geometry(wkt);
            int zone = CrsUtil.GetDh(geom);
            int projWkid = CrsUtil.GetProjectedWkid(zone);
            
            string projWkt = CrsUtil.Transform(wkt, wkid, projWkid);
            return GeometryUtil.AreaWkt(projWkt);
        }
    }
    
    /// <summary>
    /// 输出报告
    /// </summary>
    public void PrintReport(AreaReport report)
    {
        Console.WriteLine("=".PadRight(50, '='));
        Console.WriteLine($"图层名称: {report.LayerName}");
        Console.WriteLine($"要素数量: {report.FeatureCount}");
        Console.WriteLine($"总面积: {report.TotalArea / 10000:N4} 公顷");
        Console.WriteLine($"总面积: {report.TotalArea / 1000000:N6} 平方公里");
        
        if (report.GroupAreas.Any())
        {
            Console.WriteLine();
            Console.WriteLine($"按 {report.GroupField} 分组统计:");
            Console.WriteLine("-".PadRight(40, '-'));
            
            foreach (var (group, area) in report.GroupAreas.OrderByDescending(x => x.Value))
            {
                var percent = area / report.TotalArea * 100;
                Console.WriteLine($"  {group}: {area / 10000:N4} 公顷 ({percent:F1}%)");
            }
        }
        
        Console.WriteLine("=".PadRight(50, '='));
    }
}

public class AreaReport
{
    public string LayerName { get; set; }
    public int FeatureCount { get; set; }
    public double TotalArea { get; set; }
    public string GroupField { get; set; }
    public Dictionary<string, double> GroupAreas { get; set; } = new();
}

11.6 案例五:数据质量检查工具

11.6.1 实现代码

using OpenGIS.Utils.DataSource;
using OpenGIS.Utils.Engine.Enums;
using OpenGIS.Utils.Geometry;
using OpenGIS.Utils.Engine.Model.Layer;

namespace DataQualityChecker;

public class QualityChecker
{
    public QualityReport Check(string shpPath)
    {
        var layer = OguLayerUtil.ReadLayer(DataFormatType.SHP, shpPath);
        
        var report = new QualityReport
        {
            LayerName = layer.Name,
            FeatureCount = layer.GetFeatureCount(),
            FieldCount = layer.Fields.Count
        };
        
        // 检查每个要素
        foreach (var feature in layer.Features)
        {
            // 1. 空几何检查
            if (string.IsNullOrEmpty(feature.Wkt))
            {
                report.Issues.Add(new QualityIssue
                {
                    Fid = feature.Fid,
                    IssueType = "空几何",
                    Description = "要素几何为空",
                    Severity = "错误"
                });
                continue;
            }
            
            try
            {
                var geom = GeometryUtil.Wkt2Geometry(feature.Wkt);
                
                // 2. 有效性检查
                var validResult = GeometryUtil.IsValid(geom);
                if (!validResult.IsValid)
                {
                    report.Issues.Add(new QualityIssue
                    {
                        Fid = feature.Fid,
                        IssueType = "几何无效",
                        Description = validResult.ErrorMessage ?? "几何不满足OGC规范",
                        Severity = "错误"
                    });
                }
                
                // 3. 简单性检查
                var simpleResult = GeometryUtil.IsSimple(geom);
                if (!simpleResult.IsSimple)
                {
                    report.Issues.Add(new QualityIssue
                    {
                        Fid = feature.Fid,
                        IssueType = "几何不简单",
                        Description = simpleResult.Reason ?? "几何存在自相交",
                        Severity = "警告"
                    });
                }
                
                // 4. 小面积检查(面要素)
                if (layer.GeometryType == GeometryType.POLYGON || 
                    layer.GeometryType == GeometryType.MULTIPOLYGON)
                {
                    double area = GeometryUtil.Area(geom);
                    if (area < 0.000001)
                    {
                        report.Issues.Add(new QualityIssue
                        {
                            Fid = feature.Fid,
                            IssueType = "微小面积",
                            Description = $"面积过小: {area}",
                            Severity = "警告"
                        });
                    }
                }
                
                // 5. 空值属性检查
                foreach (var field in layer.Fields)
                {
                    if (!field.IsNullable)
                    {
                        var value = feature.GetValue(field.Name);
                        if (value == null || (value is string s && string.IsNullOrWhiteSpace(s)))
                        {
                            report.Issues.Add(new QualityIssue
                            {
                                Fid = feature.Fid,
                                IssueType = "空值字段",
                                Description = $"非空字段 {field.Name} 的值为空",
                                Severity = "错误"
                            });
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                report.Issues.Add(new QualityIssue
                {
                    Fid = feature.Fid,
                    IssueType = "解析错误",
                    Description = ex.Message,
                    Severity = "错误"
                });
            }
        }
        
        // 统计
        report.ErrorCount = report.Issues.Count(i => i.Severity == "错误");
        report.WarningCount = report.Issues.Count(i => i.Severity == "警告");
        
        return report;
    }
    
    public void PrintReport(QualityReport report)
    {
        Console.WriteLine("=".PadRight(60, '='));
        Console.WriteLine("数据质量检查报告");
        Console.WriteLine("=".PadRight(60, '='));
        Console.WriteLine($"图层名称: {report.LayerName}");
        Console.WriteLine($"要素数量: {report.FeatureCount}");
        Console.WriteLine($"字段数量: {report.FieldCount}");
        Console.WriteLine($"错误数量: {report.ErrorCount}");
        Console.WriteLine($"警告数量: {report.WarningCount}");
        Console.WriteLine();
        
        if (report.Issues.Any())
        {
            Console.WriteLine("问题详情:");
            Console.WriteLine("-".PadRight(60, '-'));
            
            foreach (var issue in report.Issues)
            {
                Console.WriteLine($"[{issue.Severity}] FID {issue.Fid}: {issue.IssueType}");
                Console.WriteLine($"         {issue.Description}");
            }
        }
        else
        {
            Console.WriteLine("未发现问题,数据质量良好!");
        }
        
        Console.WriteLine("=".PadRight(60, '='));
    }
}

public class QualityReport
{
    public string LayerName { get; set; }
    public int FeatureCount { get; set; }
    public int FieldCount { get; set; }
    public int ErrorCount { get; set; }
    public int WarningCount { get; set; }
    public List<QualityIssue> Issues { get; set; } = new();
}

public class QualityIssue
{
    public int Fid { get; set; }
    public string IssueType { get; set; }
    public string Description { get; set; }
    public string Severity { get; set; }
}

11.7 小结

本章通过五个实战案例展示了OGU4Net的综合应用:

  1. 批量转换工具:演示了格式转换、编码处理、坐标转换
  2. 空间查询服务:展示了Web API集成、空间关系查询
  3. 国土数据处理:涵盖了TXT文件解析、多边形构建、面积计算
  4. 面积统计工具:实现了投影转换、分组统计
  5. 质量检查工具:包括几何验证、属性检查

这些案例涵盖了实际GIS开发中的常见场景,可以作为开发参考。

posted @ 2025-12-03 17:40  我才是银古  阅读(0)  评论(0)    收藏  举报