简体   繁体   English

C#GMap.Net计算多边形的表面

[英]C# GMap.Net calculate surface of polygon

I am searching for a way to calculate the surface under a polygon. 我正在寻找一种计算多边形下的曲面的方法。

The thing I want to accomplish is that a user that uses my program, can create a polygon to mark out his property. 我要完成的事情是使用我的程序的用户可以创建一个多边形来标记其属性。 Now I want to know what the surface area is so I can tell the user how big his property is. 现在,我想知道表面积是多少,所以我可以告诉用户他的属性有多大。

Unit m² or km² or hectare. 单位m²或km²或公顷。

The points of the polygon have a latitude and longitude. 多边形的点具有经度和纬度。

I am using C# with WPF and GMap.NET. 我在WPF和GMap.NET中使用C#。 The map is in a WindowsFormHost so I can use the Winforms thing from GMap.Net because this provoides overlays etc. 该地图位于WindowsFormHost因此我可以使用GMap.Net中的Winforms,因为这样可以避免覆盖等。

I hope that someone can help me or show me a post where this is explained that I didn't found. 我希望有人可以帮助我或给我看一篇帖子,其中没有找到我的解释。

Using a 2D vector space approximation (local tangent space) 使用2D向量空间近似值(局部切线空间)

In this section, I can detail how I come to these formulas. 在本节中,我将详细介绍如何得出这些公式。

Let's note Points the points of the polygon (where Points[0] == Points[Points.Count - 1] to close the polygon). 让我们注意Points多边形的点(其中, Points[0] == Points[Points.Count - 1]可以关闭多边形)。

The idea behind the next methods is to split the polygon into triangles (the area is the sum of all triangle areas). 下一个方法背后的想法是将多边形分成三角形(面积是所有三角形面积的总和)。 But, to support all polygon types with a simple decomposition (not only star-shaped polygon), some triangle contributions are negative (we have a "negative" area). 但是,为了通过简单的分解支持所有多边形类型(不仅是星形多边形),某些三角形的贡献为负(我们有一个“负”区域)。 The triangles decomposition I use is : {(O, Points[i], Points[i + 1]} where O is the origin of the affine space. 我使用的三角形分解为: {(O, Points[i], Points[i + 1]} ,其中O是仿射空间的原点。

The area of a non-self-intersecting polygon (in euclidian geometry) is given by: 非自相交多边形(欧几里德几何)的面积由下式给出:

In 2D: 在2D模式下:

float GetArea(List<Vector2> points)
{
    float area2 = 0;
    for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
    {
        MyPoint point = points[numPoint];
        MyPoint nextPoint = points[numPoint + 1];
        area2 += point.x * nextPoint.y - point.y * nextPoint.x;
    }
    return area2 / 2f;
}

In 3D, given normal , the unitary normal of the polygon (which is planar): 在3D中,给normal ,多边形(平面)的单一法线:

float GetArea(List<Vector3> points, Vector3 normal)
{
    Vector3 vector = Vector3.Zero;
    for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
    {
        MyPoint point = points[numPoint];
        MyPoint nextPoint = points[numPoint + 1];
        vector += Vector3.CrossProduct(point, nextPoint);
    }
    return (1f / 2f) * Math.Abs(Vector3.DotProduct(vector, normal));
}

In the previous code I assumed you have a Vector3 struct with Add , Subtract , Multiply , CrossProduct and DotProduct operations. 在前面的代码中,我假定您具有一个具有AddSubtractMultiplyCrossProductDotProduct操作的Vector3结构。

In your case, you have a lattitude and longitude. 就您而言,您有一个经度和纬度。 Then, you are not in an 2D euclidean space. 然后,您不在2D欧式空间中。 It is a spheric space where computing the area of any polygon is much more complex. 这是一个球形空间,计算任何多边形的面积要复杂得多。 However, it is locally homeomorphic to a 2D vector space (using the tangent space). 但是,它对于2D向量空间是局部同胚的(使用切线空间)。 Then, if the area you try to measure is not too wide (few kilometers), the above formula should work. 然后,如果您尝试测量的区域不是太宽(几公里),则上述公式应适用。

Now, you just have to find the normal of the polygon. 现在,您只需要查找多边形的法线。 To do so, and to reduce the error (because we are approximating the area), we use the normal at the centroid of the polygon. 为此,并为了减少误差(因为我们近似于面积),我们在多边形的质心处使用法线。 The centroid is given by: 重心由下式给出:

Vector3 GetCentroid(List<Vector3> points)
{
    Vector3 vector = Vector3.Zero;
    Vector3 normal = Vector3.CrossProduct(points[0], points[1]);  // Gets the normal of the first triangle (it is used to know if the contribution of the triangle is positive or negative)
    normal = (1f / normal.Length) * normal;  // Makes the vector unitary
    float sumProjectedAreas = 0;
    for (int numPoint = 0; numPoint < points.Count - 1; numPoint++)
    {
        MyPoint point = points[numPoint];
        MyPoint nextPoint = points[numPoint + 1];
        float triangleProjectedArea = Vector3.DotProduct(Vector3.CrossProduct(point, nextPoint), normal);
        sumProjectedAreas += triangleProjectedArea;
        vector += triangleProjectedArea  * (point + nextPoint);
    }
    return (1f / (6f * sumProjectedAreas)) * vector;
}

I've added a new property to Vector3 : Vector3.Length 我向Vector3添加了一个新属性: Vector3.Length

Finally, to convert latitude and longitude into a Vector3 : 最后,将经纬度转换为Vector3

Vector3 GeographicCoordinatesToPoint(float latitude, float longitude)
{
    return EarthRadius * new Vector3(Math.Cos(latitude) * Math.Cos(longitude), Math.Cos(latitude) * Math.Sin(longitude), Math.Sin(latitude));
}

To sum up: 总结一下:

// Converts the latitude/longitude coordinates to 3D coordinates
List<Vector3> pointsIn3D = (from point in points
                            select GeographicCoordinatesToPoint(point.Latitude, point.Longitude))
                           .ToList();

// Gets the centroid (to have the normal of the vector space)
Vector3 centroid = GetCentroid(pointsIn3D );

// As we are on a sphere, the normal at a given point is the colinear to the vector going from the center of the sphere to the point.
Vector3 normal = (1f / centroid.Length) * centroid;  // We want a unitary normal.

// Finally the area is computed using:
float area = GetArea(pointsIn3D, normal);

The Vector3 struct Vector3结构

public struct Vector3
{
    public static readonly Vector3 Zero = new Vector3(0, 0, 0);

    public readonly float X;
    public readonly float Y;
    public readonly float Z;

    public float Length { return Math.Sqrt(X * X + Y * Y + Z * Z); }

    public Vector3(float x, float y, float z)
    {
         X = x;
         Y = y;
         Z = z;
    }

    public static Vector3 operator +(Vector3 vector1, Vector3 vector2)
    {
        return new Vector3(vector1.X + vector2.X, vector1.Y + vector2.Y, vector1.Z + vector2.Z);
    }
    public static Vector3 operator -(Vector3 vector1, Vector3 vector2)
    {
        return new Vector3(vector1.X - vector2.X, vector1.Y - vector2.Y, vector1.Z - vector2.Z);
    }
    public static Vector3 operator *(float scalar, Vector3 vector)
    {
        return new Vector3(scalar * vector.X, scalar * vector.Y, scalar * vector.Z);
    }

    public static float DotProduct(Vector3 vector1, Vector3 vector2)
    {
        return vector1.X * vector2.X + vector1.Y * vector2.Y + vector1.Z * vector2.Z;
    }
    public static Vector3 CrossProduct(Vector3 vector1, Vector3 vector2)
    {
        return return new Vector3(vector1.Y * vector2.Z - vector1.Z * vector2.Y,
                                  vector1.Z * vector2.X - vector1.X * vector2.Z,
                                  vector1.X * vector2.Y - vector1.Y * vector2.X);
    }
}

I fixed it with part of the code from Cédric and code from the internet. 我用Cédric的部分代码和Internet的代码修复了它。

I fixed it by using poly.Points and poly.LocalPoints. 我通过使用poly.Points和poly.LocalPoints修复了它。 The poly.Points are the latitude and longitude while the LocalPoints are points see to the center of the map on the screen. poly.Points是纬度和经度,而LocalPoints是在屏幕上看到地图中心的点。

the C# library has a function to calculate the distance (km) so I calculted the distance and then I calculated the distance in LocalPoints. C#库具有计算距离(km)的功能,因此我计算了距离,然后以LocalPoints计算距离。 Dived the localPoints throug the length in km and then you know how long 1 Localpoint is in km. 通过以公里为单位的长度除以localPoints,然后知道1 Localpoint以km为单位的长度。

Code: 码:

List<PointLatLng> firstTwoPoints = new List<PointLatLng>();
         firstTwoPoints.Add(poly.Points[0]);
         firstTwoPoints.Add(poly.Points[1]);

         GMapPolygon oneLine = new GMapPolygon(firstTwoPoints,"testesddfsdsd"); //Create new polygone from messuring the distance.
         double lengteLocalLine =
            Math.Sqrt(((poly.LocalPoints[1].X - poly.LocalPoints[0].X)*(poly.LocalPoints[1].X - poly.LocalPoints[0].X)) +
                      ((poly.LocalPoints[1].Y - poly.LocalPoints[0].Y)*(poly.LocalPoints[1].Y - poly.LocalPoints[0].Y))); //This calculates the length of the line in LocalPoints. 
         double pointInKm = oneLine.Distance / lengteLocalLine; //This gives me the length of 1 LocalPoint in km.

         List<Carthesian> waarden = new List<Carthesian>(); 

//Here we fill the list "waarden" with the points.
//NOTE: the last value is NOT copied because this is handled in calculation method.
         foreach (GPoint localPoint in poly.LocalPoints)
         {
            waarden.Add(new Carthesian(){X = (localPoint.X * pointInKm), Y = (localPoint.Y * pointInKm)});
         }

         MessageBox.Show("" + GetArea(waarden)*1000000);

    }

//Method for calculating area
      private double GetArea(IList<Carthesian> points)
      {
         if (points.Count < 3)
         {
            return 0;
         }
         double area = GetDeterminant(points[points.Count - 1].X , points[points.Count - 1].Y, points[0].X, points[0].Y);

         for (int i = 1; i < points.Count; i++)
         {
            //Debug.WriteLine("Lng: " + points[i].Lng + "   Lat:" + points[i].Lat);
            area += GetDeterminant(points[i - 1].X, points[i - 1].Y , points[i].X,  points[i].Y);
         }
         return Math.Abs(area / 2);
      }

//Methode for getting the Determinant

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



//This class is just to make it nicer to show in code and it also was from previous tries
    class Carthesian
       {
          public double X { get; set; }
          public double Y { get; set; }
          public double Z { get; set; }
       }

Because we calculate the surface using 2D there is a small error, but for my application this is acceptable. 因为我们使用2D计算表面,所以误差很小,但是对于我的应用程序来说,这是可以接受的。

And thanks to Cédric for answering my question and helping me to fix the problem I had. 感谢Cédric回答了我的问题并帮助我解决了所遇到的问题。

Its much easier to just use a backend database like SQL Server 2008 or MySql, sending the points of the polygon to the server in a query and return area, length, parimeter, intersection...etc. 仅使用后端数据库(如SQL Server 2008或MySql),将多边形的点以查询和返回区域,长度,比重,交集等方式发送到服务器,就容易得多。

If this is viable, search STArea() or STIntersect on Sql Server geography/geometry datatypes. 如果可行,请在Sql Server地理/几何数据类型上搜索STArea()或STIntersect。

here is an example of something I have been working on. 这是我一直在努力的一个例子。

using Microsoft.SqlServer.Types;
using System.Data.SqlClient;

    GMap.NET.WindowsForms.GMapOverlay o = new GMapOverlay();
    GMap.NET.WindowsForms.GMapOverlay o1 = new GMapOverlay();
    List<PointLatLng> p = new List<PointLatLng>();
    List<string> p1 = new List<string>();

//below adds a marker to the map upon each users click. At the same time adding that Lat/Long to a <PointLatLng> list
    private void gMapControl1_MouseClick(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right )
        {
            p.Add(new PointLatLng(Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat), Convert.ToDouble(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng)));
            p1.Add(gMapControl2.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl2.FromLocalToLatLng(e.X, e.Y).Lat);
            GMarkerGoogle marker = new GMarkerGoogle(gMapControl2.FromLocalToLatLng(e.X, e.Y), GMarkerGoogleType.red_small);
           // marker.Tag =(gMapControl1.FromLocalToLatLng(e.X, e.Y).Lng + " " + gMapControl1.FromLocalToLatLng(e.X, e.Y).Lat);

            o.Markers.Add(marker);
            gMapControl2.Overlays.Add(o);
        }
    }

//Then the below code will add that <PointLatLng> List to a SQL query and return Area and Centoid of polygon. Area is returned in Acres
 private void gMapControl1_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            try
            {
                o.Clear();

                n = new GMapPolygon(p, "polygon");
                n.Fill = new SolidBrush(Color.Transparent);

                n.Stroke = new Pen(Color.Red, 1);
                o.Polygons.Add(n);

                gMapControl2.Overlays.Add(o);
                StringBuilder a = new StringBuilder();
                StringBuilder b = new StringBuilder();
                p1.ToArray();

                for (int i = 0; i != p1.Count; i++)
                {
                    a.Append(p1[i].ToString() + ",");
                }
                a.Append(p1[0].ToString());

                cs.Open();
                SqlCommand cmd = new SqlCommand("Declare @g geography; set @g = 'Polygon((" + a + "))'; Select Round((@g.STArea() *0.00024711),3) As Area", cs);
                SqlCommand cmd1 = new SqlCommand("Declare @c geometry; set @c =geometry::STGeomFromText('Polygon((" + a + "))',0);  Select Replace(Replace(@c.STCentroid().ToString(),'POINT (',''),')','')AS Center", cs);

                poly = "Polygon((" + a + "))";

                SqlDataReader sdr = cmd.ExecuteReader();

                while (sdr.Read())
                {
                    txtArea.Text = sdr["Area"].ToString();
                }
                sdr.Close();

                SqlDataReader sdr1 = cmd1.ExecuteReader();
                while (sdr1.Read())
                {
                    center = sdr1["Center"].ToString();
                    lat = center.Substring(center.IndexOf(" ") + 1);
                    lat = lat.Remove(9);
                    lon = center.Substring(0, (center.IndexOf(" ")));
                    lon = lon.Remove(10);
                    txtCenter.Text = lat + ", " + lon;
                }

                sdr1.Close();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Please start the polygon over, you must create polygon in a counter-clockwise fasion","Counter-Clockwise Only!",MessageBoxButtons.OK,MessageBoxIcon.Error);
            }
            finally
            {
                p.Clear();
                p1.Clear();
                cs.Close();
                o.Markers.Clear();
            }
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM