Tôi sẽ sửa đổi câu trả lời từ gabrielk và bài đăng trên blog được liên kết bằng cách sử dụng các chỉ mục cơ sở dữ liệu và giảm thiểu số lượng tính toán khoảng cách thực tế .
Nếu bạn biết tọa độ của người dùng và bạn biết khoảng cách tối đa (giả sử là 10km), bạn có thể vẽ một hộp giới hạn 20km x 20km với vị trí hiện tại ở giữa. Nhận các tọa độ giới hạn và chỉ truy vấn lưu trữ giữa các vĩ độ và kinh độ này . Chưa sử dụng các hàm lượng giác trong truy vấn cơ sở dữ liệu của bạn, vì điều này sẽ ngăn các chỉ mục được sử dụng. (Vì vậy, bạn có thể có được một cửa hàng cách bạn 12km nếu nó ở góc đông bắc của hộp giới hạn, nhưng chúng tôi sẽ ném nó ra trong bước tiếp theo.)
Chỉ tính khoảng cách (khi chim bay hoặc với chỉ đường lái xe thực tế, tùy thích) cho một vài cửa hàng được trả lại. Điều này sẽ cải thiện đáng kể thời gian xử lý nếu bạn có một số lượng lớn các cửa hàng.
Đối với tra cứu liên quan ( "cung cấp mười cửa hàng gần nhất" ), bạn có thể thực hiện tra cứu tương tự, nhưng với dự đoán khoảng cách ban đầu (vì vậy bạn bắt đầu với diện tích 10km x 10km và nếu bạn không có đủ cửa hàng, bạn hãy mở rộng 20km bằng 20km và cứ thế). Đối với khoảng cách ban đầu này, bạn có thể tính toán số lượng cửa hàng trên tổng diện tích và sử dụng số đó. Hoặc đăng nhập số lượng truy vấn cần thiết và thích ứng theo thời gian.
Tôi đã thêm một ví dụ mã đầy đủ vào câu hỏi liên quan của Mike và đây là tiện ích mở rộng cung cấp cho bạn các vị trí X gần nhất (nhanh chóng và hầu như không được kiểm tra):
class Monkeyman_Geo_ClosestX extends Monkeyman_Geo
{
public static $closestXStartDistanceKm = 10;
public static $closestXMaxDistanceKm = 1000; // Don't search beyond this
public function addAdminPages()
{
parent::addAdminPages();
add_management_page( 'Location closest test', 'Location closest test', 'edit_posts', __FILE__ . 'closesttest', array(&$this, 'doClosestTestPage'));
}
public function doClosestTestPage()
{
if (!array_key_exists('search', $_REQUEST)) {
$default_lat = ini_get('date.default_latitude');
$default_lon = ini_get('date.default_longitude');
echo <<<EOF
<form action="" method="post">
<p>Number of posts: <input size="5" name="post_count" value="10"/></p>
<p>Center latitude: <input size="10" name="center_lat" value="{$default_lat}"/>
<br/>Center longitude: <input size="10" name="center_lon" value="{$default_lon}"/></p>
<p><input type="submit" name="search" value="Search!"/></p>
</form>
EOF;
return;
}
$post_count = intval($_REQUEST['post_count']);
$center_lon = floatval($_REQUEST['center_lon']);
$center_lat = floatval($_REQUEST['center_lat']);
var_dump(self::getClosestXPosts($center_lon, $center_lat, $post_count));
}
/**
* Get the closest X posts to a given location
*
* This might return more than X results, and never more than
* self::$closestXMaxDistanceKm away (to prevent endless searching)
* The results are sorted by distance
*
* The algorithm starts with all locations no further than
* self::$closestXStartDistanceKm, and then grows this area
* (by doubling the distance) until enough matches are found.
*
* The number of expensive calculations should be minimized.
*/
public static function getClosestXPosts($center_lon, $center_lat, $post_count)
{
$search_distance = self::$closestXStartDistanceKm;
$close_posts = array();
while (count($close_posts) < $post_count && $search_distance < self::$closestXMaxDistanceKm) {
list($north_lat, $east_lon, $south_lat, $west_lon) = self::getBoundingBox($center_lat, $center_lon, $search_distance);
$geo_posts = self::getPostsInBoundingBox($north_lat, $east_lon, $south_lat, $west_lon);
foreach ($geo_posts as $geo_post) {
if (array_key_exists($geo_post->post_id, $close_posts)) {
continue;
}
$post_lat = floatval($geo_post->lat);
$post_lon = floatval($geo_post->lon);
$post_distance = self::calculateDistanceKm($center_lat, $center_lon, $post_lat, $post_lon);
if ($post_distance < $search_distance) {
// Only include those that are in the the circle radius, not bounding box, otherwise we might miss some closer in the next step
$close_posts[$geo_post->post_id] = $post_distance;
}
}
$search_distance *= 2;
}
asort($close_posts);
return $close_posts;
}
}
$monkeyman_Geo_ClosestX_instace = new Monkeyman_Geo_ClosestX();