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


19

Sto lavorando su un sito di directory aziendale locale che utilizzerà tipi di posta personalizzati per le voci dell'attività. Uno dei campi sarà "Codice postale". Come posso impostare una ricerca basata sulla posizione?

Vorrei che i visitatori potessero inserire il loro codice postale e scegliere una categoria e mostrare tutte le attività commerciali con un certo raggio o tutte le attività ordinate a distanza. Ho visto un paio di plugin che affermano di farlo ma non supportano WordPress 3.0. Eventuali suggerimenti?


2
Sto iniziando una taglia perché questa è una domanda interessante e stimolante. Ho alcune idee per conto mio ... ma voglio vedere se qualcuno può inventare qualcosa di più elegante (e più facile da costruire).
EAMann,

Grazie EAMann. Questo può aiutare: briancray.com/2009/04/01/…
matt

l'unico suggerimento che vorrei avere qui è di utilizzare un plugin come pod
NetConstructor.com

@ NetConstructor.com - Non consiglierei i Pod per questo; non ci sono davvero vantaggi Pod offre questo problema rispetto ai tipi di post personalizzati.
MikeSchinkel,

@matt : recentemente ho implementato qualcosa di molto simile a questo, sebbene il sito non sia stato finalizzato né distribuito ancora. C'è anche un bel po ', in realtà. Ho in programma di impacchettarlo come un plug-in localizzatore del negozio ad un certo punto, ma non è alcuni che posso ancora pubblicare come soluzione generale. Contattami offline e potrei aiutarti se non ottieni la risposta di cui hai bisogno.
MikeSchinkel,

Risposte:


10

Modificherei la risposta di gabrielk e il post sul blog collegato utilizzando gli indici del database e riducendo al minimo il numero di calcoli della distanza effettiva .

Se conosci le coordinate dell'utente e conosci la distanza massima (diciamo 10 km), puoi disegnare un riquadro di delimitazione che è di 20 km per 20 km con la posizione corrente nel mezzo. Ottieni queste coordinate di delimitazione e interroga solo gli archivi tra queste latitudini e longitudini . Non utilizzare ancora le funzioni di trigonometria nella query del database, poiché ciò impedirà l'utilizzo degli indici. (Quindi potresti ottenere un negozio a 12 km da te se si trova nell'angolo nord-est del riquadro di delimitazione, ma lo butteremo fuori nel passaggio successivo.)

Calcola la distanza (come vola l'uccello o con le indicazioni stradali effettive, come preferisci) per i pochi negozi che vengono restituiti. Ciò migliorerà drasticamente i tempi di elaborazione se si dispone di un numero elevato di negozi.

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

Ho aggiunto un esempio di codice completo alla domanda correlata di Mike , ed ecco un'estensione che ti dà le posizioni X più vicine (veloci e appena testate):

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

Per prima cosa hai bisogno di un tavolo che assomigli a questo:

zip_code    lat     lon
10001       40.77    73.98

... popolato per ogni codice postale. Puoi ampliarlo aggiungendo campi di città e stato se vuoi cercare in quel modo.

Quindi a ciascun negozio può essere assegnato un codice postale e quando è necessario calcolare la distanza è possibile unire la tabella lat / long ai dati del negozio.

Quindi eseguirai una query su quella tabella per ottenere la latitudine e la longitudine per il negozio e i codici postali dell'utente. Una volta ottenuto, puoi popolare l'array e passarlo a una funzione "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);
}

Questo è inteso come una prova di concetto, non come codice che consiglierei di implementare. Se hai 10.000 negozi, ad esempio, sarebbe un'operazione piuttosto costosa interrogarli tutti e scorrere e ordinarli su ogni richiesta.


I risultati potrebbero essere memorizzati nella cache? Inoltre, sarebbe più facile interrogare uno dei database di codice postale disponibili in commercio (o gratuitamente se ce n'è uno)?
matt

1
@matt - Cosa intendi per query di uno dei disponibili commercialmente o gratuitamente? La memorizzazione nella
hakre,

@hakre: Nevermind, penso di parlare ora sopra la mia testa. Sto parlando di utilizzare un database di codici postali (USPS, Google Maps ...) per ottenere le distanze, ma non mi ero reso conto che probabilmente non memorizzano le distanze, memorizzano semplicemente il codice postale e le coordinate e dipende da me per calcolarlo.
matt

3

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

Si noti che è probabile che ogni fornitore offra diverse latitudini e longitudini che rappresentano il "centroide" di un codice postale. Vale anche la pena sapere che non ci sono file "limite" di codici postali definiti. Ogni fornitore avrà il proprio insieme di limiti 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.) Aree di tabulazione del codice postale (ZCTA), ampiamente utilizzate dai fornitori, "non rappresentano con precisione le aree di consegna del codice postale, e non includere tutti i codici postali utilizzati per la consegna della posta " http://www.census.gov/geo/www/cob/zt_metadata.html

Molte aziende del centro avranno il proprio codice postale. Avrai bisogno di un set di dati il ​​più completo possibile, quindi assicurati di trovare un elenco di codici postali che includa sia i codici postali "a punti" (in genere aziende) sia i codici postali "al contorno".

Ho esperienza di lavoro con i dati di codice postale da http://www.semaphorecorp.com/ . Anche quello non era preciso al 100%. Ad esempio, quando il mio campus ha assunto un nuovo indirizzo postale e un nuovo codice postale, lo zip era fuori luogo. Detto questo, è stata l'unica fonte di dati che ho scoperto che aveva anche il nuovo codice postale, così presto dopo che è stato creato.

Nel mio libro avevo una ricetta per sapere esattamente come soddisfare la tua richiesta ... in Drupal. Si basava sul modulo Strumenti di Google Maps ( http://drupal.org/project/gmaps , da non confondere con http://drupal.org/project/gmap , anche un modulo degno.) Potresti trovare qualche esempio utile codice in quei moduli, anche se ovviamente non funzioneranno immediatamente in WordPress.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.