C# 可扩展性位置距离搜索全美超过100000个车床位置

C# 可扩展性位置距离搜索全美超过100000个车床位置,c#,sql,distance,geo,haversine,C#,Sql,Distance,Geo,Haversine,场景= 1) 配送办事处遍布美国各地,每个办事处都规定了各自的最大配送半径限制(以英里为单位) 2) 转换为LatLng的目标地址是交付目的地 目标=将配送办公室的数据集(1)返回到目标地址(2)的配送半径限制在该距离内 尝试= 作为我问题的起点,我使用Storm consultancy的优秀工作示例来确定离客户最近的办公室: 我的“办公室”表存储办公室地址、Lat和Lng值以及最大距离“交货限制” 计算Haversine的SQL让我震惊,目前我无法理解 Storm SQL如下所示,但我需要选择

场景=

1) 配送办事处遍布美国各地,每个办事处都规定了各自的最大配送半径限制(以英里为单位)

2) 转换为LatLng的目标地址是交付目的地

目标=将配送办公室的数据集(1)返回到目标地址(2)的配送半径限制在该距离内

尝试=

作为我问题的起点,我使用Storm consultancy的优秀工作示例来确定离客户最近的办公室:

我的“办公室”表存储办公室地址、Lat和Lng值以及最大距离“交货限制”

计算Haversine的SQL让我震惊,目前我无法理解
Storm SQL如下所示,但我需要选择最大距离交付限制小于办公室与客户之间距离的所有办公室行,而不是从直线距离计算中仅选择一行

问题1=如何将最大距离限制过滤器添加到SQL查询中,使其返回包含目标位置的配送区的办公室

问题2=我如何将查询的办公室数量限制为实际可能位于美国目标地区的办公室数量? 例如,如果目标地点是爱达荷州的博伊西,加利福尼亚州洛杉矶的办事处的交货距离限制为300英里。甚至质疑这样的办公室也没有意义。然而,在华盛顿的办事处;俄勒冈州和内华达州北部与爱达荷州接壤的州应包括在搜索查询中,因为有些州可能具有达到本例Boise,ID的最大距离值

Storm使用的Haversine SQL:

SELECT TOP 1 *, ( 3960 * acos( cos( radians( @custLat ) ) *
cos( radians( Lat ) ) * cos( radians(  Lng  ) - radians( @custLng ) ) +
sin( radians( @custLat ) ) * sin( radians(  Lat  ) ) ) ) AS Distance
FROM Offices
ORDER BY Distance ASC
上面的SQL示例仅选择距离目标lat/long(@custLng)最近的办公室

风暴从两个不同方向接近距离计算。上面的SQL是第一个。第二种方法是将办公室坐标保存在内存列表中,并创建一个方法,该方法使用函数在列表上循环计算距离,最后选择最近的坐标,如下所示:

/// <summary>
/// Returns the distance in miles or kilometers of any two
/// latitude / longitude points.
/// </summary>
/// <param name="pos1">Location 1</param>
/// <param name="pos2">Location 2</param>
/// <param name="unit">Miles or Kilometers</param>
/// <returns>Distance in the requested unit</returns>
public double HaversineDistance(LatLng pos1, LatLng pos2, DistanceUnit unit)
{
    double R = (unit == DistanceUnit.Miles) ? 3960 : 6371;
    var lat = (pos2.Latitude - pos1.Latitude).ToRadians();
    var lng = (pos2.Longitude - pos1.Longitude).ToRadians();
    var h1 = Math.Sin(lat / 2) * Math.Sin(lat / 2) +
              Math.Cos(pos1.Latitude.ToRadians()) * 
              Math.Cos(pos2.Latitude.ToRadians()) *
              Math.Sin(lng / 2) * Math.Sin(lng / 2);
    var h2 = 2 * Math.Asin(Math.Min(1, Math.Sqrt(h1)));
    return R * h2;
}

public enum DistanceUnit { Miles, Kilometers };
//
///返回任意两个的距离(以英里或公里为单位)
///纬度/经度点。
/// 
///地点1
///地点2
///英里或公里
///以请求的单位表示的距离
公共双通道距离(LatLng位置1、LatLng位置2、距离单位)
{
双R=(单位==距离单位英里)?3960:6371;
var lat=(pos2.Latitude-pos1.Latitude).ToRadians();
var lng=(pos2.Longitude-pos1.Longitude).ToRadians();
变量h1=数学Sin(lat/2)*数学Sin(lat/2)+
Math.Cos(pos1.lation.ToRadians())*
Math.Cos(pos2.Latitude.ToRadians())*
数学Sin(lng/2)*数学Sin(lng/2);
var h2=2*Math.Asin(Math.Min(1,Math.Sqrt(h1));
返回R*h2;
}
公共枚举距离单位{英里,公里};

var Offices=GetMyOfficeList();
for(int i=0;ix.Distance).Take(1.Single();

可伸缩性很重要,因为我的场景很容易会有超过100000个办公室位置,所以不太可能使用内存中的办公室列表选项

您已经知道办公室和目标位置之间的距离。仅返回距离小于或等于其交付半径的办公室:

SELECT * FROM (SELECT Id, DeliveryRadius, ( 3960 * acos( cos( radians( @custLat ) ) *
    cos( radians( Lat ) ) * cos( radians(  Lng  ) - radians( @custLng ) ) +
    sin( radians( @custLat ) ) * sin( radians(  Lat  ) ) ) ) AS Distance
    FROM Offices)
WHERE Distance <= DeliveryRadius
ORDER BY Distance ASC
SELECT*FROM(选择Id,DeliveryRadius,(3960*acos(弧度(@custrat))*
cos(弧度(Lat))*cos(弧度(Lng)-弧度(@custLng))+
sin(弧度(@custrat))*sin(弧度(Lat)))作为距离
(来自办公室)

其中,@Raduis的距离以英里为单位:

DECLARE @RadiusInDegrees decimal(18,15)
SET @RadiusInDegrees = @Radius / 69.11 * 1.4142
@Radius/69.11给出了以度为单位的半径,但它需要乘以根2才能覆盖整个正方形边界区域

SELECT
    @LatitudeLow = @Latitude - @RadiusInDegrees,
    @LatitudeHigh = @Latitude + @RadiusInDegrees,       
    @LongitudeLow = @Longitude - @RadiusInDegrees,
    @LongitudeHigh = @Longitude + @RadiusInDegrees
使用边界区域会限制进行精确距离计算的次数

[...]
WHERE
    [Latitude] > @LatitudeLow  
    AND [Latitude] < @LatitudeHigh 
    AND [Longitude] > @LongitudeLow
    AND [Longitude] < @LongitudeHigh                
    AND [your exact radius calculation] <= 
      (CASE WHEN [DeliverlyLimit] < @Radius THEN 
          [DeliveryLimit] ELSE @Radius END)
[…]
哪里
[纬度]>@LatitudeLow
和[纬度]<@LatitudeHigh
和[经度]>@LongitudeLow
和[经度]<@LongitudeHigh

和[您的精确半径计算]如果您使用的是Sql2008或更新版本,它内置了特定的类型,使您想做的事情更容易。您需要使用的主要类型是

我将对您的表结构进行一些猜测,但主要是您有一个
位置
和一个
删除区域

创建桌面办公室
(
官方名称varchar(40),
地理位置,
配送距离浮动,--以英里为单位存储
--如果您使用的是SQL2008或2008R2,请将BufferWithCurves替换为旧的缓冲区函数之一
配送区域作为位置。BufferWithCurves(配送距离*1609.34)保持,--1609.34将英里转换为米
)
我在上面的示例中使用了,但这仅适用于Sql2012和更新版本,如果您使用的是2008或2008R2,则需要在insert语句中手动定义您自己的区域

现在填充数据,因为我们制作了
DeliveryArea
一个计算的持久化列,这实际上非常容易。你所需要做的就是把它放在办公室的位置和配送区域的半径上,它会为你计算出该区域的圆。我将使用您在问题中提供的示例:

插入办公室(办公名称、位置、配送距离)
值('Boise,ID',
地理::点(43.6187102,-116.21460684326),--4326表示“纵横”坐标系
300
)
插入办公室(办公室)
[...]
WHERE
    [Latitude] > @LatitudeLow  
    AND [Latitude] < @LatitudeHigh 
    AND [Longitude] > @LongitudeLow
    AND [Longitude] < @LongitudeHigh                
    AND [your exact radius calculation] <= 
      (CASE WHEN [DeliverlyLimit] < @Radius THEN 
          [DeliveryLimit] ELSE @Radius END)