web-dev-qa-db-ja.com

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

ビジネスエントリにカスタム投稿タイプを使用するローカルビジネスディレクトリサイトに取り組んでいます。フィールドの1つは「郵便番号」です。場所ベースの検索を設定するにはどうすればよいですか。

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

19
matt

gabrielkからの回答 および リンクされたブログ投稿 を変更して、データベースインデックスおよび実際の距離計算の数を最小化する

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

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

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

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();
10
Jan Fabry

まず、次のような表が必要です。

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の店舗がある場合、それらすべてにクエリを実行し、リクエストごとにループ処理して並べ替えるのはかなりコストのかかる操作になります。

8
gabrielk

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

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

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

http://www.semaphorecorp.com/ からの郵便番号データを使った作業の経験があります。それでも100%正確ではありませんでした。たとえば、私のキャンパスで新しいメーリングアドレスと新しい郵便番号を取得したときに、郵便番号が紛失しました。そうは言っても、それが私が見つけた唯一のデータソースであったとしても、それが作成された直後に、まったく新しいZipコードを持っていたのです。

私は自分の本の中で、あなたの要求をどのように満たすかについてのレシピを持っていました…Drupalで。それはGoogle Maps Toolsモジュール( http://drupal.org/project/gmapshttp://drupal.org/project/gmap と混同しないでください。これらのモジュールの中には便利なサンプルコードがあるかもしれませんが、もちろん、WordPressではそのままではうまくいきません。

3