在C#中计算不规则多边形的面积

13

我已经写了一个“白痴式”的如何在C#中计算不规则多边形面积的方法,但我需要它能动态适用于任意数量的顶点

有人可以帮忙吗?

类:

public class Vertex
{
    private int _vertexIdx;
    private double _coordX;
    private double _coordY;
    private double _coordZ;

    public Vertex()
    { }

    public Vertex(int vertexIdx, double coordX, double coordY, double coordZ)
    {
        _vertexIdx = vertexIdx;
        _coordX = coordX;
        _coordY = coordY;
        _coordZ = coordZ;
    }

    public int VertexIdx
    {
        get { return _vertexIdx; }
        set { _vertexIdx = value; }
    }

    public double X
    {
        get { return _coordX; }
        set { _coordX = value; }
    }

    public double Y
    {
        get { return _coordY; }
        set { _coordY = value; }
    }

    public double Z
    {
        get { return _coordZ; }
        set { _coordZ = value; }
    }
}

Form_Load:

->

窗体加载:

List<Vertex> verticies = new List<Vertex>();

verticies.Add(new Vertex(1, 930.9729, 802.8789, 0));
verticies.Add(new Vertex(2, 941.5341, 805.662, 0));
verticies.Add(new Vertex(3, 946.5828, 799.271, 0));
verticies.Add(new Vertex(4, 932.6215, 797.0548, 0));

dataGridView1.DataSource = verticies;

当按钮被按下时计算的代码:(针对4个点的多边形硬编码 - 应该适用于任意数量...)

        // X-coords
        double x1;
        double x2;
        double x3;
        double x4;
        double x5;

        // Y-coords
        double y1;
        double y2;
        double y3;
        double y4;
        double y5;

        // Xn * Yn++
        double x1y2;
        double x2y3;
        double x3y4;
        double x4y5;

        // Yn * Xn++
        double y1x2;
        double y2x3;
        double y3x4;
        double y4x5;

        // XnYn++ - YnXn++
        double x1y2my1x2;
        double x2y3my2x3;
        double x3y4my3x4;
        double x4y5my4x5;

        double result;
        double area;

        x1 = Convert.ToDouble(dataGridView1.Rows[0].Cells[1].Value.ToString());
        y1 = Convert.ToDouble(dataGridView1.Rows[0].Cells[2].Value.ToString());
        txtLog.Text += String.Format("X1 = {0}\tY1 = {1}\r\n", x1, y1);

        x2 = Convert.ToDouble(dataGridView1.Rows[1].Cells[1].Value.ToString());
        y2 = Convert.ToDouble(dataGridView1.Rows[1].Cells[2].Value.ToString());
        txtLog.Text += String.Format("X2 = {0}\tY2 = {1}\r\n", x2, y2);

        x3 = Convert.ToDouble(dataGridView1.Rows[2].Cells[1].Value.ToString());
        y3 = Convert.ToDouble(dataGridView1.Rows[2].Cells[2].Value.ToString());
        txtLog.Text += String.Format("X3 = {0}\tY3 = {1}\r\n", x3, y3);

        x4 = Convert.ToDouble(dataGridView1.Rows[3].Cells[1].Value.ToString());
        y4 = Convert.ToDouble(dataGridView1.Rows[3].Cells[2].Value.ToString());
        txtLog.Text += String.Format("X4 = {0}\tY4 = {1}\r\n", x4, y4);

        // add the start point again
        x5 = Convert.ToDouble(dataGridView1.Rows[0].Cells[1].Value.ToString());
        y5 = Convert.ToDouble(dataGridView1.Rows[0].Cells[2].Value.ToString());
        txtLog.Text += String.Format("X5 = {0}\tY5 = {1}\r\n", x5, y5);
        txtLog.Text += "\r\n";

        // Multiply 
        x1y2 = x1 * y2;
        x2y3 = x2 * y3;
        x3y4 = x3 * y4;
        x4y5 = x4 * y5;

        y1x2 = y1 * x2;
        y2x3 = y2 * x3;
        y3x4 = y3 * x4;
        y4x5 = y4 * x5;

        // Subtract from each other
        x1y2my1x2 = x1y2 - y1x2;
        x2y3my2x3 = x2y3 - y2x3; 
        x3y4my3x4 = x3y4 - y3x4;
        x4y5my4x5 = x4y5 - y4x5;

        // Sum all results
        result = x1y2my1x2 + x2y3my2x3 + x3y4my3x4 + x4y5my4x5;
        area = Math.Abs(result / 2);

        txtLog.Text += String.Format("Area = {0}\r\n", area);

示例输出:

X1 = 930.9729 Y1 = 802.8789

X2 = 941.5341 Y2 = 805.662

X3 = 946.5828 Y3 = 799.271

X4 = 932.6215 Y4 = 797.0548

X5 = 930.9729 Y5 = 802.8789

面积 = 83.2566504099523


1
我之前见过的典型方法是将多边形分割成三角形,然后您可以简单地对所有三角形的面积求和。然而,这并不容易,因为它需要根据多边形的复杂度(交叉边缘、孔洞、凸形/凹形等)使用不同的算法。 - Lasse V. Karlsen
你可以考虑在类似于 Stack Overflow 的网站 http://mathoverflow.net/ 上提出这个问题,该网站仅限于数学问题。请确保将问题表述为非编程问题,并询问算法方法。 - Lasse V. Karlsen
3
MathOverflow是为想要讨论研究生级别数学问题的专业数学家而设立的。 - Eric Lippert
1
好的,难怪这一切对我来说都像是外语 :) - Lasse V. Karlsen
4个回答

25

使用lambda表达式,这变得很简单!

var points = GetSomePoints();

points.Add(points[0]);
var area = Math.Abs(points.Take(points.Count - 1)
   .Select((p, i) => (points[i + 1].X - p.X) * (points[i + 1].Y + p.Y))
   .Sum() / 2);

这个算法的解释可以在这里找到:

该方法将多边形边缘下投到X轴上定义的梯形面积相加。当程序考虑到一个多边形的底边时,计算会给出负数,因此需要减去多边形与坐标轴之间的空间,留下多边形的实际面积。

如果多边形是顺时针方向的,则计算出的总面积为负数,所以函数返回绝对值。

对于非简单多边形(边交叉),该方法会给出奇怪的结果。


链接的“解释”并没有真正解释算法,它只是以不同的方式呈现了代码。 - cp.engr
1
虽然我很欣赏简洁明了,但我必须部分同意@cp.engr的观点;这可能不是最易维护(可读性高)的解决方案。如果我最终采用这样的方法,希望我能发布一个更简单易懂的替代方案。 - Nathan majicvr.com
重要提示:为了使此功能正常工作,您定义的多边形必须是封闭的,即第一个点必须在末尾重复。请参见 LinqPad Gist 示例:https://gist.github.com/elliz/9d7da1fa74a76d84be4600dcbc121bd6 - Ruskin

5
public float Area(List<PointF> vertices)
{
    vertices.Add(vertices[0]);
    return Math.Abs(vertices.Take(vertices.Count - 1).Select((p, i) => (p.X * vertices[i + 1].Y) - (p.Y * vertices[i + 1].X)).Sum() / 2);
}

1
请提供一些解释,以使您的答案更易于理解。 - Carsten

5

类似这样的一个平面多边形的代码(在记事本中编写):

static double GetDeterminant(double x1, double y1, double x2, double y2)
{
    return x1 * y2 - x2 * y1;
}

static double GetArea(IList<Vertex> vertices)
{
    if(vertices.Count < 3)
    {
        return 0;
    }
    double area = GetDeterminant(vertices[vertices.Count - 1].X, vertices[vertices.Count - 1].Y, vertices[0].X, vertices[0].Y);
    for (int i = 1; i < vertices.Count; i++)
    {
        area += GetDeterminant(vertices[i - 1].X, vertices[i - 1].Y, vertices[i].X, vertices[i].Y);
    }
    return area / 2;
}

尽管您的方法没有关注Z轴,但我建议应用一些变换来摆脱它:如果多边形不是平面,您将无法获得面积,而如果它是平面,则可以摆脱第三维。

那么,计算2D和3D中的面积有所不同? - Riaan
是的,只有一点点。面积仅适用于平面对象计算。因此,您的多边形必须是平面的 - 所有顶点都应位于同一平面上,否则无法计算面积。问题在于,这个平面不总是像您的示例中那样是Z = 0平面。您应该考虑到这一点,并在计算面积之前适当处理点。 - Li0liQ

0

我发现,在计算大约600,000个多边形的面积时,上面显示的基本公式对于一些多边形有效,但很多多边形的结果相差甚远。我正在使用https://geojson.io/检查我的结果 - 它返回了非常复杂的带洞多边形(例如中间的湖泊)的正确结果。为了计算复杂多边形的正确面积,我最终使用了与geojson.io相同的系统 - 一个客户端js库Turf.js,请参见https://turfjs.org/docs/#area

在这张图片中,您可以看到我的第一次尝试,然后是使用Turf.js的第二次尝试 - 那里有一列显示第一次尝试与第二次尝试的正确程度比率,其中1是相同的计算。您可以看到它们大多数都很接近,但有些相差10倍或更糟。

enter image description here

这里是一些示例代码来进行计算。我将200加载到屏幕上,然后在JS中运行计算,并将结果通过ajax返回到服务器端数据库。

            $(document).ready(function () {
            var items = $('.geojson');
            for (var sc = 0; sc < items.length; sc++) {
                var id = $(items[sc]).attr('data-id');
                var polyData = JSON.parse($(items[sc]).find('.geojson-data').html());
                //console.log('[' + polyData + ']');
                var polygon = turf.polygon(polyData.coordinates);

                var area = turf.area(polygon);
                console.log('id[' + id + ']sqm[' + area + ']');

                    
                $(items[sc]).closest('tr').find('.data-id').html(id);
                var answerDom = $(items[sc]).closest('tr').find('.answer');

                if (true) {
                    $.ajax({
                        url: 'calc-poly-area-from-geojson-turf-scheduled.aspx',
                        type: "get",
                        data: {id:id, area: area },
                        invokedata: { answerDom: answerDom, area: area },
                        async: true,//run all at the same time
                        cache: false,
                        success: function (data) {
                            //console.log('test email done');

                            $(this.invokedata.answerDom).html('ok:' + this.invokedata.area);
                            $(this.invokedata.answerDom).removeClass('answer');

                            if ($('.answer').length == 0) {
                                window.location.reload();
                            }
                        },
                        error: function (msg) {
                            console.log("call failed: " + msg.responseText);
                            $(el).html('error in call');

                            //prompt('copy this',url+'?'+qs)
                        }
                    });
                }
            }
        });

        window.setTimeout(function () { window.location.reload(); }, 10000);

这里是一个示例多边形

{"type":"Polygon", "coordinates":[[[171.519147876006,-43.809111826162],[171.519264282931,-43.8094307100015],[171.519615782201,-43.8097268361192],[171.519874096036,-43.8097860548424],[171.525264107563,-43.8176887926426],[171.525356625489,-43.8179845471556],[171.525750029905,-43.8185636705947],[171.526002901974,-43.8187934292356],[171.526154917292,-43.8189686576417],[171.526249645477,-43.8191111884506],[171.526245660987,-43.819269203656],[171.526032299227,-43.8200263808647],[171.524134038501,-43.8268225827224],[171.523301803308,-43.8297987275054],[171.523129147529,-43.8301621243769],[171.522991616155,-43.8300725313285],[171.52248605771,-43.8302181414427],[171.522128893843,-43.8304084928376],[171.521558488905,-43.8304389785399],[171.521371202269,-43.830481916342],[171.521023295734,-43.8309120441211],[171.520774217465,-43.8310054055632],[171.520589483523,-43.8311387384524],[171.515210823266,-43.8294163992962],[171.514763136723,-43.8292736695248],[171.496256757791,-43.8233680542711],[171.494338310605,-43.8227558913632],[171.493450128779,-43.8224739752289],[171.493221517911,-43.8223838125259],[171.493001278557,-43.8222877021167],[171.492654147639,-43.821801588707],[171.491048512765,-43.8200169686591],[171.488157604579,-43.8168246695455],[171.488051808197,-43.8166695752984],[171.487648717141,-43.8162207994268],[171.486147094889,-43.8145461538075],[171.482241975825,-43.8101769774879],[171.481683765874,-43.8095751045999],[171.480858016595,-43.8085443728491],[171.481124633337,-43.8086557677844],[171.481334008334,-43.8085534985925],[171.481540735171,-43.8083379086683],[171.4815994175,-43.8077828104991],[171.481763314624,-43.8074471226617],[171.481812168914,-43.8064706917151],[171.48196041271,-43.8063093336607],[171.482260412185,-43.8062322290662],[171.482916004007,-43.8059780008537],[171.494844864468,-43.8013540958407],[171.501308718774,-43.7988446798756],[171.506019390319,-43.797017657826],[171.508275460952,-43.7961421972998],[171.508430707528,-43.7960805551645],[171.509117292333,-43.7963432108869],[171.510511038963,-43.7968679021071],[171.513299102675,-43.8007637699317],[171.513465917258,-43.8010892007185],[171.513696634335,-43.8013818859084],[171.513929550742,-43.8016136793445],[171.514114411714,-43.8018826827151],[171.514305634465,-43.8021912982997],[171.51440028511,-43.8024789426394],[171.514828618996,-43.8028429251794],[171.51494106207,-43.8031623582355],[171.515852739466,-43.8044825303059],[171.516111930457,-43.8047763591375],[171.517116748697,-43.8062534995253],[171.517374596163,-43.8065473602078],[171.517549793874,-43.8068229401963],[171.5176213721,-43.8070824951625],[171.517796573697,-43.8073580748019],[171.518070610117,-43.8076087983324],[171.518880109148,-43.8088563353488],[171.519147876006,-43.809111826162]]]}

您可以将其粘贴到geojson.io中检查他们的区域(点击poly,然后单击“属性”选项卡)。

enter image description here


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