如何有效地查找给定位置附近的最近位置

2022-08-30 19:05:10

我正在制作一个脚本,其中将大量业务加载到具有纬度和经度的mySQL数据库中。然后,我为该脚本提供纬度和经度(最终用户的),并且该脚本必须计算从提供的纬度/经度到它从数据库中获取的每个条目的距离,并按最接近最远的顺序对它们进行排序。

实际上,我只需要大约10或20个“最接近”的结果,但是除了从数据库获取所有结果并对每个结果运行函数然后进行数组排序之外,我想不出要这样做。

这就是我已经拥有的:

<?php

function getDistance($point1, $point2){

    $radius      = 3958;      // Earth's radius (miles)
    $pi          = 3.1415926;
    $deg_per_rad = 57.29578;  // Number of degrees/radian (for conversion)

    $distance = ($radius * $pi * sqrt(
                ($point1['lat'] - $point2['lat'])
                * ($point1['lat'] - $point2['lat'])
                + cos($point1['lat'] / $deg_per_rad)  // Convert these to
                * cos($point2['lat'] / $deg_per_rad)  // radians for cos()
                * ($point1['long'] - $point2['long'])
                * ($point1['long'] - $point2['long'])
        ) / 180);

    $distance = round($distance,1);
    return $distance;  // Returned using the units used for $radius.
}

include("../includes/application_top.php");

$lat = (is_numeric($_GET['lat'])) ? $_GET['lat'] : 0;
$long = (is_numeric($_GET['long'])) ? $_GET['long'] : 0;

$startPoint = array("lat"=>$lat,"long"=>$long);

$sql = "SELECT * FROM mellow_listings WHERE active=1"; 
$result = mysql_query($sql);

while($row = mysql_fetch_array($result)){
    $thedistance = getDistance($startPoint,array("lat"=>$row['lat'],"long"=>$row['long']));
    $data[] = array('id' => $row['id'],
                    'name' => $row['name'],
                    'description' => $row['description'],
                    'lat' => $row['lat'],
                    'long' => $row['long'],
                    'address1' => $row['address1'],
                    'address2' => $row['address2'],
                    'county' => $row['county'],
                    'postcode' => strtoupper($row['postcode']),
                    'phone' => $row['phone'],
                    'email' => $row['email'],
                    'web' => $row['web'],
                    'distance' => $thedistance);
}

// integrate google local search
$url = "http://ajax.googleapis.com/ajax/services/search/local?";
$url .= "q=Off+licence";    // query
$url .= "&v=1.0";           // version number
$url .= "&rsz=8";           // number of results
$url .= "&key=ABQIAAAAtG"
        ."Pcon1WB3b0oiqER"
        ."FZ-TRQgsWYVg721Z"
        ."IDPMPlc4-CwM9Xt"
        ."FBSTZxHDVqCffQ2"
        ."W6Lr4bm1_zXeYoQ"; // api key
$url .= "&sll=".$lat.",".$long;

// sendRequest
// note how referer is set manually
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_REFERER, /* url */);
$body = curl_exec($ch);
curl_close($ch);

// now, process the JSON string
$json = json_decode($body, true);

foreach($json['responseData']['results'] as $array){

    $thedistance = getDistance($startPoint,array("lat"=>$array['lat'],"long"=>$array['lng']));
    $data[] = array('id' => '999',
                    'name' => $array['title'],
                    'description' => '',
                    'lat' => $array['lat'],
                    'long' => $array['lng'],
                    'address1' => $array['streetAddress'],
                    'address2' => $array['city'],
                    'county' => $array['region'],
                    'postcode' => '',
                    'phone' => $array['phoneNumbers'][0],
                    'email' => '',
                    'web' => $array['url'],
                    'distance' => $thedistance);

}

// sort the array
foreach ($data as $key => $row) {
$id[$key] = $row['id'];
$distance[$key] = $row['distance'];
}

array_multisort($distance, SORT_ASC, $data); 

header("Content-type: text/xml"); 


echo '<?xml version="1.0" encoding="UTF-8"?>'."\n";
echo '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'."\n";
echo '<plist version="1.0">'."\n";
echo '<array>'."\n";

for($i = 0; isset($distance[$i]); $i++){
    //echo $data[$i]['id']." -> ".$distance[$i]."<br />";
    echo '<dict>'."\n";
        foreach($data[$i] as $key => $val){
            echo '<key><![CDATA['.$key.']]></key>'."\n";
            echo '<string><![CDATA['.htmlspecialchars_decode($val, ENT_QUOTES).']]></string>'."\n";
        }
    echo '</dict>'."\n";
}

echo '</array>'."\n";
echo '</plist>'."\n";
?>

现在,这运行速度足够快,数据库中只有2或3个企业,但我目前正在将5k企业加载到数据库中,我担心它会为每个条目运行这个非常慢?你觉得怎么样?

它也不是我可以缓存的那种数据,因为两个用户具有相同的纬度/长度的可能性非常罕见,因此无济于事。

我该怎么办?

感谢您的任何帮助和任何建议。他们都非常感谢。


答案 1

我认为你试图实现的目标可以在SQL中使用Haversine公式更好地完成。Google有一个关于如何在MySQL数据库中获取最近位置的教程,但一般的想法是这个SQL:

SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) )
  * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) 
  * sin( radians( lat ) ) ) ) AS distance
FROM markers
HAVING distance < 25
ORDER BY distance LIMIT 0 , 20;

然后,您需要做的所有工作都在数据库上完成,因此您不必在检查距离之前将所有业务都拉入PHP脚本中。


答案 2

选项 1:通过切换到支持 GeoIP 的数据库对数据库执行计算。

选项2:在数据库上进行计算:您正在使用MySQL,因此以下存储过程应该会有所帮助

CREATE FUNCTION distance (latA double, lonA double, latB double, LonB double)
    RETURNS double DETERMINISTIC
BEGIN
    SET @RlatA = radians(latA);
    SET @RlonA = radians(lonA);
    SET @RlatB = radians(latB);
    SET @RlonB = radians(LonB);
    SET @deltaLat = @RlatA - @RlatB;
    SET @deltaLon = @RlonA - @RlonB;
    SET @d = SIN(@deltaLat/2) * SIN(@deltaLat/2) +
    COS(@RlatA) * COS(@RlatB) * SIN(@deltaLon/2)*SIN(@deltaLon/2);
    RETURN 2 * ASIN(SQRT(@d)) * 6371.01;
END//

编辑

如果数据库中有关于纬度和经度的索引,则可以通过在 PHP 中计算初始边界框($minLat、$maxLat、$minLong 和 $maxLong),并根据该范围将行限制为条目的子集($MINLAT 和 $maxLat 和 $minLong 和 $maxLong 之间的经度),来减少需要计算的计算次数。然后,MySQL只需要为该行子集执行距离计算。

进一步编辑(作为上一次编辑的解释)

如果您只是使用 Jonathon 提供的 SQL 语句(或用于计算距离的存储过程),则 SQL 仍必须查看数据库中的每条记录,并计算数据库中每条记录的距离,然后才能决定是返回该行还是丢弃该行。

由于计算的执行速度相对较慢,因此最好减少需要计算的行集,消除明显超出所需距离的行,以便我们只对较少数量的行执行昂贵的计算。

如果你认为你正在做的事情基本上是在地图上画一个圆,以你的初始点为中心,并带有一个距离半径;然后公式只是标识哪些行落在该圆圈内...但它仍然必须检查每一行。

使用边界框就像首先在地图上绘制一个正方形,左,右,上和下边缘与中心点的适当距离。然后,我们将在该框中绘制我们的圆,圆上的最北,最东,最南和最西的点与框的边界相接触。有些行会落在该框之外,因此SQL甚至懒得尝试计算这些行的距离。它仅计算落在边界框内的行的距离,以查看它们是否也落在圆内。

在 PHP 中,我们可以使用一个非常简单的计算,根据我们的距离计算出最小和最大纬度和经度,然后在 SQL 语句的 WHERE 子句中设置这些值。这实际上是我们的盒子,任何超出盒子的东西都会被自动丢弃,而不需要实际计算其距离。

Movable Type网站上对此有很好的解释(使用PHP代码),对于任何计划在PHP中进行任何地理定位工作的人来说,这都应该是必不可少的读物。


推荐