【问题标题】:Check if dbgeometry dbgeometry/dbgeography point is within a polygon检查 dbgeometry dbgeometry/dbgeography 点是否在多边形内
【发布时间】:2012-12-07 07:21:26
【问题描述】:

我有一个问题希望你们能帮我解决。

我有一个 DbGeometry 点(或 DbGeography,我可以同时使用两者),我想检查它是否在 DbGeometry 多边形(或者 DbGeography)内。

我现在正在这样做:

var dbZones = new List<WasteManager.Database.Zone>();
foreach(var zone in zones)
        {
            var res = from z in DatabaseContext.Zones
                   let boundary =
                       !z.BoundaryGeometry.IsValid
                           ? SqlSpatialFunctions.MakeValid(z.BoundaryGeometry)
                           : z.BoundaryGeometry
                      where z.ID == zone.ID && point.Within(boundary)
                      select z;

            if(res.FirstOrDefault() != null) dbZones.Add(res.FirstOrDefault());

        }

所以我遍历区域(我的数据库的 EF 实体)并检查我拥有的这个点是否在这个边界内。

问题是它不返回任何结果,但我知道该点在该边界内,因为我手动创建了边界和该边界内的点。

谁能告诉我我做错了什么,是否有其他方法可以做到这一点或其他什么?

非常感谢。

曼努埃尔

【问题讨论】:

  • 让我们看看你的边界,以及边界和点的一些实际值
  • 你是如何定义点的?我经常犯切换 lon 和 lat 的错误(因为 sql 将其定义为 (lon, lat) 而不是听起来更自然的 (lat, lon)
  • @tgolisch 这实际上是一个非常好的观点。我 100% 确定我在为我的区域创建边界时犯了一个错误。我将它们输入为“纬度经度”而不是“经度纬度”。该死!而且我还没有保存插入语句,ergo,我必须重新找到地图上的坐标并重新插入它们:(因为它们被存储为二进制数据并且没有办法检索它们,或者存在吗?
  • 您应该能够执行 SQL 查询来选择您的边界作为 GML。 msdn.microsoft.com/en-us/library/bb933884(v=sql.100).aspx
  • 顺便说一句,如果您发现我的答案有用,我将不胜感激一两点。向上箭头,谢谢。

标签: c# sql-server polygon point-in-polygon


【解决方案1】:

除了@BenoitGlaizette,我还想添加评论。

代码polygon.Area.HasValue可能会为某些Multipolygons抛出以下错误。

ArgumentException: 24144: 此操作无法完成,因为 该实例无效。使用 MakeValid 将实例转换为 有效实例。请注意,MakeValid 可能会导致几何的点 实例稍微移动。

但是,如果我们直接转换为SqlGeography,则不会发生这种情况。

public bool IsInside(DbGeography polygon, double longitude, double latitude)
{
    DbGeography point = DbGeography.FromText(string.Format("POINT({1} {0})", latitude.ToString().Replace(',', '.'), longitude.ToString().Replace(',', '.')), DbGeography.DefaultCoordinateSystemId);

    var wellKnownText = polygon.AsText();

    var sqlGeography =
        SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId)
            .MakeValid();

    //Now get the inversion of the above area
    var invertedSqlGeography = sqlGeography.ReorientObject();

    //Whichever of these is smaller is the enclosed polygon, so we use that one.
    if (sqlGeography.STArea() > invertedSqlGeography.STArea())
    {
        sqlGeography = invertedSqlGeography;
    }

    polygon = DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);

    return point.Intersects(polygon);
}

对于那些使用 Entity Framework 5<:>

我使用这种扩展方法检查每个PolygonMultipolygon,然后再将它们保存到数据库中。

public static DbGeography MakePolygonValid(this DbGeography geom)
{
    var wellKnownText = geom.AsText();

    //First, get the area defined by the well-known text using left-hand rule
    var sqlGeography =
        SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId)
            .MakeValid();

    //Now get the inversion of the above area
    var invertedSqlGeography = sqlGeography.ReorientObject();

    //Whichever of these is smaller is the enclosed polygon, so we use that one.
    if (sqlGeography.STArea() > invertedSqlGeography.STArea())
    {
        sqlGeography = invertedSqlGeography;
    }
    return DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);
}

然后我可以使用这样的方法在数据库级别检查Intersects

public static class GeoHelper
{
    public const int SridGoogleMaps = 4326;
    public const int SridCustomMap = 3857;

    public static DbGeography PointFromLatLng(double lat, double lng)
    {
        return DbGeography.PointFromText(
            "POINT("
            + lng.ToString(CultureInfo.InvariantCulture) + " "
            + lat.ToString(CultureInfo.InvariantCulture) + ")",
            SridGoogleMaps);
    }
}

public County GetCurrentCounty(double latitude, double longitude)
{
    var point = GeoHelper.PointFromLatLng(latitude, longitude);

    var county = db.Counties.FirstOrDefault(x =>
        x.Area.Intersects(point));

    return county;
}

Entity Framework 生成的T-SQL:

SELECT TOP (1) 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name], 
[Extent1].[Code] AS [Code], 
[Extent1].[Area] AS [Area]
FROM [Election].[County] AS [Extent1]
WHERE ([Extent1].[Area].STIntersects(@p__linq__0)) = 1


-- p__linq__0: 'POINT (10.0000000 32.0000000)' (Type = Object)

可以像这样手动测试:

declare @p__linq__0 varchar(max)
set @p__linq__0 = 'POINT (10.0000000 32.0000000)' 

SELECT TOP (1) 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[Code] AS [Code], 
    [Extent1].[Area] AS [Area]
    FROM [Election].[County] AS [Extent1]
    WHERE ([Extent1].[Area].STIntersects(@p__linq__0)) = 1

更多信息可以在这里找到:

https://docs.microsoft.com/en-us/sql/t-sql/spatial-geometry/stintersects-geometry-data-type

【讨论】:

    【解决方案2】:

    我想对 Nick Strupat 发表评论。

    您应该小心戒指的方向。 SQL Server 使用左手方向,这意味着如果您沿着多边形的周边行走,您的左手应该在多边形的内侧,而您的右手应该在外侧(逆时针或逆时针)。我得到了“环方向”错误,因为我以相反的方向(顺时针或右手方向)绘制了我的多边形,这意味着 SQL Server 将地球的整个表面都视为多边形的区域,但我的多边形除外。

    要检查一个点是否在多边形中,您应该始终使用point.Intersects(polygon) 而不是!point.Intersects(polygon)

    有一个解决方案可以通过检查区域的大小来检查你的多边形是否正常, 欲了解更多信息,请访问:

    https://blog.falafel.com/ring-orientation-sql-spatial/

    这是我基于博客解释的代码:

        private bool isInside(DbGeography polygon, double longitude, double latitude)
        {
            DbGeography point = DbGeography.FromText(string.Format("POINT({1} {0})", latitude.ToString().Replace(',', '.'), longitude.ToString().Replace(',','.')), DbGeography.DefaultCoordinateSystemId);
    
            // If the polygon area is larger than an earth hemisphere (510 Trillion m2 / 2), we know it needs to be fixed
            if (polygon.Area.HasValue && polygon.Area.Value > 255000000000000L)
            {
                // Convert our DbGeography polygon into a SqlGeography object for the ReorientObject() call
                SqlGeography sqlPolygon = SqlGeography.STGeomFromWKB(new System.Data.SqlTypes.SqlBytes(polygon.AsBinary()), DbGeography.DefaultCoordinateSystemId);
    
                // ReorientObject will flip the polygon so the outside becomes the inside
                sqlPolygon = sqlPolygon.ReorientObject();
    
                // Convert the SqlGeography object back into DbGeography
                polygon = DbGeography.FromBinary(sqlPolygon.STAsBinary().Value);
    
            }
            return point.Intersects(polygon);
        }
    

    【讨论】:

      【解决方案3】:

      其实很简单。

      bool isInside(DbGeometry polygon, double longitude, double latitude) //or DbGeography in your case
      {
          DbGeometry point = DbGeometry.FromText(string.Format("POINT({0} {1})",longitude, latitude), 4326);
          return polygon.Contains(point);    
      }
      

      【讨论】:

      • DbGeography 没有Contains() 成员
      • DbGeography 的等效项似乎是 !point.Intersects(polygon)
      • @NickStrupat 非常有用的评论,尤其是考虑到这是多么违反直觉:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-26
      • 1970-01-01
      • 2011-06-17
      • 1970-01-01
      相关资源
      最近更新 更多