Linq to sql Haversine公式

3
我有一份Harversine公式的c#实现以及TSQL的示例。不确定如何在服务器端最好地实现该公式,以便能够在Linq查询中使用它。
理想情况下,我希望将我的本地公式链接到服务器上的函数。这样可以避免“无法翻译为sql”错误,并使一切变得简单顺畅。
显然,对于问题的任何看法都很有帮助。
我了解SQL2008中的Geography类型。但是,我正在使用的代码库已经对Linq to SQL有很大的依赖,所以我认为这可能不值得花费这么多精力!
谢谢
2个回答

5

为什么不完全使用SQL进行计算,因为这是最好的方法,并简单地获得一个已经填充了距离的表格呢?

来自现有答案

CREATE FUNCTION dbo.udf_Haversine(@lat1 float, @long1 float, @lat2 float, @long2 float) RETURNS float 
  BEGIN
    DECLARE @dlon float, @dlat float, @rlat1 float, @rlat2 float, @rlong1 float, @rlong2 float, @a float, @c float, @R float, @d float, @DtoR float

    SELECT @DtoR = 0.017453293
    SELECT @R = 3937 --3976

    SELECT 
        @rlat1 = @lat1 * @DtoR,
        @rlong1 = @long1 * @DtoR,
        @rlat2 = @lat2 * @DtoR,
        @rlong2 = @long2 * @DtoR

    SELECT 
        @dlon = @rlong1 - @rlong2,
        @dlat = @rlat1 - @rlat2

    SELECT @a = power(sin(@dlat/2), 2) + cos(@rlat1) * cos(@rlat2) * power(sin(@dlon/2), 2)
    SELECT @c = 2 * atn2(sqrt(@a), sqrt(1-@a))
    SELECT @d = @R * @c

    RETURN @d 
  END

并且像这样使用:

var table = from r in db.VenuePostCodes 
            select new {
                lat = r.Latitude,
                lng = r.Longitude,
                name = r.Name,
                distance = db.udf_Haversine(
                                  r.Latitude,r.Longitude,
                                  r.Latitude,r.Longitude2)
            };

但是最好的方法是将所有内容存储在SQL中,这样您的托管服务器就少了一些工作量,只需添加一个VIEW到您的SQL中并调用该视图即可,让我们来想象一下:

SELECT 
   latitude, longitude, name, latitude1, longitude2, postcode, 
   udf_Haversine(latitude, longitude, latitude2, longitude2) AS distance 
FROM 
   venuepostcodes
ORDER BY 
   distance

并使用LINQ直接调用该视图。


我同意这是最好的方法。虽然可以在LINQ表达式中定义Haversine公式,但它会很丑陋,并包含大量冗余计算(例如,由于无法使用表达式块,因此在计算“c”时将无法存储“a”的值,因此必须计算两次)。省去麻烦,遵循@balexandre的示例。 - Ethan Brown
谢谢,我已经创建了一个函数来完成它。但是我无论如何都想不出如何从Linq中调用它!!谢谢。我会考虑使用专门的视图来实现地理功能。 - Ben Ford
你需要先将那个 SQL 函数添加到你的实体中。如果你正在使用 edmx 文件,只需右键单击并选择“函数导入...”。 - balexandre
我只是在使用Linq2SQL。理想情况下,我希望能够在L2S部分类中访问UDF。这样我就可以创建myFoo.NearestBars(),并找到按距离排序的所有酒吧。 - Ben Ford
对于纯C#,可以使用此示例,但它需要一些时间来处理,如果不是异步的话,可能会阻塞UI...最好让SQL Server来进行计算。 - balexandre

0

@balexandre的回答很好,但我对提供的SQL函数不满意(缺少注释,有有趣的注释掉的常量,是英里?公里?等等...)

CREATE FUNCTION [dbo].[udf_Haversine](@lat1 float, @long1 float, @lat2 float, @long2 float) RETURNS float 
BEGIN
    DECLARE @dlon float, @dlat float,
            @rlat1 float, @rlat2 float, @rlong1 float, @rlong2 float,
            @a float, @c float, @R float, @d float, @DtoR float

    SELECT
        @DtoR = PI() / 180, -- Degrees to radians const
        @R = 6371 -- Radius of Earth in KM

    SELECT 
        @rlat1 = @lat1 * @DtoR,
        @rlong1 = @long1 * @DtoR,
        @rlat2 = @lat2 * @DtoR,
        @rlong2 = @long2 * @DtoR

    SELECT 
        @dlat = @rlat1 - @rlat2,
        @dlon = @rlong1 - @rlong2

    SELECT @a = SIN(@dlat / 2) * SIN(@dlat / 2) +
                SIN(@dlon / 2) * SIN(@dlon / 2) * COS(@rlat2) * COS(@rlat1)

    SELECT @c = 2 * atn2(sqrt(@a), sqrt(1 - @a))
    SELECT @d = @R * @c -- Final distance in KM

    SELECT @d = @d * 0.621371192 -- Final distance in miles

RETURN @d 
END

这是从此处获取的 JavaScript 实现转换而来,最后还添加了将其转换为英里的功能:

// Converted from JavaScript implementation:
// http://www.movable-type.co.uk/scripts/latlong.html

var R = 6371; // km
var dLat = (lat2-lat1).toRad();
var dLon = (lon2-lon1).toRad();
var lat1 = lat1.toRad();
var lat2 = lat2.toRad();

var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
        Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2); 
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
var d = R * c;

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