已知一个Polygon用c#计算其质心

在 C# 中计算多边形质心有多种方法,从使用成熟的几何库到手动实现算法。以下是几种常用方案:

📐 方法一:使用 NetTopologySuite(推荐)

NetTopologySuite (NTS) 是 .NET 中最强大的几何库,提供了完善的质心计算。

1. 安装 NuGet 包

dotnet add package NetTopologySuite

2. 计算质心

using NetTopologySuite.Geometries;
using NetTopologySuite.IO;
using System;

public class CentroidCalculator
{
    /// <summary>
    /// 从 WKT 字符串计算质心
    /// </summary>
    public static Coordinate CalculateCentroidFromWkt(string wkt)
    {
        var reader = new WKTReader();
        var geometry = reader.Read(wkt);
        
        // 计算质心
        var centroid = geometry.Centroid;
        return centroid.Coordinate;
    }
    
    /// <summary>
    /// 从坐标点集合计算质心
    /// </summary>
    public static Coordinate CalculateCentroidFromPoints(Coordinate[] points)
    {
        var geometryFactory = new GeometryFactory();
        var polygon = geometryFactory.CreatePolygon(points);
        var centroid = polygon.Centroid;
        return centroid.Coordinate;
    }
}

// 使用示例
string wkt = "POLYGON((0 0,0 23.622,23.622 23.622,23.622 0,0 0))";
var centroid = CentroidCalculator.CalculateCentroidFromWkt(wkt);
Console.WriteLine($"质心坐标: ({centroid.X:F3}, {centroid.Y:F3})");

📐 方法二:手动实现算法

1. 数学原理

对于简单多边形,质心计算公式:

带符号面积 A = 0.5 * Σ (xi*yi+1 - xi+1*yi)
Cx = (1/(6A)) * Σ (xi + xi+1)*(xi*yi+1 - xi+1*yi)
Cy = (1/(6A)) * Σ (yi + yi+1)*(xi*yi+1 - xi+1*yi)

2. 完整实现

using System;
using System.Collections.Generic;
using System.Windows; // 或自定义 Point 结构

public class ManualCentroidCalculator
{
    /// <summary>
    /// 计算多边形质心(简单多边形,无孔洞)
    /// </summary>
    public static Point CalculateCentroid(IList<Point> polygon)
    {
        if (polygon == null || polygon.Count < 3)
            throw new ArgumentException("多边形至少需要3个点");
        
        double signedArea = 0.0;
        double centroidX = 0.0;
        double centroidY = 0.0;
        
        int count = polygon.Count;
        
        for (int i = 0; i < count; i++)
        {
            int j = (i + 1) % count;
            
            double xi = polygon[i].X;
            double yi = polygon[i].Y;
            double xj = polygon[j].X;
            double yj = polygon[j].Y;
            
            double crossProduct = xi * yj - xj * yi;
            signedArea += crossProduct;
            centroidX += (xi + xj) * crossProduct;
            centroidY += (yi + yj) * crossProduct;
        }
        
        signedArea *= 0.5;
        double factor = 1.0 / (6.0 * signedArea);
        
        centroidX *= factor;
        centroidY *= factor;
        
        return new Point(centroidX, centroidY);
    }
    
    /// <summary>
    /// 从 WKT 字符串解析多边形并计算质心
    /// </summary>
    public static Point CalculateCentroidFromWkt(string wkt)
    {
        var points = ParseWktPolygon(wkt);
        return CalculateCentroid(points);
    }
    
    /// <summary>
    /// 解析 WKT 格式的多边形字符串
    /// </summary>
    private static List<Point> ParseWktPolygon(string wkt)
    {
        // 简单解析 POLYGON((x1 y1, x2 y2, ...))
        var points = new List<Point>();
        
        // 提取坐标部分
        int start = wkt.IndexOf("((") + 2;
        int end = wkt.IndexOf("))");
        string coordsStr = wkt.Substring(start, end - start);
        
        // 分割坐标对
        string[] coordPairs = coordsStr.Split(',');
        
        foreach (var pair in coordPairs)
        {
            string trimmed = pair.Trim();
            string[] xy = trimmed.Split(' ');
            
            if (xy.Length == 2 &&
                double.TryParse(xy[0], out double x) &&
                double.TryParse(xy[1], out double y))
            {
                points.Add(new Point(x, y));
            }
        }
        
        return points;
    }
}

// 使用示例
var polygon = new List<Point>
{
    new Point(0, 0),
    new Point(0, 23.622),
    new Point(23.622, 23.622),
    new Point(23.622, 0),
    new Point(0, 0) // 闭合点
};

var centroid = ManualCentroidCalculator.CalculateCentroid(polygon);
Console.WriteLine($"质心坐标: ({centroid.X:F3}, {centroid.Y:F3})");

// 或直接从 WKT 计算
string wkt = "POLYGON((0 0,0 23.622,23.622 23.622,23.622 0,0 0))";
centroid = ManualCentroidCalculator.CalculateCentroidFromWkt(wkt);
Console.WriteLine($"从WKT计算的质心: ({centroid.X:F3}, {centroid.Y:F3})");

📐 方法三:使用 System.Drawing(适用于 Windows 窗体)

using System.Drawing;
using System.Drawing.Drawing2D;

public class DrawingCentroidCalculator
{
    public static PointF CalculateCentroid(PointF[] polygon)
    {
        if (polygon.Length < 3)
            throw new ArgumentException("多边形至少需要3个点");
        
        using (var path = new GraphicsPath())
        {
            path.AddPolygon(polygon);
            var region = new Region(path);
            var bounds = region.GetBounds();
            
            // 注意:这只是边界框中心,不是几何质心
            // 对于精确质心,仍需使用数学公式
            return new PointF(
                bounds.X + bounds.Width / 2,
                bounds.Y + bounds.Height / 2
            );
        }
    }
}

📊 与 MySQL 结果对比验证

using MySql.Data.MySqlClient;
using NetTopologySuite.Geometries;
using NetTopologySuite.IO;

public class DatabaseCentroidValidator
{
    public static void ValidateWithDatabase()
    {
        string connectionString = "Server=localhost;Database=cdb;Uid=root;Pwd=ketmaC;";
        
        using var connection = new MySqlConnection(connectionString);
        connection.Open();
        
        string query = @"
            SELECT 
                id,
                SpatialID,
                ST_AsText(Profile) as profile_wkt,
                ST_X(ST_Centroid(Profile)) as mysql_centroid_x,
                ST_Y(ST_Centroid(Profile)) as mysql_centroid_y
            FROM steps_instances 
            LIMIT 10";
        
        using var command = new MySqlCommand(query, connection);
        using var reader = command.ExecuteReader();
        
        var wktReader = new WKTReader();
        
        while (reader.Read())
        {
            int id = reader.GetInt32("id");
            string spatialId = reader.GetString("SpatialID");
            string wkt = reader.GetString("profile_wkt");
            double mysqlCentroidX = reader.GetDouble("mysql_centroid_x");
            double mysqlCentroidY = reader.GetDouble("mysql_centroid_y");
            
            // 使用 NTS 计算质心
            var geometry = wktReader.Read(wkt);
            var centroid = geometry.Centroid.Coordinate;
            
            // 对比结果
            Console.WriteLine($"ID: {id}");
            Console.WriteLine($"SpatialID: {spatialId}");
            Console.WriteLine($"MySQL 质心: ({mysqlCentroidX:F6}, {mysqlCentroidY:F6})");
            Console.WriteLine($"C# 质心:   ({centroid.X:F6}, {centroid.Y:F6})");
            Console.WriteLine($"差异: X={Math.Abs(mysqlCentroidX - centroid.X):F6}, Y={Math.Abs(mysqlCentroidY - centroid.Y):F6}");
            Console.WriteLine("---");
        }
    }
}

💡 选择建议

方法 优点 缺点 适用场景
NetTopologySuite 功能完整,支持复杂几何 需要安装包 生产环境、复杂几何处理
手动实现 无依赖,代码透明 需要处理边缘情况 学习原理、简单多边形
System.Drawing .NET 内置 计算不精确 简单图形处理

🔧 性能优化技巧

// 1. 使用 Span<T> 减少内存分配
public static Point CalculateCentroidSpan(Span<Point> polygon)
{
    // 实现类似,但使用 Span 避免数组分配
}

// 2. 并行计算多个多边形的质心
var centroids = polygons.AsParallel()
    .Select(p => CalculateCentroid(p))
    .ToArray();

// 3. 缓存几何对象
private static readonly GeometryFactory _geometryFactory = new GeometryFactory();

推荐使用 NetTopologySuite,因为它经过充分测试,支持各种几何类型,并且与数据库中的空间数据格式兼容。

评论 0

发表评论
支持 Markdown 格式
验证码
点击图片可刷新验证码
💬

暂无评论,快来发表第一条评论吧!