Я б змінив відповідь з gabrielk та пов'язаного допису в блозі , використовуючи індекси бази даних та мінімізуючи кількість фактичних обчислень відстані .
Якщо ви знаєте координати користувача і знаєте максимальну відстань (скажімо, 10 км), ви можете намалювати обмежувальне поле, яке становить 20 км на 20 км, з поточним розташуванням посередині. Отримуйте ці обмежуючі координати та запитуйте лише сховища між цими широтами та довготами . Ще не використовуйте функції тригонометрії у вашому запиті до бази даних, оскільки це не дозволить використовувати індекси. (Таким чином, ви можете отримати магазин, який знаходиться в 12 км від вас, якщо він знаходиться в північно-східному куті обмежувальної коробки, але ми викинемо його на наступному кроці.)
Обчислюйте лише відстань (як птах летить або з фактичними напрямками руху, як вам зручніше) для кількох магазинів, які повертаються. Це різко покращить час обробки, якщо у вас є велика кількість магазинів.
Для відповідного пошуку ( "дайте десять найближчих магазинів" ) ви можете зробити аналогічний пошук, але з початковою відгадкою про відстань (так що ви починаєте з 10 км на 10 км, і якщо у вас недостатньо магазинів, ви розгорнете його на 20 км на 20 км тощо). Для цього початкового здогаду відстані ви один раз обчислюєте кількість магазинів на загальній площі та використовуєте це. Або запишіть кількість потрібних запитів і адаптуйтеся з часом.
Я додав повний приклад коду у відповідне запитання про Майка , і ось розширення, яке дає вам найближчі місця розташування X (швидко та ледь перевірено):
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();