Jak zaimplementować wyszukiwanie oparte na lokalizacji (kod pocztowy) w WordPress?

19

Pracuję nad lokalną witryną katalogu firm, która będzie używać niestandardowych typów wpisów dla wpisów biznesowych. Jednym z pól będzie „Kod pocztowy”. Jak skonfigurować wyszukiwanie oparte na lokalizacji?

Chciałbym, aby odwiedzający mogli wprowadzić swój kod pocztowy i wybrać kategorię oraz pokazać wszystkie firmy o określonym promieniu lub wszystkie firmy uporządkowane według odległości. Widziałem kilka wtyczek, które twierdzą, że to robi, ale nie obsługują WordPress 3.0. Jakieś sugestie?

matowy
źródło
2
Zaczynam nagrodę, ponieważ jest to interesujące i trudne pytanie. Mam kilka własnych pomysłów ... ale chcę sprawdzić, czy ktoś może wymyślić coś bardziej eleganckiego (i łatwiejszego do zbudowania).
EAMann
Dzięki EAMann. Może to pomóc: briancray.com/2009/04/01/…
mat.
jedyną sugestią, którą obecnie tutaj mam, jest użycie wtyczki takiej jak strąki
NetConstructor.com
@ NetConstructor.com - Nie sugerowałbym do tego Pods; naprawdę nie ma korzyści, że strąki zapewniają ten problem w porównaniu do niestandardowych typów postów.
MikeSchinkel,
@matt : Niedawno wdrożyłem coś bardzo podobnego do tego, chociaż strona nie jest jeszcze sfinalizowana ani wdrożona. Właściwie to też trochę. W pewnym momencie planuję spakować go jako wtyczkę lokalizatora sklepów, ale nie jest tak, że mogę jeszcze opublikować jako ogólne rozwiązanie. Skontaktuj się ze mną w trybie offline, a mogę Ci pomóc, jeśli nie otrzymasz odpowiedzi, której potrzebujesz.
MikeSchinkel,

Odpowiedzi:

10

Zmodyfikowałbym odpowiedź z gabrielka i powiązanego posta na blogu , używając indeksów baz danych i minimalizując liczbę faktycznych obliczeń odległości .

Jeśli znasz współrzędne użytkownika i znasz maksymalną odległość (powiedzmy 10 km), możesz narysować obwiednię o wymiarach 20 km na 20 km z aktualną lokalizacją pośrodku. Uzyskaj te współrzędne graniczne i przeszukuj tylko magazyny między tymi szerokościami i długościami geograficznymi . Nie używaj jeszcze funkcji trygonometrii w zapytaniu do bazy danych, ponieważ uniemożliwi to użycie indeksów. (Więc możesz dostać sklep, który jest 12 km od ciebie, jeśli znajduje się w północno-wschodnim rogu obwiedni, ale wyrzucimy go w następnym kroku.)

Oblicz tylko odległość (gdy ptak lata lub według rzeczywistych wskazówek dojazdu, jak wolisz) dla kilku zwracanych sklepów. To znacznie skróci czas przetwarzania, jeśli masz dużą liczbę sklepów.

W przypadku powiązanego wyszukiwania ( „podaj dziesięć najbliższych sklepów” ) możesz przeprowadzić podobne wyszukiwanie, ale z początkowym odgadnięciem odległości (więc zaczynasz od obszaru 10 km na 10 km, a jeśli nie masz wystarczającej liczby sklepów, rozszerzasz go na 20 km na 20 km itd.). W tej początkowej odległości zgadnij, ile raz obliczysz liczbę sklepów na całej powierzchni i wykorzystaj ją. Lub zaloguj liczbę potrzebnych zapytań i dostosuj się w czasie.

Dodałem pełny przykład kodu na powiązane pytanie Mike'a , a oto rozszerzenie, które daje ci najbliższe lokalizacje X (szybkie i ledwo przetestowane):

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
źródło
8

Najpierw potrzebujesz stołu, który wygląda mniej więcej tak:

zip_code    lat     lon
10001       40.77    73.98

... wypełnione dla każdego kodu pocztowego. Możesz rozwinąć tę kwestię, dodając pola miasta i województwa, jeśli chcesz spojrzeć w ten sposób.

Następnie do każdego sklepu można otrzymać kod pocztowy, a gdy trzeba obliczyć odległość, można dołączyć tabelę lat / long do danych sklepu.

Następnie zapytaj tę tabelę, aby uzyskać szerokość i długość geograficzną sklepu i kodów pocztowych użytkownika. Gdy go zdobędziesz, możesz wypełnić tablicę i przekazać ją do funkcji „Uzyskaj odległość”:

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

Ma to na celu sprawdzenie poprawności koncepcji, a nie kodu, który faktycznie poleciłbym zaimplementować. Na przykład, jeśli masz 10 000 sklepów, przeszukanie ich wszystkich, przejrzenie i posortowanie ich na każde żądanie byłoby dość kosztowną operacją.

gabrielk
źródło
Czy wyniki mogą być buforowane? Czy łatwiej byłoby również wysłać zapytanie do jednej z dostępnych w handlu (lub darmowych, jeśli istnieje) baz danych kodów pocztowych?
mat
1
@matt - Co rozumiesz przez zapytanie jednego z dostępnych w handlu lub za darmo? Buforowanie powinno być zawsze możliwe, patrz codex.wordpress.org/Transients_API
hakre
@hakre: Nieważne, myślę, że teraz mówię nad głową. Mówię o korzystaniu z baz danych kodów pocztowych (USPS, Google Maps ...), aby uzyskać odległości, ale nie zdawałem sobie sprawy, że prawdopodobnie nie przechowują odległości, po prostu przechowują kod pocztowy i współrzędne i będzie bądź ode mnie, aby to obliczyć.
mat
3

Dokumentacja MySQL zawiera również informacje o rozszerzeniach przestrzennych. Co dziwne, standardowa funkcja distance () nie jest dostępna, ale sprawdź tę stronę: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html, aby uzyskać szczegółowe informacje na temat „konwertowania” dwie wartości POINT do REGLINP, a następnie obliczyć jego długość. ”

Pamiętaj, że każdy sprzedawca może oferować różne szerokości i długości geograficzne reprezentujące „centroid” kodu pocztowego. Warto również wiedzieć, że nie ma żadnych naprawdę zdefiniowanych plików „brzegowych” kodu pocztowego. Każdy sprzedawca będzie miał swój własny zestaw granic, które z grubsza odpowiadają konkretnym listom adresów, które składają się na kod pocztowy USPS. (Na przykład w niektórych „granicach” trzeba uwzględnić obie strony ulicy, w innych tylko jedną.) Obszary tabel ZCTA, powszechnie używane przez dostawców, „nie przedstawiają dokładnie obszarów dostarczania kodu pocztowego, i nie zawierają wszystkich kodów pocztowych używanych do dostarczania poczty ” http://www.census.gov/geo/www/cob/zt_metadata.html

Wiele firm w centrum będzie miało swój własny kod pocztowy. Będziesz potrzebować możliwie pełnego zestawu danych, więc upewnij się, że znajdziesz listę kodów pocztowych, która zawiera zarówno „punktowe” kody pocztowe (zwykle firmy), jak i „graniczne” kody pocztowe.

Mam doświadczenie w pracy z danymi kodu pocztowego z http://www.semaphorecorp.com/ . Nawet to nie było w 100% dokładne. Na przykład, kiedy mój kampus przyjął nowy adres pocztowy i nowy kod pocztowy, kod pocztowy został zgubiony. To powiedziawszy, to jedyne źródło danych, które znalazłem, że w ogóle Miałem nowy kod pocztowy, więc wkrótce po jego utworzeniu.

W mojej książce był przepis na dokładnie to, jak spełnić twoją prośbę ... w Drupal. Opierał się na module Narzędzi Map Google ( http://drupal.org/project/gmaps , nie należy mylić go z http://drupal.org/project/gmap , również godnym modułem). Możesz znaleźć pomocną próbkę kod w tych modułach, choć oczywiście nie będą działać po wyjęciu z pudełka w WordPress.

Marjorie Roswell
źródło