Come implementare una ricerca basata sulla posizione (codice postale) in WordPress?
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?

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

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.

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 - 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: 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.

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.
