¿Cómo puedo implementar una búsqueda basada en ubicación (código postal) en WordPress?

28 sept 2010, 01:32:13
Vistas: 26.2K
Votos: 21

Estoy trabajando en un sitio de directorio de negocios locales que utilizará tipos de publicación personalizados para las entradas de negocios. Uno de los campos será "Código Postal". ¿Cómo puedo configurar una búsqueda basada en ubicación?

Me gustaría que los visitantes pudieran ingresar su código postal y elegir una categoría para mostrar todos los negocios dentro de un cierto radio, o todos los negocios ordenados por distancia. Vi algunos plugins que dicen hacer esto pero no son compatibles con WordPress 3.0. ¿Alguna sugerencia?

9
Comentarios

Estoy iniciando una recompensa porque esta es una pregunta interesante y desafiante. Tengo algunas ideas propias... pero quiero ver si alguien puede proponer algo más elegante (y más fácil de construir).

EAMann EAMann
3 oct 2010 17:53:59

La única sugerencia que tengo actualmente es utilizar un plugin como Pods

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

@NetConstructor.com - No recomendaría Pods para esto; realmente no hay ningún beneficio que Pods aporte a este problema en comparación con los Tipos de Publicación Personalizados.

MikeSchinkel MikeSchinkel
5 oct 2010 02:36:59

@matt: Recientemente he implementado algo muy similar a esto, aunque el sitio aún no está finalizado ni desplegado. En realidad hay bastante trabajo involucrado. Planeo empaquetarlo como un plugin de localizador de tiendas en algún momento, pero no es algo que pueda publicar como una solución general todavía. Contáctame fuera de línea y tal vez pueda ayudarte si no obtienes la respuesta que necesitas.

MikeSchinkel MikeSchinkel
5 oct 2010 02:38:13

@mikeschinkel -- Hola Mike, la única razón por la que mencioné Pods fue porque podría darle a Chris más flexibilidad, ya que creo que crea sus propias tablas de base de datos lo que podría simplificar el proceso o hacerlo más robusto, pero supongo que tienes razón, supongo que lo mismo podría hacerse a través de un tipo de publicación personalizado. Por cierto, Mike... estate atento a un correo mío pronto ;-)

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

@NetConstructor: Estaba considerando este enfoque usando un tutorial para el lunes al mediodía pero me preocupaba que no fuera tan escalable o preparado para el futuro como usar la funcionalidad nativa.

matt matt
5 oct 2010 20:11:12

@mikeschinkel: Gracias, sospecho que recibirás un correo de mi parte. Por curiosidad, ¿estás usando alguna base de datos de terceros?

matt matt
5 oct 2010 20:12:07

@matt: No, lo escribí todo yo mismo. Consulta Google Maps y almacena en caché los resultados para uso futuro y para minimizar la cantidad de llamadas diarias a Google Maps. También guarda en caché las distancias para que puedan ser indexadas en lugar de ejecutar una consulta no indexable. La lógica de caché es en parte de donde viene la complejidad.

MikeSchinkel MikeSchinkel
6 oct 2010 00:33:06
Mostrar los 4 comentarios restantes
Todas las respuestas a la pregunta 3
0
10

Modificaría la respuesta de gabrielk y la publicación de blog vinculada utilizando índices de bases de datos y minimizando el número de cálculos reales de distancia.

Si conoces las coordenadas del usuario y la distancia máxima (digamos 10km), puedes dibujar un cuadro delimitador de 20km por 20km con la ubicación actual en el centro. Obtén estas coordenadas delimitadoras y consulta solo las tiendas entre estas latitudes y longitudes. No uses aún funciones trigonométricas en tu consulta de base de datos, ya que esto evitará que se usen los índices. (Así que podrías obtener una tienda que esté a 12km de ti si está en la esquina noreste del cuadro delimitador, pero la descartaremos en el siguiente paso.)

Solo calcula la distancia (en línea recta o con direcciones reales de conducción, como prefieras) para las pocas tiendas que se devuelvan. Esto mejorará drásticamente el tiempo de procesamiento si tienes un gran número de tiendas.

Para la búsqueda relacionada ("dame las diez tiendas más cercanas") puedes hacer una búsqueda similar, pero con una suposición inicial de distancia (así que empiezas con un área de 10km por 10km, y si no tienes suficientes tiendas, la amplías a 20km por 20km y así sucesivamente). Para esta suposición inicial de distancia, calculas una vez el número de tiendas sobre el área total y lo usas. O registra el número de consultas necesarias y adáptalo con el tiempo.

He añadido un ejemplo de código completo en la pregunta relacionada de Mike, y aquí hay una extensión que te da las ubicaciones X más cercanas (rápido y apenas probado):

class Monkeyman_Geo_ClosestX extends Monkeyman_Geo
{
    public static $closestXStartDistanceKm = 10;
    public static $closestXMaxDistanceKm = 1000; // No buscar más allá de esto

    public function addAdminPages()
    {
        parent::addAdminPages();
        add_management_page( 'Prueba de ubicación más cercana', 'Prueba de ubicación más cercana', '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>Número de publicaciones: <input size="5" name="post_count" value="10"/></p>
    <p>Latitud del centro: <input size="10" name="center_lat" value="{$default_lat}"/>
        <br/>Longitud del centro: <input size="10" name="center_lon" value="{$default_lon}"/></p>
    <p><input type="submit" name="search" value="¡Buscar!"/></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));
    }

    /**
     * Obtiene las X publicaciones más cercanas a una ubicación dada
     *
     * Esto podría devolver más de X resultados, y nunca más de
     * self::$closestXMaxDistanceKm de distancia (para evitar búsquedas infinitas)
     * Los resultados están ordenados por distancia
     *
     * El algoritmo comienza con todas las ubicaciones no más lejos que
     * self::$closestXStartDistanceKm, y luego expande esta área
     * (duplicando la distancia) hasta encontrar suficientes coincidencias.
     *
     * El número de cálculos costosos debe minimizarse.
     */
    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) {
                    // Solo incluir aquellas que están dentro del radio del círculo, no del cuadro delimitador, de lo contrario podríamos perder algunas más cercanas en el siguiente paso
                    $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

Primero necesitas una tabla que se vea algo así:

zip_code    lat     lon
10001       40.77    73.98

...poblada para cada código postal. Puedes ampliar esto agregando campos de ciudad y estado si deseas buscar de esa manera.

Luego, a cada tienda se le puede asignar un código postal, y cuando necesites calcular la distancia puedes unir la tabla de lat/long con los datos de la tienda.

Después consultarás esa tabla para obtener la latitud y longitud de los códigos postales de la tienda y del usuario. Una vez que los obtengas, puedes llenar tu arreglo y pasarlo a una función "obtener distancia":

$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 'La tienda ' . $id . ' está a ' . $distance . ' de distancia';
    }
}

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

Esto está pensado como una prueba de concepto, no como código que realmente recomendaría implementar. Si tienes 10,000 tiendas, por ejemplo, sería una operación bastante costosa consultarlas todas, recorrerlas y ordenarlas en cada solicitud.

5 oct 2010 01:04:21
Comentarios

¿Se podrían almacenar en caché los resultados? Además, ¿sería más fácil consultar una de las bases de datos de códigos postales disponibles comercialmente (o gratuitas si hay alguna)?

matt matt
5 oct 2010 20:03:08

@matt - ¿Qué quieres decir con consultar una de las bases disponibles comercialmente o gratuitas? El almacenamiento en caché siempre debería ser posible, consulta http://codex.wordpress.org/Transients_API

hakre hakre
6 oct 2010 13:08:49

@hakre: No importa, creo que estoy hablando más de lo que sé. Me refiero a usar bases de datos de códigos postales (USPS, Google Maps...) para obtener las distancias pero no me di cuenta de que probablemente no almacenan las distancias, solo almacenan el código postal y las coordenadas y sería mi responsabilidad calcularlas.

matt matt
7 oct 2010 23:32:05
0

La documentación de MySQL también incluye información sobre extensiones espaciales. Curiosamente, la función estándar distance() no está disponible, pero revisa esta página: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html para obtener detalles sobre cómo "convertir los dos valores POINT a un LINESTRING y luego calcular la longitud de este."

Ten en cuenta que cada proveedor probablemente ofrecerá diferentes latitudes y longitudes que representan el "centroide" de un código postal. También vale la pena saber que no existen archivos reales definidos de "límites" de códigos postales. Cada proveedor tendrá su propio conjunto de límites que coinciden aproximadamente con las listas específicas de direcciones que conforman un código postal del USPS. (Por ejemplo, en algunos "límites" necesitarías incluir ambos lados de la calle, en otros, solo uno.) Las Áreas de Tabulación de Códigos Postales (ZCTAs), ampliamente utilizadas por los proveedores, "no representan con precisión las áreas de entrega de códigos postales, y no incluyen todos los códigos postales utilizados para el envío de correo" http://www.census.gov/geo/www/cob/zt_metadata.html

Muchos negocios del centro tendrán su propio código postal. Desearás un conjunto de datos lo más completo posible, así que asegúrate de encontrar una lista de códigos postales que incluya tanto códigos postales de "punto" (generalmente negocios), como códigos postales de "límite".

Tengo experiencia trabajando con los datos de códigos postales de http://www.semaphorecorp.com/. Incluso esos no eran 100% precisos. Por ejemplo, cuando mi campus adoptó una nueva dirección postal y un nuevo código postal, el código postal fue mal ubicado. Dicho esto, era la única fuente de datos que encontré que incluso TENÍA el nuevo código postal, tan poco después de su creación.

Tenía una receta en mi libro sobre exactamente cómo cumplir con tu solicitud... en Drupal. Se basaba en el módulo Google Maps Tools ( http://drupal.org/project/gmaps, que no debe confundirse con http://drupal.org/project/gmap, también un módulo valioso). Podrías encontrar algún código de muestra útil en esos módulos, aunque, por supuesto, no funcionarán directamente en WordPress.

7 oct 2010 23:27:00