Como posso implementar uma pesquisa baseada em localização (código postal) no WordPress?

19

Estou trabalhando em um site de diretório de empresas local que usará tipos de postagem personalizados para as entradas de negócios. Um dos campos será "CEP". Como posso configurar uma pesquisa baseada em localização?

Gostaria que os visitantes pudessem inserir seu código postal e escolher uma categoria e mostrar todos os negócios em um determinado raio ou todas as empresas solicitadas por distância. Eu vi alguns plugins que afirmam fazer isso, mas eles não suportam o WordPress 3.0. Alguma sugestão?

    
por matt 28.09.2010 / 00:32

3 respostas

10

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

Se você souber as coordenadas do usuário e souber a distância máxima (digamos, 10 km), poderá desenhar uma caixa delimitadora que fica a 20 km por 20 km com a localização atual no meio. Obtenha estas coordenadas delimitadoras e consulte apenas as lojas entre essas latitudes e longitudes . Ainda não use funções de trigonometria em sua consulta de banco de dados, pois isso evitará que índices sejam usados. (Assim, você pode obter uma loja que fica a 12 km de distância de você, se estiver no canto nordeste da caixa delimitadora, mas a descartamos no próximo passo.)

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

Para a pesquisa relacionada ( "dar as dez lojas mais próximas" ) você pode fazer uma pesquisa similar, mas com uma estimativa inicial de distância (então você começa com uma área de 10km por 10km, e se não tem lojas suficientes, você a expande para 20km por 20km e assim por diante). Para essa distância inicial, você calcula o número de lojas na área total e usa isso. Ou registre o número de consultas necessárias e adapte-as ao longo do tempo.

Eu adicionei um exemplo de código completo na pergunta relacionada do Mike , e aqui está uma extensão que fornece os locais X mais próximos (rápidos e mal 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();
    
por Jan Fabry 07.10.2010 / 11:52
8

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

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

Em seguida, cada loja pode receber um código postal e, quando precisar calcular a distância, você pode ingressar na tabela de lat / long nos dados da loja.

Em seguida, você consultará essa tabela para obter a latitude e a longitude da loja e dos CEPs do usuário. Depois de conseguir isso, você pode preencher sua matriz e passá-la 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ê tem 10.000 lojas, por exemplo, seria uma operação bastante cara para consultá-las, fazer um loop e classificá-las em todas as solicitações.

    
por gabrielk 05.10.2010 / 00:04
3

A documentação do MySQL também inclui informações sobre extensões espaciais. Estranhamente, a função standard distance () não está disponível, mas verifique esta página: enlace para obter detalhes sobre como "converter os dois valores POINT em um LINESTRING e, em seguida, calcular o tamanho dele".

Observe que todos os fornecedores podem oferecer diferentes latitudes e longitudes representando o "centróide" de um código postal. Também vale a pena saber que não existem arquivos "boundary" de código postal definidos reais. Cada fornecedor terá seu próprio conjunto de limites que correspondem aproximadamente às listas específicas de endereços que compõem um CEP do USPS. (Por exemplo, em alguns "limites" você precisaria incluir ambos os lados da rua, em outros, apenas um.) As Áreas de Tabulação do CEP (ZCTAs), amplamente usadas pelos fornecedores, "não representam precisamente áreas de entrega de CEPs, e não inclui todos os CEPs usados para entrega de e-mails " enlace

Muitas empresas do centro terão seu próprio código postal. Você desejará um conjunto de dados tão completo quanto possível, portanto, certifique-se de encontrar uma lista de códigos postais que inclua códigos postais "ponto" (geralmente empresas) e códigos postais "limite".

Tenho experiência em trabalhar com os dados de CEP do enlace . Mesmo isso não foi 100% exato. Por exemplo, quando meu campus aceitou um novo endereço de correspondência e um novo código postal, o zip foi perdido. Dito isto, foi a única fonte de dados que descobri que até tinha o novo código postal, logo após ter sido criado.

Eu tinha uma receita no meu livro para saber exatamente como atender a sua solicitação ... no Drupal. Ele se baseou no módulo de Ferramentas do Google Maps ( enlace , para não ser confundido com enlace , também um módulo digno.) Você pode encontrar algum código de amostra útil nesses módulos, embora, é claro, eles não funcionem fora da caixa no WordPress.

    
por Marjorie Roswell 07.10.2010 / 22:27