MySQL更快更准确地计算邮政编码之间的距离?

Posted

技术标签:

【中文标题】MySQL更快更准确地计算邮政编码之间的距离?【英文标题】:MySQL Calculate the Distance between Zipcodes Faster and More Accurately? 【发布时间】:2015-09-22 18:59:31 【问题描述】:

我有一个包含 42000 多个邮政编码、经度、纬度和州信息的表格。什么是最准确和最快的查询来返回所有邮政编码的结果,其中所有邮政编码半径为 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

【问题讨论】:

@YourCommonSense 我不会删除sql 标签。因为是通用标签。有了这个标签,只有寻找mysql 的人会看到这个问题 Slop 你当前的代码有什么问题? 对我来说似乎不准确 如何准确?距离错了吗? 78.3232 - 65.3234 是中心邮政编码,不是吗? 【参考方案1】:

关于准确性

准确计算距离的唯一方法是使用 3D 三角,正如您所做的那样。您可以在此处阅读有关该主题的更多信息:https://en.wikipedia.org/wiki/Geographical_distance

虽然给出了邮政编码的 lat/lng 中心点之间的相当准确的距离,但这些中心点是任意挑选的,并且距离是“像乌鸦飞一样”计算出来的,所以你赢了'无法准确表示每个点内两点之间的实际行驶距离。

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

解决该问题的唯一方法是计算地址距离,这需要 USPS 数据将地址映射到更具体的点,或者使用像 Google Maps 这样的 API,它还将计算给定可用道路的实际行驶距离.

关于性能

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

1.减少实时数学

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

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(radians(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 结合使用。

在执行触发之前,您可以通过在子查询中添加 zip 的边界框减少来稍微提高性能,但这可能比您想要的更复杂。

例如,而不是:

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(因为纬度/经度的每十分之一度对应于美国大约 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 亿条记录。

【讨论】:

您可以根据需要执行存储表方法 - 如果您愿意,这是一个机会。您实际上不太可能最终使用所有 10 亿个组合 :) 第一个解决方案很棒,但是为这些字段添加 index 不会提高性能。如果只计算distance ( zip1, zip2) 其中zip1 @JuanCarlosOropeza - 关于索引的要点,忘记了它们正在被相乘/相加。【参考方案2】:

看来您已经知道如何使用 Latitud, Logitud 计算距离

最快的方法是在ZIPCODE附近创建一个环绕框

| 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

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

【讨论】:

【参考方案3】:

这可能是最快的,也可能不是最快的,但您可以通过首先预先计算每个坐标对的法线向量 (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)))

【讨论】:

以上是关于MySQL更快更准确地计算邮政编码之间的距离?的主要内容,如果未能解决你的问题,请参考以下文章

计算两个经纬度点之间的距离? (Haversine 公式)

使用 IATA 代码计算机场之间的距离

mysql中的ST_Distance_Sphere没有给出两个位置之间的准确距离

使用javascript计算两个邮政编码之间的距离

如何使用 doParallel 计算 R 中邮政编码之间的距离?

使用蓝牙计算两部或多部 iPhone 之间的距离