【问题标题】:Get all Longitude and Latitude within a radius without spatial type Entity Framework在没有空间类型实体框架的情况下获取半径内的所有经度和纬度
【发布时间】:2019-03-06 17:10:08
【问题描述】:

我们有一个以经纬度为列的邮政编码表,由于生产需要,无法更改为空间数据类型。

正在使用STDistance(),但出现错误:

LINQ to Entities 无法识别该方法

因此我必须调用toList 然后只使用STDistance(),这会将每个邮政编码都拉入系统并导致性能下降。

private static List<string> GetPostalCodesWithinRange( DbContext db, double latitude, double longitude)
{
    var yourLocation = SqlGeography.Point(latitude, longitude, 4326);

    var query = from postal in db.POSTALS
                                 .Where(x => x.LATITUDE != null || 
                                             x.LONGITUDE != null)
                                 .ToList()
    let distance = SqlGeography.Point((double)postal.LATITUDE.Value, (double)postal.LONGITUDE.Value, 4326).STDistance(yourLocation)
                    .Value
                where postal.LATITUDE != null && postal.LONGITUDE != null &&  distance < 3000
                orderby distance
                select postal.POSTAL_CODE;

    return query.Distinct().ToList();
}

目前PostalstoList()方法只过滤掉空的纬度和经度

db.POSTALS.Where(x => x.LATITUDE != null || x.LONGITUDE != null).ToList()

由于我们只检查 3km 以内的经纬度,现在我们想优化查询,只拉取位于 3km 正方形边界内的经度和纬度,然后使用上述方法仅按半径过滤。

请问有什么方法可以得到距离中心1.5公里的角落longlat点?

【问题讨论】:

  • 出于好奇,您是否有兴趣添加一个计算列,该列是基于现有 lat 和 long 列的空间点?
  • @BenThul 这可能是一个很好的解决方案,但就目前而言,我打算在不更改生产数据库的情况下这样做。
  • 您可以创建一个使用 STDistance 函数的 T-SQL 存储过程,并使用 LINQ to Entities 调用该存储过程。如果您的生产系统不允许您创建存储过程,或者您可以使用 STDistance 函数编写 SQL 查询并使用 SQLQuery() 直接从 LINQ 执行 SQL 到实体。这些中的任何一个都可以消除计算角点的需要!

标签: sql-server entity-framework sqlgeography


【解决方案1】:

SQL Server 地理函数“STBuffer”将缓冲区距离添加到输入地理。您可以向 STBuffer 输入一个点,您将得到一个近似于以该点为中心的圆的多边形。然后,您可以找到该多边形的最小和最大 LAT/LONG。

(这在很远的距离上并不准确,因为地球是一个球体,但对于 1.5 公里的半径,它已经足够近了;它也会在接近 180 度经度时分解,因为接近这个你去 -180度。)

忽略这些警告,这里有一个函数可以做到这一点:

public static void GetBorderBounds(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
{
    var buffer = point.STBuffer(distanceMeters);

    minLat = (double)point.Lat;
    maxLat = (double)point.Lat;
    minLong = (double)point.Long;
    maxLong = (double)point.Long;
    for (int i = 1; i <= buffer.STNumPoints(); i++)
    {
        var p = buffer.STPointN(i);
        if (p.Lat < minLat) { minLat = (double)p.Lat; }
        if (p.Long < minLong) { minLong = (double)p.Long; }
        if (p.Lat > minLat) { maxLat = (double)p.Lat; }
        if (p.Long > maxLong) { maxLong = (double)p.Long; }
    }
}

以下是将此函数添加到原始查询的方法:

private static List<string> GetPostalCodesWithinRange(DbContext db, double latitude, double longitude)
{
    var yourLocation = SqlGeography.Point(latitude, longitude, 4326);

    double minLat1, minLong1, maxLat1, maxLong1;
    GetBorderBounds(yourLocation, 1500, out minLat1, out minLong1, out maxLat1, out maxLong1);

    var query = from postal in db.POSTALS
                                 .Where(x => x.LATITUDE != null ||
                                             x.LONGITUDE != null)
                                 .ToList()
                let distance = SqlGeography.Point((double)postal.LATITUDE.Value, (double)postal.LONGITUDE.Value, 4326).STDistance(yourLocation)
                                .Value
                where postal.LATITUDE != null && postal.LONGITUDE != null && distance < 3000
                && postal.LATITUDE > minLat1 && postal.LATITUDE < maxLat1
                && postal.LONGITUDE > minLong1 && postal.LONGITUDE < maxLong1
                orderby distance
                select postal.POSTAL_CODE;

    return query.Distinct().ToList();
}

【讨论】:

    【解决方案2】:

    在我使用 STBuffer 发布我的第一个答案后,我意识到有一种更简单的方法可以让角点与该点保持一定距离。

    我保留原来的 STBuffer 答案,因为它没有错,它可以作为使用 STBuffer 和迭代 Geography 对象中的点的示例。

    这里是 GetBorderBounds 的简单实现:

    public static void GetBorderBounds2(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
    {
        var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326));
        var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long + 1.0, 4326));
        minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
        maxLat = (double)(point.Lat + distanceMeters / metresPerDegreeLat);
    
        minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
        maxLong = (double)(point.Long + distanceMeters / metresPerDegreeLong);
    }
    

    此实施在北极和南极附近出现故障。此外,与第一个解决方案一样,如果边界越过 180 度子午线,它会给出错误的答案。

    如果你所有的点都远离两极或第 180 条子午线,这没关系。

    如果您的点可能靠近两极或第 180 条子午线,这里有一个防弹实现:

    public static void GetBorderBounds3(SqlGeography point, double distanceMeters, out double minLat, out double minLong, out double maxLat, out double maxLong)
    {
        // Near the North pole: 
        // Select whole circle of longitude from North pole to latitude south of point by distance
        if (point.Lat >= 89)
        {
            minLat = (double)(point.Lat - distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat - 1.0, (double)point.Long, 4326)));
            maxLat = 90;
            minLong = -180;
            maxLong = 180;
            return;
        }
    
        // Near the South pole: 
        // Select whole circle of longitude from South pole to latitude north of point by distance metres
        if (point.Lat <= -89)
        {
            minLat = -90;
            maxLat = (double)(point.Lat + distanceMeters / point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326)));
            minLong = -180;
            maxLong = 180;
            return;
        }
    
        var metresPerDegreeLat = point.STDistance(SqlGeography.Point((double)point.Lat + 1.0, (double)point.Long, 4326));
        var metresPerDegreeLong = point.STDistance(SqlGeography.Point((double)point.Lat, (double)point.Long + 1.0, 4326));
        minLat = (double)(point.Lat - distanceMeters / metresPerDegreeLat);
        maxLat = (double)(point.Lat + distanceMeters / metresPerDegreeLat);
        minLong = (double)(point.Long - distanceMeters / metresPerDegreeLong);
        maxLong = (double)(point.Long + distanceMeters / metresPerDegreeLong);
    
        // If we cross the 180th meridian, select the whole circle of longitude:
        if (minLong < -180 || maxLong > 180.0)
        {
            minLong = -180;
            maxLong = 180;
        }
    } 
    

    【讨论】:

      猜你喜欢
      • 2010-12-15
      • 1970-01-01
      • 1970-01-01
      • 2015-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多