Como implementar uma pesquisa baseada em local (CEP) no WordPress?

19

Estou trabalhando em um site de diretório comercial local que usará tipos de postagem personalizados para as entradas comerciais. Um dos campos será "CEP". Como posso configurar uma pesquisa baseada em local?

Gostaria que os visitantes pudessem inserir seu CEP e escolher uma categoria e mostrar todas as empresas dentro de um determinado raio ou todas as empresas ordenadas por distância. Vi alguns plugins que afirmam fazer isso, mas eles não suportam o WordPress 3.0. Alguma sugestão?

mate
fonte
2
Estou começando uma recompensa porque esta é uma pergunta interessante e desafiadora. Tenho algumas idéias próprias ... mas quero ver se alguém pode apresentar algo mais elegante (e mais fácil de construir).
EAMann # 3/10
Obrigado EAMann. Isso pode ajudar: briancray.com/2009/04/01/…
matt
única sugestão que eu teria atualmente temos aqui é utilizar um plug-in, como vagens
NetConstructor.com
@ NetConstructor.com - eu não sugeriria Pods para isso; realmente não há benefício O Pods fornece esse problema x tipos de postagem personalizados.
MikeSchinkel
@matt : recentemente implementei algo muito semelhante a esse, embora o site ainda não esteja finalizado nem implantado. Na verdade, existe um pouco demais. Eu pretendo empacotá-lo como um plug-in de localizador de loja em algum momento, mas não é algo que eu possa postar como uma solução geral ainda. Entre em contato comigo offline e talvez eu possa ajudar se você não receber a resposta que precisa.
precisa saber é o seguinte

Respostas:

10

Eu modificaria a resposta de gabrielk e da postagem do blog vinculado usando índices de banco de dados e minimizando o número de cálculos de distância reais .

Se você conhece as coordenadas do usuário e a distância máxima (digamos 10 km), pode desenhar uma caixa delimitadora de 20 a 20 km com a localização atual no meio. Obtenha essas coordenadas delimitadoras e consulte apenas armazenamentos entre essas latitudes e longitudes . Ainda não use funções trigonométricas em sua consulta ao banco de dados, pois isso impedirá que índices sejam usados. (Portanto, você pode obter uma loja a 12 km de distância, se estiver no canto nordeste da caixa delimitadora, mas a lançaremos na próxima etapa.)

Calcule apenas a distância (conforme o pássaro voa ou com instruções de direção reais, como preferir) para as poucas lojas que são devolvidas. Isso aumentará drasticamente o tempo de processamento se você tiver um grande número de lojas.

Para a pesquisa relacionada ( "ofereça as dez lojas mais próximas" ), é possível fazer uma pesquisa semelhante, mas com uma estimativa inicial da distância (você começa com uma área de 10 km por 10 km e, se não tiver lojas suficientes, expanda-a para 20 km por 20 km e assim por diante). Para essa distância inicial, suponha que você calcule o número de lojas na área total e use-a. Ou registre o número de consultas necessárias e se adapte ao longo do tempo.

Eu adicionei um exemplo de código completo na pergunta relacionada de Mike e aqui está uma extensão que fornece os locais X mais próximos (rápidos e pouco testados):

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
fonte
8

Primeiro, você precisa de uma tabela parecida com esta:

zip_code    lat     lon
10001       40.77    73.98

... preenchido para cada código postal. Você pode expandir isso adicionando campos de cidade e estado, se quiser procurar dessa maneira.

Então, cada loja pode receber um código postal e, quando você precisar calcular a distância, poderá associar a tabela lat / long aos dados da loja.

Em seguida, você consultará essa tabela para obter a latitude e longitude dos códigos postais da loja e do usuário. Depois de obtê-lo, você pode preencher seu array e passá-lo para uma função "obter distância":

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

Isso é uma prova de conceito, não um código que eu recomendaria implementar. Se você tiver 10.000 lojas, por exemplo, seria uma operação bastante cara consultá-los todos, percorrer e classificá-los em todas as solicitações.

gabrielk
fonte
Os resultados podem ser armazenados em cache? Além disso, seria mais fácil consultar um dos bancos de dados de códigos postais disponíveis comercialmente (ou gratuitos, se houver um)?
matt
1
@matt - O que você quer dizer com consulta a um dos disponíveis comercialmente ou gratuitamente? O cache sempre deve ser possível, consulte codex.wordpress.org/Transients_API
hakre
@hakre: Deixa pra lá, acho que estou falando demais agora. Estou falando sobre o uso de bancos de dados de códigos postais (USPS, Google Maps ...) para obter as distâncias, mas não sabia que eles provavelmente não armazenam as distâncias, apenas armazenam o código postal e as coordenadas. depende de mim calculá-lo.
matt
3

A documentação do MySQL também inclui informações sobre extensões espaciais. Estranhamente, a função distance () padrão não está disponível, mas verifique esta página: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html para obter detalhes sobre como "converter os dois valores de POINT em um LINESTRING e depois calculam o comprimento disso. "

Observe que é provável que todo fornecedor ofereça latitudes e longitudes diferentes, representando o "centróide" de um CEP. Também vale a pena saber que não há arquivos de "limite" de CEP definidos. Cada fornecedor terá seu próprio conjunto de limites que correspondem aproximadamente às listas específicas de endereços que compõem um código postal do USPS. (Por exemplo, em alguns "limites", é necessário incluir os dois lados da rua, em outros, apenas um.) As ZCTAs (Áreas de Tabulação de CEP), amplamente usadas pelos fornecedores ", não representam precisamente as áreas de entrega de CEP, e não inclui todos os códigos postais usados ​​para entrega de correio " http://www.census.gov/geo/www/cob/zt_metadata.html

Muitas empresas do centro terão seu próprio código postal. Você deseja que o conjunto de dados seja o mais completo possível, encontre uma lista de códigos postais que inclua os códigos postais "ponto" (geralmente empresas) e os códigos postais "limite".

Tenho experiência em trabalhar com os dados de CEP de http://www.semaphorecorp.com/ . Mesmo isso não era 100% exato. Por exemplo, quando meu campus assumiu um novo endereço para correspondência e um novo CEP, o CEP foi extraviado. Dito isto, foi a única fonte de dados que eu descobri que ainda tinha o novo código postal, logo após a sua criação.

Eu tinha uma receita no meu livro para exatamente como atender seu pedido ... no Drupal. Baseava-se no módulo Ferramentas do Google Maps ( http://drupal.org/project/gmaps , para não confundir com http://drupal.org/project/gmap , também um módulo válido.) Você pode encontrar alguns exemplos úteis código nesses módulos, embora, é claro, eles não funcionem imediatamente no WordPress.

Marjorie Roswell
fonte