如何计算质心

12

我正在处理地理空间形状,并查看这里的重心算法:

http://en.wikipedia.org/wiki/Centroid#Centroid_of_polygon

我已经用C#实现了类似这样的代码(只需适应):

Finding the centroid of a polygon?

class Program
{
    static void Main(string[] args)
    {
        List<Point> vertices = new List<Point>();

        vertices.Add(new Point() { X = 1, Y = 1 });
        vertices.Add(new Point() { X = 1, Y = 10 });
        vertices.Add(new Point() { X = 2, Y = 10 });
        vertices.Add(new Point() { X = 2, Y = 2 });
        vertices.Add(new Point() { X = 10, Y = 2 });
        vertices.Add(new Point() { X = 10, Y = 1 });
        vertices.Add(new Point() { X = 1, Y = 1 });

        Point centroid = Compute2DPolygonCentroid(vertices);
    }

    static Point Compute2DPolygonCentroid(List<Point> vertices)
    {
        Point centroid = new Point() { X = 0.0, Y = 0.0 };
        double signedArea = 0.0;
        double x0 = 0.0; // Current vertex X
        double y0 = 0.0; // Current vertex Y
        double x1 = 0.0; // Next vertex X
        double y1 = 0.0; // Next vertex Y
        double a = 0.0;  // Partial signed area

        // For all vertices except last
        int i=0;
        for (i = 0; i < vertices.Count - 1; ++i)
        {
            x0 = vertices[i].X;
            y0 = vertices[i].Y;
            x1 = vertices[i+1].X;
            y1 = vertices[i+1].Y;
            a = x0*y1 - x1*y0;
            signedArea += a;
            centroid.X += (x0 + x1)*a;
            centroid.Y += (y0 + y1)*a;
        }

        // Do last vertex
        x0 = vertices[i].X;
        y0 = vertices[i].Y;
        x1 = vertices[0].X;
        y1 = vertices[0].Y;
        a = x0*y1 - x1*y0;
        signedArea += a;
        centroid.X += (x0 + x1)*a;
        centroid.Y += (y0 + y1)*a;

        signedArea *= 0.5;
        centroid.X /= (6*signedArea);
        centroid.Y /= (6*signedArea);

        return centroid;
    }
}

public class Point
{
    public double X { get; set; }
    public double Y { get; set; }
}
问题在于当我拥有这样一个形状(即L形状)时,该算法给出的结果为(3.62,3.62),但该点在形状外部。是否有其他算法可以考虑到这一点?
基本上,一个人会在地图上绘制一个形状。这个形状可能跨越多条道路(所以可能是L形),我想计算出这个形状的中心。这样我就可以在那个点处找到道路名称。如果他们画了一个又长又瘦的L形状,它在形状外部就没有意义了。

4
多边形的质心不一定在其内部。这仅适用于多边形。 - Hong Ooi
是的,我同意该算法是正确的,但是否有另一种算法可以确保计算多边形内部的点?理想情况下,上述形状的结果应为(1.5,1.5)。 - peter
如果对您的问题有意义,您可以将道路表示为粗线甚至联合或矩形(从而引入轴的概念)。您的中心是该轴的中点。 - Om Deshmane
不要使用点来查找道路名称。这样做无法保证该点足够接近被勾画的道路。正确的做法是检索该区域内的道路线段,并确定哪条道路在多边形中具有最长的长度。 - CodeSlinger
5个回答

20

这篇答案受到 Jer2654 的回答以及以下来源的启发: http://coding-experiments.blogspot.com/2009/09/xna-quest-for-centroid-of-polygon.html

  /// <summary>
  /// Method to compute the centroid of a polygon. This does NOT work for a complex polygon.
  /// </summary>
  /// <param name="poly">points that define the polygon</param>
  /// <returns>centroid point, or PointF.Empty if something wrong</returns>
  public static PointF GetCentroid(List<PointF> poly)
  {
     float accumulatedArea = 0.0f;
     float centerX = 0.0f;
     float centerY = 0.0f;

     for (int i = 0, j = poly.Count - 1; i < poly.Count; j = i++)
     {
        float temp = poly[i].X * poly[j].Y - poly[j].X * poly[i].Y;
        accumulatedArea += temp;
        centerX += (poly[i].X + poly[j].X) * temp;
        centerY += (poly[i].Y + poly[j].Y) * temp;
     }

     if (Math.Abs(accumulatedArea) < 1E-7f)
        return PointF.Empty;  // Avoid division by zero

     accumulatedArea *= 3f;
     return new PointF(centerX / accumulatedArea, centerY / accumulatedArea);
  }

如果(Math.Abs(accumulatedArea) < 1E-7f) - Koray
1
@Koray 谢谢!我不太明白这个的必要性,但我认为你是对的,我已经在我的答案中包含了你的建议 - 以及我自己在使用此代码时的一个Java版本,也用于Android应用程序。 - RenniePet
3
你的代码运行得很好,但是如果某些坐标为负数,则需要进行以下操作。如果可以保证每个坐标都是正数,那么就不需要这样做。谢谢。 - Koray

7
public static Point GetCentroid( Point[ ] nodes, int count )
{
    int x = 0, y = 0, area = 0, k;
    Point a, b = nodes[ count - 1 ];

    for( int i = 0; i < count; i++ )
    {
        a = nodes[ i ];

        k = a.Y * b.X - a.X * b.Y;
        area += k;
        x += ( a.X + b.X ) * k;
        y += ( a.Y + b.Y ) * k;

        b = a;
    }
    area *= 3;

    return ( area == 0 ) ? Point.Empty : new Point( x /= area, y /= area );
}

1
看起来这是基于与此处使用的相同算法: http://coding-experiments.blogspot.com/2009/09/xna-quest-for-centroid-of-polygon.html - RenniePet

5

0

对于3D点,我在C#中创建了一个方法,希望能帮到你:

public static double[] GetCentroid(List<double[]> listOfPoints)
    {
        // centroid[0] = X
        // centroid[1] = Y
        // centroid[2] = Z
        double[] centroid = new double[3];

        // List iteration
        // Link reference:
        // https://en.wikipedia.org/wiki/Centroid
        foreach (double[] point in listOfPoints)
        {
            centroid[0] += point[0];
            centroid[1] += point[1];
            centroid[2] += point[2];
        }

        centroid[0] /= listOfPoints.Count;
        centroid[1] /= listOfPoints.Count;
        centroid[2] /= listOfPoints.Count;

        return centroid;
    }

-1

我的实现:

GpsCoordinates GetCentroid(ICollection<GpsCoordinates> polygonCorners)
{
    return new GpsCoordinates(polygonCorners.Average(x => x.Latitude), polygonCorners.Average(x => x.Longitude));
}

public readonly struct GpsCoordinates
{
    public GpsCoordinates(
        double latitude,
        double longitude
        )
    {
       
        Latitude = latitude;
        Longitude = longitude;
    }

    public double Latitude { get; }
    public double Longitude { get; }
}

这个想法来自这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接