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

19

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

Me gustaría que los visitantes puedan ingresar su código postal y elegir una categoría y mostrar todas las empresas dentro de un determinado radio, o todas las empresas ordenadas por distancia. Vi un par de complementos que dicen hacer esto, pero no son compatibles con WordPress 3.0. ¿Alguna sugerencia?

mate
fuente
2
Estoy comenzando una recompensa porque esta es una pregunta interesante y desafiante. Tengo algunas ideas propias ... pero quiero ver si alguien puede llegar a algo más elegante (y más fácil de construir).
EAMann
Gracias EAMann. Esto puede ayudar: briancray.com/2009/04/01/…
mate
La única sugerencia que tendría actualmente aquí es utilizar un complemento como pods
NetConstructor.com
@ NetConstructor.com - No sugeriría Pods para esto; realmente no hay beneficio Pods proporciona este problema frente a los tipos de publicaciones personalizadas.
MikeSchinkel
@matt : Recientemente he implementado algo muy similar a esto, aunque el sitio aún no está finalizado ni implementado. También hay bastante, en realidad. Planeo empaquetarlo como un complemento de localizador de tiendas en algún momento, pero aún no es algo que pueda publicar como una solución general. Contácteme sin conexión y podría ayudarlo si no obtiene la respuesta que necesita.
MikeSchinkel

Respuestas:

10

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

Si conoce las coordenadas del usuario y conoce la distancia máxima (por ejemplo, 10 km), puede dibujar un cuadro delimitador de 20 km por 20 km con la ubicación actual en el medio. Obtenga estas coordenadas limitantes y solo almacene consultas entre estas latitudes y longitudes . Todavía no use las funciones de trigonometría en su consulta de base de datos, ya que esto evitará que se utilicen índices. (Por lo tanto, es posible que obtenga una tienda que esté a 12 km de distancia si está en la esquina noreste de la caja delimitadora, pero la descartamos en el siguiente paso).

Solo calcule la distancia (como vuela el pájaro o con las indicaciones de manejo reales, como prefiera) para las pocas tiendas que se devuelven. Esto mejorará drásticamente el tiempo de procesamiento si tiene una gran cantidad de tiendas.

Para la búsqueda relacionada ( "dar las diez tiendas más cercanas" ) puede hacer una búsqueda similar, pero con una suposición de distancia inicial (por lo que comienza con un área de 10 km por 10 km, y si no tiene suficientes tiendas, la expande a 20 km por 20 km y así sucesivamente). Para esta distancia inicial, suponga que una vez calcule el número de tiendas sobre el área total y úsela. O registre la cantidad de consultas necesarias y adáptese con el tiempo.

Agregué un ejemplo de código completo en la pregunta relacionada de Mike , y aquí hay una extensión que le brinda las ubicaciones X más cercanas (rápida y apenas probada):

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();
Jan Fabry
fuente
8

Primero necesitas una tabla que se vea así:

zip_code    lat     lon
10001       40.77    73.98

... rellenado para cada código postal. Puede ampliar esto agregando campos de ciudad y estado si desea buscar de esa manera.

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

Luego consultará esa tabla para obtener la latitud y longitud de la tienda y los códigos postales del usuario. Una vez que lo obtenga, puede completar su matriz y pasarla a la 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 '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);
}

Esto significa una prueba de concepto, no un código que realmente recomendaría implementar. Si tiene 10,000 tiendas, por ejemplo, sería una operación bastante costosa consultarlas todas y recorrerlas y clasificarlas en cada solicitud.

gabrielk
fuente
¿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ódigo postal disponibles comercialmente (o gratuitas si hay una)?
mate
1
@matt : ¿Qué quiere decir con una consulta comercial o gratuita disponible? El almacenamiento en caché siempre debe ser posible, consulte codex.wordpress.org/Transients_API
hakre
@hakre: No importa, creo que estoy hablando por encima de mi cabeza ahora. Estoy hablando de usar bases de datos de código postal (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 lo hará. Depende de mí calcularlo.
mate
3

La documentación de MySQL también incluye información sobre extensiones espaciales. Curiosamente, la función de distancia estándar () no está disponible, pero consulte 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 LINESTRING y luego calculan la longitud de eso ".

Tenga en cuenta que es probable que cada proveedor ofrezca diferentes latitudes y longitudes que representan el "centroide" de un código postal. También vale la pena saber que no hay archivos de "límites" de código postal definidos reales. Cada proveedor tendrá su propio conjunto de límites que coinciden aproximadamente con las listas específicas de direcciones que forman un código postal de USPS. (Por ejemplo, en algunos "límites", necesitaría incluir ambos lados de la calle, en otros, solo uno). y no incluya todos los códigos postales utilizados para la entrega por correo " http://www.census.gov/geo/www/cob/zt_metadata.html

Muchas empresas del centro tendrán su propio código postal. Querrá un conjunto de datos lo más completo posible, así que asegúrese de encontrar una lista de códigos postales que incluya códigos postales de "punto" (generalmente empresas) y códigos postales de "límite".

Tengo experiencia trabajando con los datos del código postal de http://www.semaphorecorp.com/ . Incluso eso no era 100% exacto. Por ejemplo, cuando mi campus adquirió una nueva dirección postal y un nuevo código postal, el código postal estaba fuera de lugar. Dicho esto, fue la única fuente de datos que encontré que incluso TENÍA el nuevo código postal, tan pronto como fue creado.

Tenía una receta en mi libro para saber exactamente cómo satisfacer su solicitud ... en Drupal. Se basó en el módulo Herramientas de Google Maps ( http://drupal.org/project/gmaps , que no debe confundirse con http://drupal.org/project/gmap , también un módulo valioso). Puede encontrar alguna muestra útil código en esos módulos, aunque, por supuesto, no funcionarán de fábrica en WordPress.

Marjorie Roswell
fuente