Cum pot implementa o căutare bazată pe locație (cod poștal) în WordPress?

28 sept. 2010, 01:32:13
Vizualizări: 26.2K
Voturi: 21

Lucrez la un site de tip director pentru afaceri locale care va folosi custom post types pentru înregistrările companiilor. Unul dintre câmpuri va fi "Cod Poștal". Cum pot configura o căutare bazată pe locație?

Mi-ar plăcea ca vizitatorii să poată introduce codul lor poștal și să aleagă o categorie pentru a afișa toate afacerile într-o anumită rază sau toate afacerile ordonate după distanță. Am văzut câteva plugin-uri care pretind că fac acest lucru, dar nu sunt compatibile cu WordPress 3.0. Aveți sugestii?

9
Comentarii

Încep o recompensă pentru că aceasta este o întrebare interesantă și provocatoare. Am câteva idei proprii... dar vreau să văd dacă cineva poate veni cu ceva mai elegant (și mai ușor de construit).

EAMann EAMann
3 oct. 2010 17:53:59

singura sugestie pe care aș avea-o în prezent ar fi să utilizezi un plugin precum pods

NetConstructor.com NetConstructor.com
4 oct. 2010 11:19:44

@NetConstructor.com - Nu aș sugera Pods pentru asta; nu există cu adevărat niciun avantaj pe care Pods îl oferă pentru această problemă în comparație cu Tipurile de Postare Personalizate.

MikeSchinkel MikeSchinkel
5 oct. 2010 02:36:59

@matt: Am implementat recent ceva foarte asemănător cu asta, deși site-ul nu este finalizat și nici implementat încă. Este de fapt destul de complex. Plănuiesc să-l împachetez ca un plugin pentru localizare de magazine la un moment dat, dar nu este ceva pe care să-l pot posta ca o soluție generală încă. Contactează-mă în privat și s-ar putea să te pot ajuta dacă nu primești răspunsul de care ai nevoie.

MikeSchinkel MikeSchinkel
5 oct. 2010 02:38:13

@mikeschinkel -- Hei Mike, singurul motiv pentru care am menționat Pods a fost pentru că ar putea oferi lui Chris mai multă flexibilitate, deoarece cred că își creează propriile tabele de baze de date, ceea ce ar putea face procesul mai simplu sau mai robust. Dar presupun că ai dreptate, același lucru ar putea fi realizat printr-un tip de postare personalizat. Apropo, mike... așteaptă un email de la mine în curând ;-)

NetConstructor.com NetConstructor.com
5 oct. 2010 10:43:45

@NetConstructor: Mă gândeam la această abordare folosind un tutorial până luni la prânz, dar eram îngrijorat că nu va fi la fel de scalabilă sau viabilă pe termen lung ca utilizarea funcționalităților native.

matt matt
5 oct. 2010 20:11:12

@mikeschinkel: Mulțumesc, bănuiesc că vei primi un email de la mine. Doar din curiozitate, folosești vreo bază de date terță parte?

matt matt
5 oct. 2010 20:12:07

@matt: Nu, tocmai am scris-o eu în totalitate. Interoghează Google Maps și stochează în cache rezultatele pentru utilizare viitoare și pentru a minimiza numărul de apeluri zilnice către Google Maps. De asemenea, stochează în cache distanțele astfel încât acestea să poată fi indexate în loc să ruleze o interogare neindexabilă. Logica de caching este parțial sursa complexității.

MikeSchinkel MikeSchinkel
6 oct. 2010 00:33:06
Arată celelalte 4 comentarii
Toate răspunsurile la întrebare 3
0
10

Eu aș modifica răspunsul de la gabrielk și postul de blog legat prin utilizarea indexurilor de bază de date și minimizarea numărului de calcule efective de distanță.

Dacă cunoști coordonatele utilizatorului și distanța maximă (de exemplu 10km), poți desena un dreptunghi de delimitare de 20km pe 20km cu locația curentă în centru. Obține aceste coordonate de delimitare și interoghează doar magazinele dintre aceste latitudini și longitudini. Nu utiliza încă funcții trigonometrice în interogarea bazei de date, deoarece acest lucru va împiedica utilizarea indexurilor. (Deci, s-ar putea să obții un magazin care este la 12km distanță de tine dacă se află în colțul nord-estic al dreptunghiului de delimitare, dar îl vom elimina în pasul următor.)

Calculează distanța (în linie dreaptă sau cu direcții reale de condus, după preferință) doar pentru puținele magazine returnate. Acest lucru va îmbunătăți drastic timpul de procesare dacă ai un număr mare de magazine.

Pentru căutarea înrudită ("dă-mi cele zece magazine cele mai apropiate") poți face o căutare similară, dar cu o estimare inițială a distanței (deci începi cu o zonă de 10km pe 10km, iar dacă nu ai suficiente magazine, o extinzi la 20km pe 20km și așa mai departe). Pentru această estimare inițială a distanței, calculezi o dată numărul de magazine din întreaga zonă și îl folosești. Sau poți înregistra numărul de interogări necesare și să te adaptezi în timp.

Am adăugat un exemplu complet de cod la întrebarea înrudită a lui Mike, iar aici este o extensie care îți oferă cele mai apropiate X locații (rapid și testat minimal):

class Monkeyman_Geo_ClosestX extends Monkeyman_Geo
{
    public static $closestXStartDistanceKm = 10;
    public static $closestXMaxDistanceKm = 1000; // Nu căuta dincolo de aceasta

    public function addAdminPages()
    {
        parent::addAdminPages();
        add_management_page( 'Test cele mai apropiate locații', 'Test cele mai apropiate locații', '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>Număr de postări: <input size="5" name="post_count" value="10"/></p>
    <p>Latitudinea centrului: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Longitudinea centrului: <input size="10" name="center_lon" value="{$default_lon}"/></p>
    <p><input type="submit" name="search" value="Caută!"/></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));
    }

    /**
     * Obține cele mai apropiate X postări de o locație dată
     *
     * Acest lucru poate returna mai multe decât X rezultate și niciodată mai mult decât
     * self::$closestXMaxDistanceKm distanță (pentru a preveni căutarea fără sfârșit)
     * Rezultatele sunt sortate după distanță
     *
     * Algoritmul începe cu toate locațiile care nu sunt mai departe decât
     * self::$closestXStartDistanceKm, apoi extinde această zonă
     * (prin dublarea distanței) până când sunt găsite suficiente potriviri.
     *
     * Numărul de calcule costisitoare ar trebui minimizat.
     */
    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) {
                    // Include doar cele care sunt în raza cercului, nu în dreptunghiul de delimitare, altfel am putea rata unele mai apropiate în pasul următor
                    $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();
7 oct. 2010 12:52:40
3

În primul rând ai nevoie de un tabel care arată cam așa:

zip_code    lat     lon
10001       40.77    73.98

...completat pentru fiecare cod poștal. Poți extinde acest tabel prin adăugarea câmpurilor pentru oraș și stat dacă dorești să faci căutări și după aceste criterii.

Apoi fiecărui magazin i se poate atribui un cod poștal, iar când trebuie să calculezi distanța poți face join între tabelul cu latitudine/longitudine și datele magazinului.

Ulterior vei interoga acest tabel pentru a obține latitudinea și longitudinea pentru codurile poștale ale magazinului și ale utilizatorului. După ce le obții, poți popula array-ul și să-l transmiți unei funcții "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 'Magazinul ' . $id . ' se află la ' . $distance . ' distanță';
    }
}

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

Acesta este doar un concept demonstrativ, nu un cod pe care l-aș recomanda pentru implementare. Dacă ai, de exemplu, 10.000 de magazine, ar fi o operațiune destul de costisitoare să interoghezi toate datele și să le sortezi la fiecare solicitare.

5 oct. 2010 01:04:21
Comentarii

Pot fi rezultatele stocate în cache? De asemenea, ar fi mai ușor să interogăm una dintre bazele de date de coduri poștale disponibile comercial (sau gratuite, dacă există)?

matt matt
5 oct. 2010 20:03:08

@matt - Ce înțelegi prin interogarea uneia dintre opțiunile comerciale sau gratuite disponibile? Stocarea în cache ar trebui să fie întotdeauna posibilă, vezi http://codex.wordpress.org/Transients_API

hakre hakre
6 oct. 2010 13:08:49

@hakre: Nu mai contează, cred că vorbesc peste capacitatea mea acum. Mă refeream la utilizarea unei baze de date de coduri poștale (USPS, Google Maps...) pentru a obține distanțele, dar nu mi-am dat seama că probabil ele nu stochează distanțele, ci doar codul poștal și coordonatele, iar mie mi-ar rămâne să le calculez.

matt matt
7 oct. 2010 23:32:05
0

Documentația MySQL include și informații despre extensiile spațiale. În mod ciudat, funcția standard distance() nu este disponibilă, dar verificați această pagină: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html pentru detalii despre cum să "convertiți cele două valori POINT într-un LINESTRING și apoi să calculați lungimea acestuia."

Rețineți că fiecare furnizor este probabil să ofere latitudini și longitudini diferite reprezentând "centroidul" unui cod poștal. De asemenea, merită să știți că nu există fișiere reale definite pentru "granițele" codurilor poștale. Fiecare furnizor va avea propriul set de granițe care se potrivesc aproximativ cu listele specifice de adrese care alcătuiesc un cod poștal USPS. (De exemplu, în unele "granițe" ar trebui să includeți ambele părți ale străzii, în altele, doar una.) Zonele de Tabulație a Codurilor Poștale (ZCTAs), utilizate pe scară largă de furnizori, "nu descriu cu precizie zonele de livrare a codurilor poștale și nu includ toate codurile poștale utilizate pentru livrarea poștei" http://www.census.gov/geo/www/cob/zt_metadata.html

Multe afaceri din centrul orașelor vor avea propriul cod poștal. Veți dori un set de date cât mai complet, așa că asigurați-vă că găsiți o listă de coduri poștale care include atât coduri poștale "punct" (de obicei afaceri), cât și coduri poștale "graniță".

Am experiență în lucrul cu datele despre codurile poștale de la http://www.semaphorecorp.com/. Chiar și acelea nu au fost 100% precise. De exemplu, când campusul meu a primit o nouă adresă poștală și un nou cod poștal, codul a fost plasat greșit. Cu toate acestea, a fost singura sursă de date pe care am găsit-o care avea chiar și noul cod poștal, atât de curând după ce a fost creat.

Aveam o rețetă în cartea mea pentru exact cum să vă îndepliniți cerința... în Drupal. Se baza pe modulul Google Maps Tools ( http://drupal.org/project/gmaps, nu trebuie confundat cu http://drupal.org/project/gmap, de asemenea un modul valoros.) S-ar putea să găsiți niște coduri de exemplu utile în acele module, desigur, ele nu vor funcționa direct în WordPress.

7 oct. 2010 23:27:00