Як я можу здійснити пошук на основі місцезнаходження (поштовий індекс) у WordPress?


19

Я працюю на сайті місцевого бізнес-довідника, який використовуватиме власні типи публікацій для бізнес-записів. Одним із полів буде "Поштовий індекс". Як я можу налаштувати пошук на основі місцезнаходження?

Я хотів би, щоб відвідувачі мали змогу ввести свій поштовий індекс та вибрати категорію та показати всі компанії із певним радіусом, або всі підприємства, упорядковані на відстані. Я побачив пару плагінів, які заявляють про це, але вони не підтримують WordPress 3.0. Будь-які пропозиції?


2
Я починаю щедро, тому що це цікаве та складне питання. У мене є кілька власних ідей ... але я хочу подивитися, чи може хтось придумати щось більш елегантне (і простіше побудувати).
EAMann

Дякую EAMann Це може допомогти: briancray.com/2009/04/01/…
мат

Єдина пропозиція, яку я зараз маю тут, - це використовувати плагін, такий як стручки
NetConstructor.com

@ NetConstructor.com - я б не пропонував Pods для цього; дійсно немає користі. Програми надають цю проблему порівняно зі спеціальними типами пошти.
MikeSchinkel

@matt : Нещодавно я реалізував щось дуже схоже на це, хоча сайт ще не доопрацьований і не розгорнутий. Насправді це теж небагато. Я планую упакувати його як плагін локатора магазину в якийсь момент, але це ще не те, що я ще можу розмістити як загальне рішення. Зв'яжіться зі мною в автономному режимі, і я можу допомогти, якщо не отримаєте потрібної відповіді.
MikeSchinkel

Відповіді:


10

Я б змінив відповідь з 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();

8

Спочатку вам потрібна таблиця, яка виглядає приблизно так:

zip_code    lat     lon
10001       40.77    73.98

... заповнюється для кожного поштового індексу. Ви можете розширити це, додавши поля міста та штату, якщо ви хочете шукати так.

Тоді кожному магазину можна надати поштовий індекс, і коли вам потрібно буде обчислити відстань, ви можете приєднати lat / long таблицю до даних магазину.

Тоді ви запитаєте цю таблицю, щоб отримати широту та довготу для магазину та поштових індексів користувача. Як тільки ви отримаєте його, ви можете заповнити свій масив і передати його функції "отримати відстань":

$user_location = array(
    'latitude' => 42.75,
    'longitude' => 73.80,
);

$output = array();
$results = $wpdb->get_results("SELECT id, zip_code, lat, lon FROM store_table");
foreach ( $results as $store ) {
    $store_location = array(
        'zip_code' => $store->zip_code, // 10001
        'latitude' => $store->lat, // 40.77
        'longitude' => $store->lon, // 73.98
    );

    $distance = get_distance($store_location, $user_location, 'miles');

    $output[$distance][$store->id] = $store_location;
}

ksort($output);

foreach ($output as $distance => $store) {
    foreach ( $store as $id => $location ) {
        echo 'Store ' . $id . ' is ' . $distance . ' away';
    }
}

function get_distance($store_location, $user_location, $units = 'miles') {
    if ( $store_location['longitude'] == $user_location['longitude'] &&
    $store_location['latitude'] == $user_location['latitude']){
        return 0;

    $theta = ($store_location['longitude'] - $user_location['longitude']);
    $distance = sin(deg2rad($store_location['latitude'])) * sin(deg2rad($user_location['latitude'])) + cos(deg2rad($store_location['latitude'])) * cos(deg2rad($user_location['latitude'])) * cos(deg2rad($theta));
    $distance = acos($distance);
    $distance = rad2deg($distance);
    $distance = $distance * 60 * 1.1515;

    if ( 'kilometers' == $units ) {
        $distance = $distance * 1.609344;
    }

    return round($distance);
}

Це мається на увазі як доказ концепції, а не код, який я б фактично рекомендував реалізувати. Наприклад, якщо у вас є 10 000 магазинів, це було б досить дорогою операцією, щоб переглядати їх усіх і перебирати та сортувати за кожним запитом.


Чи можна кешувати результати? Крім того, чи було б простіше запитувати одну з комерційно доступних (або безкоштовних, якщо є) баз поштових індексів?
мат

1
@matt - Що ви маєте на увазі під запитом одного з комерційних або безкоштовних доступних? Кешування має бути завжди можливим, див. Codex.wordpress.org/Transients_API
hakre

@hakre: Ніколи не думаю, я зараз говорю над головою. Я говорю про використання баз даних поштового коду (USPS, Google Maps ...) для отримання відстаней, але я не усвідомлював, що вони, ймовірно, не зберігають відстані, вони просто зберігають поштовий індекс і координати, і це буде до мене, щоб її обчислити.
мат

3

Документація MySQL також включає інформацію про просторові розширення. Як не дивно, стандартна функція відстані () недоступна, але перевірте цю сторінку: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html для отримання детальної інформації про те, як "перетворити два значення POINT на LINESTRING, а потім обчислити довжину цього. "

Зауважте, що кожен постачальник може запропонувати різні широти та довготи, що представляють "центроїд" поштового індексу. Також варто знати, що не існує реальних файлів "кордону" з поштовим кодом. У кожного постачальника буде свій набір меж, які приблизно відповідають певним спискам адрес, що складають поштовий індекс USPS. (Наприклад, в деяких "межах" вам потрібно буде включити обидві сторони вулиці, в інших - лише одну.) Області табуляції поштових індексів (ZCTA), широко використовувані постачальниками, "не чітко зображують області доставки поштового індексу, і не включають усі поштові індекси, які використовуються для доставки пошти " http://www.census.gov/geo/www/cob/zt_metadata.html

Багато підприємств у центрі міста матимуть свій поштовий індекс. Ви хочете якомога повніший набір даних, тому не забудьте знайти список поштових індексів, що включає в себе як "точкові" поштові індекси (як правило, підприємства), так і "граничні" поштові індекси.

У мене є досвід роботи з даними поштового індексу з http://www.semaphorecorp.com/ . Навіть це не було на 100% точно. Наприклад, коли мій кампус прийняв нову поштову адресу та новий поштовий індекс, поштовий індекс був замінений. Це означає, що це єдине джерело даних, яке я виявив, що навіть HAD новий поштовий індекс взагалі, так що незабаром після його створення.

У моїй книзі був рецепт того, як саме відповідати вашим запитам ... в Drupal. Він спирався на модуль інструментів Google Maps ( http://drupal.org/project/gmaps , не плутати його з http://drupal.org/project/gmap , також гідним модулем.) Ви можете знайти корисний зразок коду в цих модулях, хоча, звичайно, вони не вийдуть з поля в WordPress.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.