WordPressでロケーションベース(郵便番号)の検索を実装するにはどうすればよいですか?


19

私は、ビジネスエントリにカスタム投稿タイプを使用するローカルビジネスディレクトリサイトで作業しています。フィールドの1つは「郵便番号」です。ロケーションベースの検索を設定するにはどうすればよいですか?

訪問者が郵便番号を入力してカテゴリを選択し、特定の半径内のすべてのビジネス、または距離順に並べられたすべてのビジネスを表示できるようにしたいと思います。これを行うと主張するプラグインがいくつかありましたが、WordPress 3.0をサポートしていません。助言がありますか?


2
これは興味深く挑戦的な質問なので、私は賞金を始めています。私は自分のアイデアをいくつか持っています...しかし、誰かがもっとエレガントな(そして構築しやすい)ものを思い付くことができるかどうかを見たいです。
EAMann

EAMannに感謝します。これを助けることがあります。briancray.com/2009/04/01/...
マット

私は現在、ここに持っているだろう唯一の提案はポッドとしてのプラグインなどを利用することである
NetConstructor.com

@ NetConstructor.com-このためのポッドはお勧めしません。Podsがこの問題とカスタム投稿タイプを提供する利点は本当にありません。
MikeSchinkel

@matt:サイトはまだ完成も展開もされていませんが、最近これに非常に似たものを実装しました。実際、かなりあります。ある時点で店舗検索プラグインとしてパッケージ化する予定ですが、まだ一般的なソリューションとして投稿できるものはまだありません。オフラインで私に連絡してください。必要な答えが得られない場合、私がお手伝いできるかもしれません。
MikeSchinkel

回答:


10

データベースインデックスを使用し、実際の距離計算の数を最小限に抑えることで、gabrielkリンクされたブログ投稿からの回答を変更します

ユーザーの座標がわかっていて、最大距離(10 kmなど)がわかっている場合は、現在の位置を中央にして20 km x 20 kmの境界ボックスを描画できます。これらの境界座標を取得し、これらの緯度と経度の間のストアのみ照会します。データベースクエリで三角関数をまだ使用しないでください。これにより、インデックスの使用が妨げられます。(したがって、バウンディングボックスの北東の角にある場合は、12km離れた場所に店舗があるかもしれませんが、次のステップでそれを捨てます。)

返されるいくつかの店舗の距離のみを計算します(鳥が飛ぶように、または実際の運転方向を使用して)。多数の店舗がある場合、これにより処理時間が大幅に改善されます。

関連するルックアップ(「最も近い10店舗を与える」)についても、同様のルックアップを行うことができますが、初期距離を推測します(したがって、10 km x 10 kmのエリアから開始し、十分な店舗がない場合は展開します) 20km x 20kmなど)。この最初の距離の推測では、総面積の店舗数を一度計算し、それを使用します。または、必要なクエリの数を記録し、時間の経過とともに適応します。

Mikeの関連する質問完全なコード例を追加しました。次に、 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のストアがある場合、すべてのクエリを実行し、すべての要求でループして並べ替えるのは、かなり費用のかかる操作になります。


結果をキャッシュできますか?また、市販されている(または存在する場合は無料の)郵便番号データベースの1つを照会する方が簡単でしょうか?
マット

1
@matt-市販または無料のいずれかを照会することはどういう意味ですか?キャッシュは常に可能である必要があります。codex.wordpress.org/ Transients_API
hakre

@hakre:気にしないで、今は頭の上で話していると思う。私は、距離を取得するために郵便番号データベース(USPS、Googleマップ...)を使用することについて話しているが、おそらく距離を保存せず、単に郵便番号と座標を保存するだけで、計算は私次第です。
マット

3

MySQLのドキュメントには、空間拡張に関する情報も含まれています。奇妙なことに、標準距離()関数は使用できませんが、このページをチェック: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.htmlを 変換」する方法の詳細については、 2つのPOINT値をLINESTRINGに追加し、その長さを計算します。」

すべてのベンダーは、郵便番号の「重心」を表すさまざまな緯度と経度を提供する可能性が高いことに注意してください。また、実際に定義された郵便番号の「境界」ファイルがないことも知っておく価値があります。各ベンダーには、USPS郵便番号を構成するアドレスの特定のリストにほぼ一致する独自の境界セットがあります。(たとえば、一部の「境界」では通りの両側を含める必要があり、他の場合は1つだけにする必要があります。)ベンダーによって広く使用されている郵便番号集計エリア(ZCTA)メール配信に使用されるすべての郵便番号を含めないでくださいhttp://www.census.gov/geo/www/cob/zt_metadata.html

多くのダウンタウンの企業は、独自の郵便番号を持っています。データセットを可能な限り完全にする必要があるため、「ポイント」郵便番号(通常はビジネス)と「境界」郵便番号の両方を含む郵便番号のリストを必ず見つけてください。

http://www.semaphorecorp.com/の郵便番号データを扱った経験があります。それも100%正確ではありませんでした。たとえば、私のキャンパスが新しい郵送先住所と新しい郵便番号を受け取ったとき、郵便番号が間違っていました。とはいえ、新しい郵便番号でさえもまったく作成していなかったため、作成後すぐに見つけた唯一のデータソースでした。

私の本には、あなたの要求に正確に応えるためのレシピがありました... Drupalで。Google Maps Toolsモジュール(http://drupal.org/project/gmaps、信頼できるモジュールであるhttp://drupal.org/project/gmapと混同しないでください)に依存していました。もちろん、これらのモジュールのコードは、WordPressですぐに使用できるわけではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.