WordPress에서 위치 기반 (우편 번호) 검색을 어떻게 구현할 수 있습니까?


19

비즈니스 항목에 사용자 정의 게시물 유형을 사용하는 로컬 비즈니스 디렉토리 사이트에서 작업하고 있습니다. 필드 중 하나는 "우편 번호"입니다. 위치 기반 검색을 설정하려면 어떻게합니까?

방문자가 우편 번호를 입력하고 카테고리를 선택하고 특정 반경을 가진 모든 비즈니스 또는 거리별로 주문한 모든 비즈니스를 보여줄 수 있기를 바랍니다. 나는 이것을 요구하는 몇 가지 플러그인을 보았지만 WordPress 3.0을 지원하지 않습니다. 어떤 제안?


2
흥미롭고 도전적인 질문이기 때문에 현상금을 시작하고 있습니다. 나는 내 자신의 아이디어를 가지고 있지만 ... 누군가 더 우아하고 (더 쉽게 만들 수있는) 무언가를 만들 수 있는지 알고 싶습니다.
EAMann

고마워 EAMann. 도움이 될 수 있습니다 : briancray.com/2009/04/01/…
matt

내가 지금 여기있을 제안은 포드와 같은 플러그인을 사용하는 것입니다
NetConstructor.com

@ NetConstructor.com- 이것에 대한 포드를 제안하지는 않습니다. 포드가이 문제와 사용자 지정 게시물 유형을 제공하면 실제로 이점은 없습니다.
MikeSchinkel

@matt : 사이트가 아직 마무리되거나 배포되지 않았지만 최근에 이와 비슷한 것을 구현했습니다. 사실, 그것도 꽤 있습니다. 어느 시점에서 상점 로케이터 플러그인으로 패키지 할 계획이지만 아직 일반 솔루션으로 게시 할 수있는 것은 아닙니다. 오프라인으로 연락하면 필요한 답변을 얻지 못할 경우 도와 드릴 수 있습니다.
MikeSchinkel

답변:


10

데이터베이스 색인 을 사용 하고 실제 거리 계산 수를 최소화 하여 gabrielk링크 된 블로그 게시물 의 답변을 수정하겠습니다 .

사용자의 좌표를 알고 최대 거리 (예 : 10km)를 알고 있다면 현재 위치가 중간에 20km x 20km 인 경계 상자를 그릴 수 있습니다. 이 경계 좌표를 얻고 위도와 경도 사이의 상점 만 쿼리하십시오 . 데이터베이스 쿼리에서 삼각 함수를 사용하지 마십시오. 인덱스가 사용되지 않습니다. (따라서 경계 상자의 북동쪽 모서리에 있으면 12km 떨어진 상점을 얻을 수 있지만 다음 단계에서 버립니다.)

반환되는 몇 개의 상점에 대한 거리 (조류 파리 또는 원하는 실제 운전 방향) 만 계산하십시오. 상점 수가 많은 경우 처리 시간이 크게 향상됩니다.

관련 검색 ( "가장 가까운 10 개의 매장 제공" )의 경우 유사한 검색을 수행 할 수 있지만 초기 거리 추측으로 (10km x 10km 면적으로 시작하고, 매장이 충분하지 않으면 20km 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

... 우편 번호마다 채워집니다. 그런 식으로 찾으려면 도시 및 주 필드를 추가하여이를 확장 할 수 있습니다.

그런 다음 각 상점에 우편 번호를 부여 할 수 있으며 거리를 계산해야 할 때 위도 / 경도 테이블을 상점 데이터에 결합 할 수 있습니다.

그런 다음 해당 테이블을 쿼리하여 상점 및 사용자의 우편 번호에 대한 위도와 경도를 얻습니다. 일단 그것을 얻으면 배열을 채우고 "get distance"함수로 전달할 수 있습니다 :

$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 개의 상점이있는 경우 상점을 모두 쿼리하고 모든 요청에 ​​대해 반복하여 정렬하는 것은 비용이 많이 드는 작업입니다.


결과를 캐시 할 수 있습니까? 또한, 상업적으로 이용 가능한 (또는 무료 인 경우) 우편 번호 데이터베이스 중 하나를 쿼리하는 것이 더 쉬울까요?
matt

1
@matt- 상업적 또는 무료로 제공되는 제품 중 하나를 쿼리한다는 것은 무슨 의미입니까? 캐싱은 항상 가능해야합니다. codex.wordpress.org/Transients_API
hakre

@hakre : 신경 쓰지 마, 지금 내 머리 위로 얘기하고있는 것 같아. 나는 거리를 얻기 위해 우편 번호 데이터베이스 (USPS, Google Maps ...)를 사용하는 것에 대해 이야기하고 있지만 거리를 저장하지 않는다는 것을 몰랐습니다. 우편 번호와 좌표 만 저장하면됩니다. 그것을 계산하기 위해 나에게 달려 있습니다.
matt

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 % 정확하지는 않았습니다. 예를 들어, 캠퍼스에서 새 우편 주소와 새 우편 번호를 받으면 우편 번호가 잘못되었습니다. 즉, 새로운 우편 번호가 생성 된 직후에도 발견 된 유일한 데이터 소스였습니다.

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.