Как я могу реализовать поиск по почтовому индексу в WordPress?

19

Я работаю на локальном сайте бизнес-каталога, который будет использовать пользовательские типы записей для бизнес-записей. Одним из полей будет «Почтовый индекс». Как настроить поиск по местоположению?

Я бы хотел, чтобы посетители могли ввести свой почтовый индекс, выбрать категорию и показать все предприятия с определенным радиусом или все предприятия, упорядоченные по расстоянию. Я видел пару плагинов, которые утверждают, что делают это, но они не поддерживают WordPress 3.0. Какие-либо предложения?

матовый
источник
2
Я начинаю вознаграждение, потому что это интересный и сложный вопрос. У меня есть несколько собственных идей ... но я хочу посмотреть, сможет ли кто-нибудь придумать что-нибудь более элегантное (и более простое в сборке).
EAMann
Спасибо Е.А.Манн. Это может помочь: briancray.com/2009/04/01/...
матовое
единственное предложение, которое у меня сейчас есть, - использовать плагин, такой как pods
NetConstructor.com,
@ NetConstructor.com - я бы не предложил для этого стручки; Там действительно нет никакой пользы, Pods предоставляет эту проблему по сравнению с Custom Types.
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

... заполняется для каждого почтового индекса. Вы можете расширить это, добавив поля города и штата, если хотите посмотреть таким образом.

Тогда каждому магазину может быть присвоен почтовый индекс, и когда вам нужно рассчитать расстояние, вы можете присоединить таблицу широты / длины к данным магазина.

Затем вы запросите эту таблицу, чтобы узнать широту и долготу магазина и почтовые индексы пользователя. Получив его, вы можете заполнить свой массив и передать его в функцию «получить расстояние»:

$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 магазинов, было бы довольно дорогой операцией запросить их все, просмотреть и отсортировать их при каждом запросе.

gabrielk
источник
Могут ли результаты быть кэшированы? Кроме того, было бы легче запросить одну из коммерчески доступных (или бесплатных, если есть) баз данных почтового индекса?
Мэтт
1
@matt - Что вы подразумеваете под запросом одного из коммерческих или бесплатных? Кэширование всегда должно быть возможным, см. Codex.wordpress.org/Transients_API
hakre
@hakre: Неважно, я думаю, что я говорю сейчас над головой. Я говорю об использовании баз данных почтового индекса (USPS, Google Maps ...), чтобы получить расстояния, но я не осознавал, что они, вероятно, не хранят расстояния, они просто хранят почтовый индекс и координаты, и это будет быть до меня, чтобы рассчитать это.
Мэтт
3

Документация MySQL также включает информацию о пространственных расширениях. Как ни странно, стандартная функция distance () недоступна, но проверьте эту страницу: 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% точно. Например, когда мой кампус взял новый почтовый адрес и новый почтовый индекс, почтовый индекс был не на своем месте. Тем не менее, это был единственный источник данных, который я обнаружил, что даже имел новый почтовый индекс, так что вскоре после его создания.

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

Марджори Розуэлл
источник