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

时间:2013-07-21 19:06:10

标签: c# wpf geometry-surface gmap.net

我正在寻找一种计算多边形下表面的方法。

我想要完成的事情是使用我的程序的用户可以创建一个多边形来标记他的属性。现在我想知道表面区域是什么,所以我可以告诉用户他的财产有多大。

单位m²或km²或公顷。

多边形的点具有纬度和经度。

我正在使用带有WPF和GMap.NET的C#。地图位于WindowsFormHost中,因此我可以使用GMap.Net中的Winforms,因为这可以覆盖等。

我希望有人可以帮助我,或者给我一个帖子,说明我没有找到这个帖子。

3 个答案:

答案 0 :(得分:5)

使用2D矢量空间近似(局部切线空间)

在本节中,我将详细说明如何使用这些公式。

让我们注意Points多边形的点(其中Points[0] == Points[Points.Count - 1]关闭多边形)。

下一种方法背后的想法是将多边形分成三角形(该区域是所有三角形区域的总和)。但是,为了通过简单的分解(不仅是星形多边形)来支持所有多边形类型,一些三角形贡献是负的(我们有一个“负”区域)。我使用的三角形分解是:{(O, Points[i], Points[i + 1]}其中O是仿射空间的起源。

非自相交多边形的面积(在欧几里德几何中)由下式给出:

在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;
}

在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));
}

在上面的代码中,我假设你有一个带有AddSubtractMultiplyCrossProductDotProduct操作的Vector3结构。

在你的情况下,你有一个经度和经度。然后,您不在2D欧几里德空间。它是一个球形空间,计算任何多边形的面积要复杂得多。 但是,它与2D矢量空间局部同胚(使用切线空间)。 然后,如果您尝试测量的区域不太宽(几公里),则上述公式应该有效。

现在,您只需要找到多边形的法线。为了做到这一点,并减少误差(因为我们接近该区域),我们使用多边形质心处的法线。质心由下式给出:

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;
}

我已将新属性添加到Vector3Vector3.Length

最后,将纬度和经度转换为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));
}

总结一下:

// 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);

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);
    }
}

答案 1 :(得分:1)

我用Cédric的部分代码和来自互联网的代码修复了它。

我使用poly.Points和poly.LocalPoints修复了它。 poly.Points是纬度和经度,而LocalPoints是点,在屏幕上看到地图的中心。

C#库有一个计算距离(km)的功能,所以我计算了距离,然后我计算了LocalPoints中的距离。通过以km为单位的长度潜入localPoints,然后你知道1个Localpoint以km为单位的时间。

代码:

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; }
       }

因为我们使用2D计算表面有一个小错误,但对于我的应用程序,这是可以接受的。

感谢Cédric回答我的问题并帮助我解决问题。

答案 2 :(得分:0)

更容易使用SQL Server 2008或MySql等后端数据库,在查询中将多边形的点发送到服务器并返回区域,长度,参数,交叉点等。

如果这是可行的,请在Sql Server地理/几何数据类型上搜索STArea()或STIntersect。

这是我一直在努力的一个例子。

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();
            }
        }
    }