繁体   English   中英

可扩展性位置距离搜索美国超过100,000个LatLng位置

[英]Scalability Location Distance Search Over 100,000 LatLng Positions Across USA

场景=

1)交付局遍布美国,每个交付局都指定了自己的最大交付半径限制(英里数)。

2)转换为LatLng的目标地址geo是传送目的地。

目标 =返回交付办公室(1)的数据集,其交付半径限制在此距离内与目标地址(2)

尝试=

作为我的问题的起点,我使用Storm咨询公司的优秀工作示例来确定与客户最近的办公室: 两点之间的离岸距离

我的“办公室”表存储了办公地址以及他们的Lat和Lng值以及他们的最大距离'deliveryLimit'。

用来计算Haversine的SQL让我大吃一惊,目前还不能理解!
风暴SQL在下面,但我不是从直线距离计算中仅选择一行,而是选择所有办公室行,其最大距离交付限制小于办公室与客户之间的距离。

问题1 =如何将最大距离限制过滤器添加到SQL查询中,以便它返回包含目标位置的交付区域的办公室?

问题2 =如何将查询的办公室数量限制在实际可能位于美国目标区域的办事处数量? 例如,如果目标位置是博伊西,爱达荷州和洛杉矶的办事处,则加利福尼亚的交付距离限制为300英里。 即使查询这些办公室也没有意义。 但是,华盛顿的办事处; 在爱达荷州边境的俄勒冈州和北内华达州应该包括在搜索查询中,因为有些人可能有最大距离值,这达到了博伊西,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 };

var Offices = GetMyOfficeList();
for(int i = 0; i< Offices.Count; i++)
{
    Offices[i].Distance = HaversineDistance(
                        coord,
                        new LatLng(Offices[i].Lat, Offices[i].Lng),
                        DistanceUnit.Miles);
}

var closestOffice = Offices.OrderBy(x => x.Distance).Take(1).Single();

可伸缩性非常重要,因为我的场景可能很容易导致超过100,000个办公地点,因此内存中的办公室列表选项不太可能!

如果您使用的是Sql2008或更新版本,则它具有内置的特定类型,可以使您想要做的更容易。 您需要使用的主要类型是geography

我将对您的表结构进行一些猜测,但主要的是您有一个Location和一个DeleveryArea

create table Offices
(
  OfficeName varchar(40),
  Location geography,
  DeliveryDistance float, --stored in miles
  --If you are on SQL2008 or 2008R2 replace BufferWithCurves with one of the older Buffer functions
  DeliveryArea as Location.BufferWithCurves(DeliveryDistance * 1609.34) PERSISTED, --1609.34 converts miles to meters 
)

我在上面的示例中使用了BufferWithCurves ,但这只适用于Sql2012及更新版本,如果您使用的是2008或2008R2,则需要使用BufferWithToleranceSTBuffer,或者只需在insert参数中手动定义自己的区域。

现在要填充数据,因为我们使DeliveryArea成为计算的持久列,实际上很容易实现。 您需要做的就是放置在办公室的位置和交付区域的半径,它将为您计算该区域的圆。 我将使用您在问题中提供的示例:

insert into Offices (OfficeName, Location, DeliveryDistance) 
values ('Boise, ID', 
        geography::Point(43.6187102,-116.2146068, 4326), --4326 represents a "lat and long" coordinate system
        300
       )

insert into Offices (OfficeName, Location,DeliveryDistance) 
values ('LA, CA', 
        geography::Point(34.0204989,-118.4117325, 4326),
        300
       )

insert into Offices (OfficeName, Location,DeliveryDistance) 
values ('Walla Walla, WI', 
        geography::Point(46.0627549,-118.3337259, 4326),
        300
       )

insert into Offices (OfficeName, Location,DeliveryDistance) 
values ('Baker City, OR', 
        geography::Point(44.7746169,-117.8317284, 4326),
        300
       )

insert into Offices (OfficeName, Location,DeliveryDistance) 
values ('Elko, NV', 
        geography::Point(40.846931,-115.7669825, 4326),
        300
       )

现在您的查询,如果您想找到哪些送货区域为Jordan Vally,Oregon (42.9740245,-117.0533247)提供服务,那么您的查询只是

declare @cust geography = geography::Point(42.9740245,-117.0533247, 4326)

SELECT OfficeName, 
       Location.STDistance(@cust) / 1609.34 AS Distance, --convert back to miles
       Location.Lat as Lat,
       Location.Long as Lng
FROM Offices
where  DeliveryArea.STContains(@cust) = 1
ORDER BY Distance asc

这将为您提供所有办事处,您选择的位置在交付区域内。 关于这个系统真正的DeleveryArea是,如果不是根据位置和删除范围计算DeleveryArea ,你实际上可以给它一组点来勾勒出一个非圆形的地理区域,比如一个城市。

通过精心规划的空间索引,此解决方案甚至可以用于100,000个位置记录集。 如果您想了解有关使用geography一些好处的更多信息,请参阅此SO问题和答案

这是一个SQL小提琴 ,我在上面的工作示例中显示了所有查询。

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

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

您的第二个问题询问如何对办公室进行分区,以便搜索考虑办公室的一部分; 在考虑这样做之前,考虑到您的数据集和延迟要求,我首先要确保您的搜索不够高效。

对于@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)

另外,为了获得更好的性能,如果可以避免,请不要使用SELECT *或子查询。 仅返回ID,并为ID,纬度,经度和DeliveryLimit创建覆盖索引。 将表中的所有数据缓存到一个对象数组中,其中索引是ID,因此从结果集中快速匹配。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM