Come implementare una ricerca basata sulla posizione (codice postale) in WordPress?

28 set 2010, 01:32:13
Visualizzazioni: 26.2K
Voti: 21

Sto lavorando su un sito di directory aziendale locale che utilizzerà custom post types per le inserzioni delle attività. Uno dei campi sarà "Codice Postale". Come posso impostare una ricerca basata sulla posizione?

Vorrei che i visitatori possano inserire il loro codice postale e scegliere una categoria per visualizzare tutte le attività all'interno di un determinato raggio, oppure tutte le attività ordinate per distanza. Ho visto un paio di plugin che dichiarano di farlo ma non supportano WordPress 3.0. Avete suggerimenti?

9
Commenti

Sto iniziando una bounty perché questa è una domanda interessante e impegnativa. Ho alcune idee mie... ma voglio vedere se qualcuno riesce a trovare qualcosa di più elegante (e più semplice da realizzare).

EAMann EAMann
3 ott 2010 17:53:59

l'unico suggerimento che avrei al momento è utilizzare un plugin come pods

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

@NetConstructor.com - Non consiglierei Pods per questo; in realtà non c'è alcun vantaggio che Pods offra rispetto ai Custom Post Types per questo problema.

MikeSchinkel MikeSchinkel
5 ott 2010 02:36:59

@matt: Recentemente ho implementato qualcosa di molto simile, anche se il sito non è ancora finalizzato né pubblicato. C'è parecchio da fare, in realtà. Ho intenzione di impacchettarlo come un plugin per localizzatore di negozi a un certo punto, ma non è qualcosa che posso pubblicare come soluzione generica al momento. Contattami privatamente e potrei essere in grado di aiutarti se non trovi la risposta che cerchi.

MikeSchinkel MikeSchinkel
5 ott 2010 02:38:13

@mikeschinkel -- Ehi Mike, l'unico motivo per cui ho menzionato Pods era perché potrebbe offrire a Chris più flessibilità, dato che credo crei le proprie tabelle del database, il che potrebbe semplificare il processo o renderlo più robusto. Ma suppongo tu abbia ragione, immagino che la stessa cosa possa essere fatta con un custom post type. A proposito, Mike... aspetta un'email da parte mia a breve ;-)

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

@NetConstructor: Stavo valutando questo approccio seguendo un tutorial entro lunedì a mezzogiorno ma ero preoccupato che non fosse scalabile o a prova di futuro come utilizzare le funzionalità native.

matt matt
5 ott 2010 20:11:12

@mikeschinkel: Grazie, sospetto che riceverai un'email da me. Solo per curiosità, stai utilizzando un database di terze parti di qualche tipo?

matt matt
5 ott 2010 20:12:07

@matt: No, l'ho scritto tutto da solo. Interroga Google Maps e memorizza nella cache i risultati per un uso futuro e per ridurre al minimo il numero di chiamate giornaliere a Google Maps. Memorizza anche le distanze in modo che possano essere indicizzate invece di eseguire una query non indicizzabile. La logica di caching è in parte ciò che introduce la complessità.

MikeSchinkel MikeSchinkel
6 ott 2010 00:33:06
Mostra i restanti 4 commenti
Tutte le risposte alla domanda 3
0
10

Modificherei la risposta di gabrielk e il post del blog collegato utilizzando indici del database e riducendo al minimo il numero di calcoli effettivi delle distanze.

Se conosci le coordinate dell'utente e la distanza massima (ad esempio 10km), puoi disegnare un rettangolo di delimitazione di 20km per 20km con la posizione corrente al centro. Ottieni queste coordinate di delimitazione e interroga solo i negozi compresi tra queste latitudini e longitudini. Non utilizzare ancora funzioni trigonometriche nella query del database, poiché ciò impedirebbe l'uso degli indici. (Quindi potresti ottenere un negozio che si trova a 12km da te se si trova nell'angolo nord-est del rettangolo di delimitazione, ma lo escluderemo nel passaggio successivo.)

Calcola la distanza (in linea d'aria o con indicazioni stradali reali, a tua preferenza) solo per i pochi negozi restituiti. Ciò migliorerà drasticamente il tempo di elaborazione se hai un gran numero di negozi.

Per la ricerca correlata ("fornisci i dieci negozi più vicini") puoi eseguire una ricerca simile, ma con un'ipotesi di distanza iniziale (quindi inizi con un'area di 10km per 10km e se non hai abbastanza negozi la espandi a 20km per 20km e così via). Per questa ipotesi di distanza iniziale calcoli una volta il numero di negozi sull'area totale e lo utilizzi. Oppure registra il numero di query necessarie e adattati nel tempo.

Ho aggiunto un esempio di codice completo alla domanda correlata di Mike, ed ecco un'estensione che ti fornisce le X posizioni più vicine (rapido e poco testato):

class Monkeyman_Geo_ClosestX extends Monkeyman_Geo
{
    public static $closestXStartDistanceKm = 10;
    public static $closestXMaxDistanceKm = 1000; // Non cercare oltre questo limite

    public function addAdminPages()
    {
        parent::addAdminPages();
        add_management_page( 'Test posizione più vicina', 'Test posizione più vicina', '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>Numero di articoli: <input size="5" name="post_count" value="10"/></p>
    <p>Latitudine centro: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Longitudine centro: <input size="10" name="center_lon" value="{$default_lon}"/></p>
    <p><input type="submit" name="search" value="Cerca!"/></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));
    }

    /**
     * Ottieni i X articoli più vicini a una posizione data
     *
     * Questo potrebbe restituire più di X risultati, e mai più di
     * self::$closestXMaxDistanceKm di distanza (per evitare ricerche infinite)
     * I risultati sono ordinati per distanza
     *
     * L'algoritmo inizia con tutte le posizioni non più lontane di
     * self::$closestXStartDistanceKm, quindi espande quest'area
     * (raddoppiando la distanza) finché non vengono trovati abbastanza risultati.
     *
     * Il numero di calcoli costosi dovrebbe essere minimizzato.
     */
    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) {
                    // Includi solo quelli che sono nel raggio del cerchio, non nel rettangolo di delimitazione, altrimenti potremmo perderne alcuni più vicini nel passaggio successivo
                    $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 ott 2010 12:52:40
3

Innanzitutto ti servirà una tabella simile a questa:

zip_code    lat     lon
10001       40.77    73.98

...popolata per ogni codice postale. Puoi espanderla aggiungendo campi per città e stato se vuoi effettuare ricerche anche in quel modo.

Poi ogni negozio può essere associato a un codice postale, e quando devi calcolare le distanze puoi unire la tabella lat/long con i dati dei negozi.

Quindi interrogherai quella tabella per ottenere latitudine e longitudine sia del negozio che del codice postale dell'utente. Una volta ottenuti, puoi popolare il tuo array e passarlo a una funzione "calcola distanza":

$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 'Il negozio ' . $id . ' si trova a ' . $distance . ' di distanza';
    }
}

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

Questo è pensato come proof-of-concept, non come codice che consiglierei effettivamente di implementare. Se hai 10.000 negozi, ad esempio, sarebbe un'operazione piuttosto costosa interrogarli tutti, ciclarli e ordinarli a ogni richiesta.

5 ott 2010 01:04:21
Commenti

I risultati potrebbero essere memorizzati nella cache? Inoltre, sarebbe più semplice interrogare uno dei database di codici postali disponibili commercialmente (o gratuiti se ce ne sono)?

matt matt
5 ott 2010 20:03:08

@matt - Cosa intendi con "interrogare uno di quelli disponibili commercialmente o gratuitamente"? La memorizzazione nella cache dovrebbe essere sempre possibile, vedi http://codex.wordpress.org/Transients_API

hakre hakre
6 ott 2010 13:08:49

@hakre: Lascia perdere, penso di aver parlato oltre le mie capacità. Sto parlando di usare database di codici postali (USPS, Google Maps...) per ottenere le distanze, ma non avevo realizzato che probabilmente non memorizzano le distanze, ma solo il codice postale e le coordinate, e spetterebbe a me calcolarle.

matt matt
7 ott 2010 23:32:05
0

La documentazione di MySQL include anche informazioni sulle estensioni spaziali. Stranamente, la funzione standard distance() non è disponibile, ma controlla questa pagina: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html per i dettagli su come "convertire i due valori POINT in un LINESTRING e poi calcolare la lunghezza di quest'ultimo."

Tieni presente che ogni fornitore probabilmente offrirà latitudini e longitudini diverse per rappresentare il "centroide" di un codice postale. Vale anche la pena sapere che non esistono veri e propri file di "confini" definiti per i codici postali. Ogni fornitore avrà il proprio set di confini che corrispondono approssimativamente agli elenchi specifici di indirizzi che compongono un codice postale USPS. (Ad esempio, in alcuni "confini" dovresti includere entrambi i lati della strada, in altri solo uno.) Le Aree di Tabulazione dei Codici Postali (ZCTAs), ampiamente utilizzate dai fornitori, "non rappresentano con precisione le aree di consegna dei codici postali e non includono tutti i codici postali utilizzati per la consegna della posta" http://www.census.gov/geo/www/cob/zt_metadata.html

Molte attività commerciali del centro avranno il proprio codice postale. Vorrai un dataset il più completo possibile, quindi assicurati di trovare un elenco di codici postali che includa sia codici postali "punto" (di solito attività commerciali), sia codici postali "confine".

Ho esperienza nel lavorare con i dati dei codici postali di http://www.semaphorecorp.com/. Anche quelli non erano accurati al 100%. Ad esempio, quando il mio campus ha adottato un nuovo indirizzo postale e un nuovo codice postale, il codice è stato posizionato erroneamente. Detto questo, era l'unica fonte di dati che ho trovato che avesse addirittura il nuovo codice postale, così poco dopo la sua creazione.

Avevo una ricetta nel mio libro per soddisfare esattamente la tua richiesta... in Drupal. Si basava sul modulo Google Maps Tools (http://drupal.org/project/gmaps, da non confondere con http://drupal.org/project/gmap, anch'esso un modulo valido.) Potresti trovare del codice di esempio utile in quei moduli, anche se ovviamente non funzioneranno immediatamente in WordPress.

7 ott 2010 23:27:00