Làm cách nào tôi có thể triển khai tìm kiếm dựa trên vị trí (mã zip) trong WordPress?


19

Tôi đang làm việc trên một trang web thư mục doanh nghiệp địa phương sẽ sử dụng các loại bài đăng tùy chỉnh cho các mục kinh doanh. Một trong các trường sẽ là "Mã Zip." Làm cách nào tôi có thể thiết lập tìm kiếm dựa trên vị trí?

Tôi muốn khách truy cập có thể nhập mã zip của họ và chọn một danh mục và hiển thị tất cả các doanh nghiệp có bán kính nhất định hoặc tất cả các doanh nghiệp được đặt hàng theo khoảng cách. Tôi đã thấy một vài plugin yêu cầu thực hiện việc này nhưng chúng không hỗ trợ WordPress 3.0. Bất kỳ đề xuất?


2
Tôi đang bắt đầu một tiền thưởng vì đây là một câu hỏi thú vị và đầy thách thức. Tôi có một vài ý tưởng của riêng mình ... nhưng tôi muốn xem liệu có ai có thể nghĩ ra thứ gì đó thanh lịch hơn (và dễ xây dựng hơn) không.
EAMann

Cảm ơn EAMann. Điều này có thể giúp: briancray.com/2009/04/01/ trên
matt

chỉ đề xuất hiện tại tôi có ở đây là sử dụng một plugin như pods
NetConstructor.com

@ NetConstructor.com - Tôi sẽ không đề xuất Pods cho việc này; thực sự không có lợi ích Pods cung cấp vấn đề này so với các loại bài đăng tùy chỉnh.
MikeSchinkel

@matt : Gần đây tôi đã triển khai một cái gì đó rất giống với điều này mặc dù trang web chưa được hoàn thiện cũng như chưa được triển khai. Có một chút quá nó, thực sự. Tôi có kế hoạch đóng gói nó như một plugin định vị cửa hàng tại một số điểm nhưng không phải là một số mà tôi có thể đăng như một giải pháp chung. Liên hệ với tôi ngoại tuyến và tôi có thể giúp đỡ nếu bạn không nhận được câu trả lời mình cần.
MikeSchinkel

Câu trả lời:


10

Tôi sẽ sửa đổi câu trả lời từ gabrielkbài đăng trên blog được liên kết bằng cách sử dụng các chỉ mục cơ sở dữ liệugiảm thiểu số lượng tính toán khoảng cách thực tế .

Nếu bạn biết tọa độ của người dùng và bạn biết khoảng cách tối đa (giả sử là 10km), bạn có thể vẽ một hộp giới hạn 20km x 20km với vị trí hiện tại ở giữa. Nhận các tọa độ giới hạn và chỉ truy vấn lưu trữ giữa các vĩ độ và kinh độ này . Chưa sử dụng các hàm lượng giác trong truy vấn cơ sở dữ liệu của bạn, vì điều này sẽ ngăn các chỉ mục được sử dụng. (Vì vậy, bạn có thể có được một cửa hàng cách bạn 12km nếu nó ở góc đông bắc của hộp giới hạn, nhưng chúng tôi sẽ ném nó ra trong bước tiếp theo.)

Chỉ tính khoảng cách (khi chim bay hoặc với chỉ đường lái xe thực tế, tùy thích) cho một vài cửa hàng được trả lại. Điều này sẽ cải thiện đáng kể thời gian xử lý nếu bạn có một số lượng lớn các cửa hàng.

Đối với tra cứu liên quan ( "cung cấp mười cửa hàng gần nhất" ), bạn có thể thực hiện tra cứu tương tự, nhưng với dự đoán khoảng cách ban đầu (vì vậy bạn bắt đầu với diện tích 10km x 10km và nếu bạn không có đủ cửa hàng, bạn hãy mở rộng 20km bằng 20km và cứ thế). Đối với khoảng cách ban đầu này, bạn có thể tính toán số lượng cửa hàng trên tổng diện tích và sử dụng số đó. Hoặc đăng nhập số lượng truy vấn cần thiết và thích ứng theo thời gian.

Tôi đã thêm một ví dụ mã đầy đủ vào câu hỏi liên quan của Mike và đây là tiện ích mở rộng cung cấp cho bạn các vị trí X gần nhất (nhanh chóng và hầu như không được kiểm tra):

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

Trước tiên, bạn cần một bảng trông giống như thế này:

zip_code    lat     lon
10001       40.77    73.98

... được điền cho mỗi mã zip. Bạn có thể mở rộng về điều này bằng cách thêm các trường thành phố và tiểu bang nếu bạn muốn tìm kiếm theo cách đó.

Sau đó, mỗi cửa hàng có thể được cung cấp một mã zip và khi bạn cần tính khoảng cách, bạn có thể tham gia bảng lat / long vào dữ liệu lưu trữ.

Sau đó, bạn sẽ truy vấn bảng đó để lấy vĩ độ và kinh độ cho mã zip của cửa hàng và người dùng. Khi bạn nhận được nó, bạn có thể điền vào mảng của mình và chuyển nó đến hàm "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);
}

Điều này có nghĩa là một bằng chứng về khái niệm, không phải là mã mà tôi thực sự khuyên bạn nên thực hiện. Nếu bạn có 10.000 cửa hàng, chẳng hạn, đó sẽ là một hoạt động khá tốn kém để truy vấn tất cả chúng và lặp lại và sắp xếp chúng theo mọi yêu cầu.


Kết quả có thể được lưu trữ? Ngoài ra, sẽ dễ dàng hơn để truy vấn một trong những cơ sở dữ liệu mã zip có sẵn (hoặc miễn phí nếu có)?
matt

1
@matt - Bạn có ý nghĩa gì khi truy vấn một trong những thương mại hoặc miễn phí có sẵn? Bộ nhớ đệm phải luôn luôn có thể, xem codex.wordpress.org/Transrons_API
hakre

@hakre: Nevermind, tôi nghĩ bây giờ tôi đang nói chuyện qua đầu. Tôi đang nói về việc sử dụng cơ sở dữ liệu mã zip (USPS, Google Maps ...) để có khoảng cách nhưng tôi không nhận ra rằng họ có thể không lưu trữ khoảng cách, họ chỉ lưu trữ mã zip và tọa độ và nó sẽ tùy thuộc vào tôi để tính toán nó
matt

3

Tài liệu MySQL cũng bao gồm thông tin về các phần mở rộng không gian. Điều kỳ lạ là hàm distance () tiêu chuẩn không khả dụng, nhưng hãy kiểm tra trang này: http://dev.mysql.com/tech-resource/articles/4.1/gis-with-mysql.html để biết chi tiết về cách "chuyển đổi hai giá trị ĐIỂM cho LINESTRING và sau đó tính toán độ dài của nó. "

Lưu ý rằng mọi nhà cung cấp có khả năng cung cấp các vĩ độ và kinh độ khác nhau đại diện cho "tâm" của mã zip. Cũng đáng để biết rằng không có tệp "ranh giới" mã zip thực được xác định. Mỗi nhà cung cấp sẽ có một bộ ranh giới riêng phù hợp với danh sách địa chỉ cụ thể tạo nên mã zip USPS. (Ví dụ: trong một số "ranh giới", bạn sẽ cần bao gồm cả hai bên đường, chỉ ở một bên.) Khu vực lập bảng mã ZIP (ZCTA), được sử dụng rộng rãi bởi các nhà cung cấp, "không mô tả chính xác các khu vực phân phối Mã ZIP," và không bao gồm tất cả các Mã ZIP được sử dụng để gửi thư " http://www.cencies.gov/geo/www/cob/zt_metadata.html

Nhiều doanh nghiệp trung tâm thành phố sẽ có mã zip riêng của họ. Bạn sẽ muốn hoàn thành một bộ dữ liệu càng tốt, vì vậy hãy chắc chắn rằng bạn tìm thấy một danh sách các mã zip bao gồm cả mã zip "điểm" (thường là doanh nghiệp) và mã zip "ranh giới".

Tôi có kinh nghiệm làm việc với dữ liệu mã zip từ http://www.semaphorecorp.com/ . Ngay cả điều đó không chính xác 100%. Chẳng hạn, khi khuôn viên của tôi nhận một địa chỉ gửi thư mới và mã zip mới, zip bị đặt sai vị trí. Điều đó nói rằng, đó là nguồn dữ liệu duy nhất tôi thấy rằng thậm chí HAD mã zip mới, ngay sau khi nó được tạo.

Tôi đã có một công thức trong cuốn sách của mình để biết chính xác làm thế nào để đáp ứng yêu cầu của bạn ... trong Drupal. Nó dựa vào mô-đun Công cụ Google Maps ( http://drupal.org/project/gmaps , đừng nhầm lẫn với http://drupal.org/project/gmap , cũng là một mô-đun xứng đáng.) Bạn có thể tìm thấy một số mẫu hữu ích. mã trong các mô-đun đó, mặc dù vậy, tất nhiên, chúng sẽ không hoạt động trong hộp.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.