MySQL更快速地计算Zipcodes之间的距离?

时间:2015-09-22 18:59:32

标签: mysql sql

我有一张包含42000多个zipcodes,经度,纬度和状态信息的表格。什么是最准确和最快速的查询返回结果,所有包含25英里半径的邮政编码的邮政编码?

当前代码(我认为不准确)

SELECT
zipcode, (
  3959 * acos (
  cos ( radians(78.3232) )
  * cos( radians( latitude ) )
  * cos( radians( longitude ) - radians(65.3234) )
  + sin ( radians(78.3232) )
  * sin( radians( latitude ) )
)
) AS distance
FROM Location
HAVING distance < 25
ORDER BY distance

3 个答案:

答案 0 :(得分:1)

关于准确性

准确计算距离的唯一方法是使用3D trig,正如您所做的那样。您可以在此处详细了解该主题:https://en.wikipedia.org/wiki/Geographical_distance

虽然拉链码的纬度/经度中心点之间的距离非常准确,但是这些中心点是任意选择的,并且距离是按照“乌鸦飞行”计算的,所以你赢了无法准确表示每个点内两点之间的实际行程距离。

例如,您可能在相邻的邮政编码中有两个相邻的房屋,或者每个邮政编码的两端有两个房屋,根据此计算,这两个房屋将按等距计算。

纠正该问题的唯一方法是计算地址距离,这需要USPS数据将地址映射到更具体的点,或者使用像谷歌地图这样的API,这也会计算给定可用道路的实际行程距离

关于效果

有几种方法可以加快查询速度。

<强> 1。减少实时数学

实时计算的最快方法是在表中的列中预先计算并存储昂贵的trig值,例如:

ALTER TABLE Location
    ADD COLUMN cos_rad_lat DOUBLE,
    ADD COLUMN cos_rad_lng DOUBLE,
    ADD COLUMN sin_rad_lat DOUBLE;

然后

UPDATE Location
SET cos_rad_lat = cos(radians(latitude)),
    cos_rad_lng = cos(radians(longitude)),
    sin_rad_lat = sin(radians(latitude));

在查询之外进行cos(弧度(78.3232))类型计算,以便不对每行数据进行数学运算。

因此,将所有计算减少到常量值(在获取SQL之前)和计算列将使您的查询看起来像这样:

SELECT
    zipcode,
    3959 * acos(
        0.20239077538110228
        * cos_rad_lat
        * cos_rad_lng - 1.140108408597264
    )
    + 0.979304842243025 * sin_rad_lat AS distance
FROM Location
HAVING distance < 25
ORDER BY distance

<强> 2。边界框缩减

注意:您可以将其与方法1结合使用。

你可以通过在执行trig之前在子查询中添加拉链的边界框减少来略微提高性能,但这可能比你想要的更复杂。

例如,而不是:

FROM Location

你可以做到

FROM (
    SELECT * 
    FROM Location 
    WHERE latitude BETWEEN A and B
        AND longitude BETWEEN C and D
) AS Location

其中A,B,C和D是与您的中心点相对应的数字+ - 约为0.3(因为每10度lat / lng对应于美国约5-7英里)。

此方法在-180 / 180经度时变得棘手,但这不会影响美国。

第3。存储所有计算距离 你可以做的另一件事是预先计算所有拉链的所有距离,然后存储在一个单独的表中

CREATE TABLE LocationDistance (
    zipcode1 varchar(5) NOT NULL REFERENCES Location(zipcode),
    zipcode2 varchar(5) NOT NULL REFERENCES Location(zipcode)
    distance double NOT NULL,
    PRIMARY KEY (zipcode1, zipcode2),
    INDEX (zipcode1, distance)
);

使用zip及其计算距离的每个组合填充此表格。

您的查询将如下所示:

SELECT zipcode2
FROM LocationDistance 
WHERE zipcode1 = 12345
    AND distance < 25;

这是迄今为止最快的解决方案,但它涉及存储大约10亿条记录。

答案 1 :(得分:0)

您似乎已经知道如何使用Latitud, Logitud

计算距离

最快的方法是创建靠近ZIPCODE

的surronding框
| X-25, Y-25 |            | X+25, Y-25 |        

                 X , Y

| X-25, Y+25 |            | X+25, Y+25 |        

所以创建4个变量

Xleft = X - 25miles
Xright = X + 25miles
Ytop = Y - 25miles
Ybottom = Y + 25miles

然后,如果纬度和纵向具有索引,则此查询几乎是即时的

SELECT *
FROM
  Location
WHERE 
    latitud between Xleft AND Xright
AND longitud between Ytop AND Ybottom

enter image description here

使用正方形会出现一些错误,但您会过滤掉大多数错误的zipcodes。然后,您可以使用更小的数据集进行原始查询。

答案 2 :(得分:0)

这可能是也可能不是最快的,但您可以先为每个坐标对预先计算法向量(NV),然后根据X,Y和Z分量表示向量:

NV = [Nx, Ny, Nz]

,其中

Nx = cos(radians(latitude))*cos(radians(longitude))
Ny = cos(radians(latitude))*sin(radians(longitude))
Nz = sin(radians(latitude))

然后可以通过确定两个法向量NV1和NV2的差异并使用三维的毕达哥拉斯方程来计算任意两个坐标之间的距离,以获得两个点之间的直线距离,即弦长C:

C = SQRT(dx^2+dy^2+dz^2)

,其中

dx = Nx1-Nx2
dy = Ny1-Ny2
dz = Nz1-Nz2

然后可以使用以下公式找到大圆距离:

D = arcsin(C/2)*2*R

其中R是球体的半径,在这种情况下是地球,即3959mi。

全部放在一起:

 select pt2.zip
      , asin(power(power(pt1.nx-pt2.nx,2)
                  +power(pt1.ny-pt2.ny,2)
                  +power(pt1.nz-pt2.nz,2)
            ,.5)/2)*2*3959 distance
   from (select 78.3232 lattitude
              , 65.3234 longitude
              , cos(radians(78.3232))*cos(radians(65.3234)) nx
              , cos(radians(78.3232))*sin(radians(65.3234)) ny
              , sin(radians(78.3232)) nz
        ) pt1
      , (select zip
              , lattitude
              , longitude
              , cos(radians(latitude))*cos(radians(longitude)) nx
              , cos(radians(latitude))*sin(radians(longitude)) ny
              , sin(radians(latitude)) nz
           from location) pt2
having distance < 25;

要进一步优化此项,您可以计算坐标上的某些边界。每个纬度都大约等于69英里,因此您可以将搜索限制在那些纬度±(D / 69)。然而,每度经度的里程数随着纬度变化而变化,从赤道每度约69英里到极点零或69 * cos(纬度),你使用±(D / 69 * cos(纬度)) )。

 where pt2.latitude  between pt1.latitude - 25/69
                         and pt1.latitude + 25/69
   and pt2.longitude between pt1.longitude - 25/(69*cos(radians(abs(pt1.latitude)+25/69)))
                         and pt1.longitude + 25/(69*cos(radians(abs(pt1.latitude)+25/69)))