Wie kann ich eine standortbasierte Suche (Postleitzahl) in WordPress implementieren?

19

Ich arbeite an einer lokalen Unternehmensverzeichnis-Site, die benutzerdefinierte Beitragstypen für die Unternehmenseinträge verwendet. Eines der Felder ist "Postleitzahl". Wie kann ich eine standortbasierte Suche einrichten?

Ich möchte, dass Besucher ihre Postleitzahl eingeben und eine Kategorie auswählen können und alle Unternehmen mit einem bestimmten Radius oder alle Unternehmen nach Entfernung anzeigen können. Ich habe ein paar Plugins gesehen, die behaupten, dies zu tun, aber WordPress 3.0 nicht unterstützen. Irgendwelche Vorschläge?

matt
quelle
2
Ich beginne ein Kopfgeld, weil dies eine interessante und herausfordernde Frage ist. Ich habe ein paar eigene Ideen ... aber ich möchte sehen, ob sich irgendjemand etwas Eleganteres einfallen lassen kann (und es einfacher zu bauen ist).
EAMann
Danke EAMann. Dies kann helfen: briancray.com/2009/04/01/…
matt
Ich würde hier nur vorschlagen, ein Plugin wie
pods
@ NetConstructor.com - Ich würde keine Pods dafür vorschlagen. Es gibt wirklich keinen Vorteil Pods bietet dieses Problem im Vergleich zu benutzerdefinierten Beitragstypen.
MikeSchinkel
@matt : Ich habe kürzlich etwas Ähnliches implementiert, obwohl die Site noch nicht fertiggestellt oder bereitgestellt ist. Eigentlich gibt es auch einiges. Ich plane, es irgendwann als Store Locator-Plugin zu packen, aber es ist noch nicht so, dass ich es als allgemeine Lösung posten kann. Kontaktieren Sie mich offline und ich kann Ihnen möglicherweise helfen, wenn Sie nicht die Antwort erhalten, die Sie benötigen.
MikeSchinkel

Antworten:

10

Ich würde ändert die Antwort von gabrielk und den verknüpfte Blogeintrag unter Verwendung von Datenbankindizes und die Anzahl der tatsächlichen Entfernungsberechnungen zu minimieren .

Wenn Sie die Koordinaten des Benutzers und die maximale Entfernung (z. B. 10 km) kennen, können Sie einen Begrenzungsrahmen von 20 x 20 km mit der aktuellen Position in der Mitte zeichnen. Holen Sie sich diese Grenzkoordinaten und fragen Sie nur Speicher zwischen diesen Breiten- und Längengraden ab . Verwenden Sie noch keine Trigonometriefunktionen in Ihrer Datenbankabfrage, da dies die Verwendung von Indizes verhindert. (Sie könnten also ein Geschäft in 12 km Entfernung von Ihnen bekommen, wenn es sich in der nordöstlichen Ecke des Begrenzungsrahmens befindet, aber wir werfen es im nächsten Schritt weg.)

Berechnen Sie nur die Entfernung (wie der Vogel fliegt oder mit tatsächlichen Wegbeschreibungen, wie Sie es bevorzugen) für die wenigen Geschäfte, die zurückgegeben werden. Dies wird die Verarbeitungszeit drastisch verkürzen, wenn Sie eine große Anzahl von Geschäften haben.

Für die zugehörige Suche ( "Geben Sie die zehn nächstgelegenen Geschäfte") ) können Sie eine ähnliche Suche durchführen, jedoch mit einer anfänglichen Entfernungsschätzung (Sie beginnen also mit einem Bereich von 10 km x 10 km, und wenn Sie nicht genügend Geschäfte haben, erweitern Sie ihn auf 20 km mal 20 km und so weiter). Für diese anfängliche Entfernung erraten Sie einmal die Anzahl der Geschäfte über die gesamte Fläche und verwenden diese. Oder protokollieren Sie die Anzahl der erforderlichen Abfragen und passen Sie sie im Laufe der Zeit an.

Ich habe bei Mikes verwandter Frage ein vollständiges Codebeispiel hinzugefügt. Hier ist eine Erweiterung, die die nächstgelegenen X-Standorte angibt (schnell und kaum getestet):

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

Zuerst brauchen Sie eine Tabelle, die ungefähr so ​​aussieht:

zip_code    lat     lon
10001       40.77    73.98

... für jede Postleitzahl ausgefüllt. Sie können dies erweitern, indem Sie Felder für Stadt und Bundesland hinzufügen, wenn Sie auf diese Weise nachschlagen möchten.

Dann kann jedem Geschäft eine Postleitzahl zugewiesen werden, und wenn Sie die Entfernung berechnen müssen, können Sie die Lat / Long-Tabelle mit den Geschäftsdaten verknüpfen.

Anschließend fragen Sie diese Tabelle ab, um den Breiten- und Längengrad für das Geschäft und die Postleitzahlen des Benutzers abzurufen. Sobald Sie es erhalten haben, können Sie Ihr Array füllen und es an eine Funktion zum Abrufen der Entfernung übergeben:

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

Dies ist als Proof-of-Concept gedacht, nicht als Code, dessen Implementierung ich empfehlen würde. Wenn Sie beispielsweise 10.000 Filialen haben, ist es ziemlich teuer, alle abzufragen und sie bei jeder Anfrage zu durchlaufen und zu sortieren.

gabrielk
quelle
Könnten die Ergebnisse zwischengespeichert werden? Wäre es auch einfacher, eine der kommerziell verfügbaren (oder kostenlosen) Postleitzahlen-Datenbanken abzufragen?
Matt
1
@matt - Was meinst du mit Abfrage einer der im Handel oder kostenlos erhältlichen? Caching sollte immer möglich sein, siehe codex.wordpress.org/Transients_API
am
@hakre: Nevermind, ich denke ich rede jetzt über meinen Kopf. Ich spreche davon, Postleitzahlen-Datenbanken (USPS, Google Maps ...) zu verwenden, um die Entfernungen abzurufen, aber ich habe nicht bemerkt, dass sie die Entfernungen wahrscheinlich nicht speichern, sondern nur die Postleitzahl und die Koordinaten und das wird es Überlasse es mir, es zu berechnen.
Matt
3

Die MySQL-Dokumentation enthält auch Informationen zu räumlichen Erweiterungen. Seltsamerweise ist die Standard-distance () -Funktion nicht verfügbar. Weitere Informationen zum "Konvertieren" finden Sie auf dieser Seite: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html die beiden POINT-Werte zu einem LINESTRING und berechne dann die Länge davon. "

Beachten Sie, dass jeder Anbieter wahrscheinlich unterschiedliche Breiten- und Längengrade anbietet, die den "Schwerpunkt" einer Postleitzahl darstellen. Es ist auch wichtig zu wissen, dass es keine wirklich definierten "Grenz" -Dateien für die Postleitzahl gibt. Jeder Anbieter hat seine eigenen Grenzen, die in etwa mit den spezifischen Adressenlisten einer USPS-Postleitzahl übereinstimmen. (In einigen "Grenzen" müssten Sie beispielsweise beide Straßenseiten einschließen, in anderen nur eine.) ZCTAs (ZIP Code Tabulation Areas), die von Anbietern häufig verwendet werden, beschreiben "Postleitzahl-Zustellbereiche nicht genau. und nicht alle für die Postzustellung verwendeten Postleitzahlen einschließen " http://www.census.gov/geo/www/cob/zt_metadata.html

Viele Unternehmen in der Innenstadt haben eine eigene Postleitzahl. Sie möchten einen möglichst vollständigen Datensatz erstellen. Stellen Sie daher sicher, dass Sie eine Liste mit Postleitzahlen finden, die sowohl die Postleitzahl "point" (in der Regel Unternehmen) als auch die Postleitzahl "border" enthält.

Ich habe Erfahrung mit den PLZ-Daten von http://www.semaphorecorp.com/ . Auch das war nicht 100% genau. Als mein Campus beispielsweise eine neue Postanschrift und eine neue Postleitzahl annahm, war die Postleitzahl verlegt. Das heißt, es war die einzige Datenquelle, die ich fand, die sogar die neue Postleitzahl hatte, also kurz nachdem sie erstellt wurde.

Ich hatte ein Rezept in meinem Buch, wie ich genau auf Ihre Anfrage eingehen kann ... in Drupal. Es stützte sich auf das Google Maps Tools-Modul ( http://drupal.org/project/gmaps , nicht zu verwechseln mit http://drupal.org/project/gmap , ebenfalls ein würdiges Modul). Möglicherweise finden Sie einige hilfreiche Beispiele Code in diesen Modulen funktioniert in WordPress natürlich nicht sofort.

Marjorie Roswell
quelle