Ben veritabanı dizinleri kullanarak ve gerçek mesafe hesaplamaları sayısını en aza indirerek gabrielk ve bağlantılı blog yazı cevap değiştirirsiniz .
Kullanıcının koordinatlarını ve maksimum mesafeyi (10km diyelim) biliyorsanız, geçerli konum ortada 20km x 20km olan bir sınırlayıcı kutu çizebilirsiniz. Bu sınırlayıcı koordinatları alın ve yalnızca bu enlemler ve boylamlar arasındaki depoları sorgulayın . Veritabanı sorgunuzda henüz trigonometri işlevlerini kullanmayın; bu, dizinlerin kullanılmasını önler. (Yani sınırlayıcı kutunun kuzey-doğu köşesinde ise sizden 12km uzakta bir mağaza alabilirsiniz, ancak bir sonraki adımda dışarı atarız.)
Sadece iade edilen birkaç mağaza için mesafeyi (kuş uçarken veya gerçek sürüş yönleriyle) hesaplayın. Bu, çok sayıda mağazanız varsa işlem süresini önemli ölçüde artıracaktır.
İlgili arama için ( "en yakın on mağazayı verin" ) benzer bir arama yapabilirsiniz, ancak başlangıçtaki bir mesafe tahminiyle (böylece 10 km x 10 km'lik bir alanla başlarsınız ve yeterli mağazanız yoksa, 20km x 20km ve benzeri). Bu başlangıç mesafesi için bir kez toplam alan üzerindeki mağaza sayısını hesaplar ve kullanırsınız. Veya gereken sorgu sayısını günlüğe kaydedin ve zaman içinde uyum sağlayın.
Mike'ın ilgili sorusuna tam bir kod örneği ekledim ve işte size en yakın X konumunu (hızlı ve zorlukla test edilmiş) veren bir uzantı var:
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();