Bagaimana saya bisa menerapkan pencarian berbasis lokasi (kode pos) di WordPress?

19

Saya bekerja di situs direktori bisnis lokal yang akan menggunakan jenis posting khusus untuk entri bisnis. Salah satu bidangnya adalah "Kode Pos." Bagaimana cara saya mengatur pencarian berdasarkan lokasi?

Saya ingin pengunjung dapat memasukkan kode pos mereka dan memilih kategori dan menunjukkan semua bisnis dalam radius tertentu, atau semua bisnis yang dipesan berdasarkan jarak. Saya melihat beberapa plugin yang mengklaim melakukan ini tetapi tidak mendukung WordPress 3.0. Ada saran?

matt
sumber
2
Saya memulai hadiah karena ini adalah pertanyaan yang menarik dan menantang. Saya punya beberapa ide sendiri ... tapi saya ingin melihat apakah ada yang bisa datang dengan sesuatu yang lebih elegan (dan lebih mudah dibangun).
EAMann
Terima kasih EAMann. Ini dapat membantu: briancray.com/2009/04/01/…
matt
hanya saran yang saat ini saya miliki di sini adalah dengan menggunakan plugin seperti pod
NetConstructor.com
@ NetConstructor.com - Saya tidak akan menyarankan Pod untuk ini; benar-benar tidak ada manfaat Pods menyediakan masalah ini vs Jenis Posting Kustom.
MikeSchinkel
@ Matt : Saya baru-baru ini mengimplementasikan sesuatu yang sangat mirip dengan ini meskipun situs tersebut belum selesai atau belum digunakan. Sebenarnya ada sedikit juga. Saya berencana untuk mengemasnya sebagai plugin pencari lokasi di beberapa titik, tetapi ini belum beberapa yang dapat saya posting sebagai solusi umum dulu. Hubungi saya offline dan saya mungkin bisa membantu jika Anda tidak mendapatkan jawaban yang Anda butuhkan.
MikeSchinkel

Jawaban:

10

Saya akan memodifikasi jawaban dari gabrielk dan posting blog yang ditautkan dengan menggunakan indeks basis data dan meminimalkan jumlah perhitungan jarak aktual .

Jika Anda tahu koordinat pengguna dan Anda tahu jarak maksimum (katakanlah 10km), Anda bisa menggambar kotak pembatas 20km x 20km dengan lokasi saat ini di tengah. Dapatkan koordinat terikat ini dan kueri hanya menyimpan di antara garis lintang dan bujur ini . Jangan gunakan fungsi trigonometri dalam kueri basis data Anda, karena ini akan mencegah indeks digunakan. (Jadi Anda mungkin mendapatkan toko yang berjarak 12 km dari Anda jika berada di sudut timur laut kotak pembatas, tetapi kami membuangnya di langkah berikutnya.)

Hitung saja jarak (seperti burung terbang atau dengan arah mengemudi yang sebenarnya, seperti yang Anda inginkan) untuk beberapa toko yang dikembalikan. Ini akan secara drastis meningkatkan waktu pemrosesan jika Anda memiliki banyak toko.

Untuk pencarian terkait ( "berikan sepuluh toko terdekat" ) Anda dapat melakukan pencarian yang serupa, tetapi dengan tebakan jarak awal (jadi Anda mulai dengan area 10km kali 10km, dan jika Anda tidak memiliki cukup toko, Anda perlu memperluasnya ke 20km kali 20km dan seterusnya). Untuk jarak awal ini, tebak Anda pernah menghitung jumlah toko di seluruh area dan menggunakannya. Atau catat jumlah permintaan yang dibutuhkan dan sesuaikan dengan waktu.

Saya menambahkan contoh kode lengkap di pertanyaan terkait Mike , dan berikut ini adalah ekstensi yang memberi Anda lokasi X terdekat (cepat dan hampir tidak diuji):

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

Pertama, Anda membutuhkan tabel yang terlihat seperti ini:

zip_code    lat     lon
10001       40.77    73.98

... diisi untuk setiap kode pos. Anda dapat memperluas ini dengan menambahkan bidang kota dan negara bagian jika Anda ingin melihat ke sana.

Kemudian setiap toko dapat diberi kode pos, dan ketika Anda perlu menghitung jarak Anda dapat bergabung dengan tabel lat / panjang ke data toko.

Kemudian Anda akan meminta tabel itu untuk mendapatkan lintang dan bujur untuk kode pos toko dan pengguna. Setelah Anda mendapatkannya, Anda dapat mengisi array Anda dan meneruskannya ke fungsi "dapatkan jarak":

$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);
}

Ini dimaksudkan sebagai bukti konsep, bukan kode yang sebenarnya akan saya sarankan untuk diterapkan. Jika Anda memiliki 10.000 toko, misalnya, itu akan menjadi operasi yang cukup mahal untuk menanyakan semuanya dan mengulanginya dan mengurutkannya pada setiap permintaan.

Gabrielk
sumber
Bisakah hasilnya di-cache? Juga, apakah akan lebih mudah untuk meminta salah satu dari basis data kode pos yang tersedia secara komersial (atau gratis jika ada)?
matt
1
@ tikar - Apa yang Anda maksud dengan permintaan salah satu dari yang tersedia secara komersial atau gratis? Caching harus selalu dimungkinkan, lihat codex.wordpress.org/Transients_API
hakre
@akre: Nevermind, saya pikir saya sedang berbicara di atas kepala saya sekarang. Saya sedang berbicara tentang menggunakan database kode pos (USPS, Google Maps ...) untuk mendapatkan jarak tetapi saya tidak menyadari bahwa mereka mungkin tidak menyimpan jarak, mereka hanya menyimpan kode pos dan koordinat dan itu akan terserah saya untuk menghitungnya.
matt
3

Dokumentasi MySQL juga mencakup informasi tentang ekstensi spasial. Anehnya, fungsi jarak standar () tidak tersedia, tetapi periksa halaman ini: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html untuk detail tentang cara "mengkonversi" dua POINT nilai ke LINESTRING dan kemudian menghitung panjangnya. "

Perhatikan bahwa setiap vendor cenderung menawarkan garis lintang dan bujur yang berbeda yang mewakili "centroid" dari kode pos. Perlu juga diketahui bahwa tidak ada file "batas" kode pos nyata yang ditentukan. Setiap vendor akan memiliki seperangkat batasan yang kira-kira sama dengan daftar alamat khusus yang membentuk kode pos USPS. (Misalnya, di beberapa "batas" Anda perlu memasukkan kedua sisi jalan, di sisi lain, hanya satu.) Area Tabulasi Kode ZIP (ZCTA), banyak digunakan oleh vendor, "jangan menggambarkan area pengiriman Kode ZIP secara tepat, dan tidak termasuk semua Kode ZIP yang digunakan untuk pengiriman surat " http://www.census.gov/geo/www/cob/zt_metadata.html

Banyak bisnis pusat kota akan memiliki kode pos sendiri. Anda akan ingin set data setengkap mungkin, jadi pastikan Anda menemukan daftar kode pos yang mencakup kode pos "titik" (biasanya bisnis), dan kode pos "batas".

Saya memiliki pengalaman bekerja dengan data kode pos dari http://www.semaphorecorp.com/ . Bahkan itu tidak 100% akurat. Misalnya, ketika kampus saya mengambil alamat surat dan kode pos baru, kode pos salah tempat. Yang mengatakan, itu adalah satu-satunya sumber data yang saya temukan bahwa bahkan MEMILIKI kode pos baru sama sekali, jadi segera setelah itu dibuat.

Saya punya resep di buku saya untuk mengetahui bagaimana cara memenuhi permintaan Anda ... di Drupal. Itu bergantung pada modul Google Maps Tools ( http://drupal.org/project/gmaps , jangan bingung dengan http://drupal.org/project/gmap , juga modul yang layak.) Anda mungkin menemukan beberapa sampel yang bermanfaat kode dalam modul-modul itu, meskipun tentu saja, mereka tidak akan bekerja di luar kotak di WordPress.

Marjorie Roswell
sumber