web-dev-qa-db-fra.com

Comment puis-je implémenter une recherche basée sur l'emplacement (code postal) dans WordPress?

Je travaille sur un site de répertoire professionnel local qui utilisera des types de publication personnalisés pour les entrées professionnelles. L'un des champs sera "Code postal". Comment puis-je configurer une recherche par emplacement?

J'aimerais que les visiteurs puissent entrer leur code postal, choisir une catégorie et afficher toutes les entreprises d'un certain rayon ou toutes les entreprises classées par distance. J'ai vu quelques plugins qui prétendent le faire, mais ils ne supportent pas WordPress 3.0. Aucune suggestion?

19
matt

Je modifierais la réponse de gabrielk et le billet de blog lié en utilisant index de base de données et réduisant le nombre de calculs de distance réels .

Si vous connaissez les coordonnées de l'utilisateur et la distance maximale (par exemple, 10 km), vous pouvez dessiner un cadre de sélection de 20 km sur 20 km avec la position actuelle au milieu. Obtenir ces coordonnées de délimitation et query stocke uniquement entre ces latitudes et longitudes . N'utilisez pas encore de fonctions trigonométriques dans votre requête de base de données, car cela empêcherait l'utilisation des index. (Vous pouvez donc obtenir un magasin situé à 12 km de distance s’il se trouve dans le coin nord-est de la boîte de sélection, mais nous le jetterons à l’étape suivante.)

Calculez uniquement la distance (à vol d'oiseau ou avec les directions réelles, selon vos préférences) pour les quelques magasins restitués. Cela améliorera considérablement le temps de traitement si vous avez un grand nombre de magasins.

Pour la recherche associée ("donner les dix magasins les plus proches"), vous pouvez effectuer une recherche similaire, mais avec une estimation de la distance initiale (vous commencez donc par une zone de 10 km sur 10 km). suffisamment de magasins pour l’étendre à 20 km sur 20 km, etc.). Pour cette distance initiale, calculez une fois le nombre de magasins sur la surface totale et utilisez-le. Ou enregistrez le nombre de requêtes nécessaires et adaptez-vous avec le temps.

J'ai ajouté un exemple de code complet à la question connexe de Mike , et voici une extension qui vous donne les emplacements X les plus proches (rapide et à peine testé):

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

D'abord, vous avez besoin d'une table qui ressemble à ceci:

Zip_code    lat     lon
10001       40.77    73.98

... rempli pour chaque code postal. Vous pouvez développer ceci en ajoutant des champs de ville et d'état si vous voulez regarder de cette façon.

Ensuite, vous pouvez attribuer un code postal à chaque magasin. Lorsque vous devez calculer une distance, vous pouvez joindre la table lat/long aux données du magasin.

Vous interrogerez ensuite cette table pour obtenir la latitude et la longitude du magasin et des codes postaux de l'utilisateur. Une fois que vous l'obtenez, vous pouvez remplir votre tableau et le transmettre à une fonction "obtenir une distance":

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

Ceci est conçu comme une preuve de concept, pas un code que je recommanderais réellement de mettre en œuvre. Si vous avez 10 000 magasins, par exemple, il serait assez coûteux de les interroger tous, de les parcourir en boucle et de les trier à chaque demande.

8
gabrielk

La documentation MySQL comprend également des informations sur les extensions spatiales. Bizarrement, la fonction standard distance () n’est pas disponible, mais consultez cette page: http://dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html pour plus de détails sur comment "convertir les deux valeurs POINT en LINESTRING, puis en calculer la longueur".

Notez que chaque fournisseur est susceptible d’offrir différentes latitudes et longitudes représentant le "centroïde" d’un code postal. Cela vaut également la peine de savoir qu’il n’existe pas de fichiers "limites" définis par le code postal. Chaque fournisseur aura son propre ensemble de limites qui correspond approximativement aux listes d'adresses spécifiques qui constituent un code postal USPS. (Par exemple, dans certaines "limites", vous devez inclure les deux côtés de la rue, dans d'autres, un seul.) Les zones de tabulation de code postal (ZCTA), largement utilisées par les fournisseurs, "ne décrivent pas avec précision les zones de livraison de code postal, et n'incluent pas tous les codes postaux utilisés pour la distribution du courrier " http://www.census.gov/geo/www/cob/zt_metadata.html

De nombreuses entreprises du centre-ville auront leur propre code postal. Vous souhaiterez un ensemble de données aussi complet que possible. Assurez-vous donc de trouver une liste de codes postaux comprenant des codes postaux "ponctuels" (généralement des entreprises) et des codes postaux "limites".

J'ai déjà travaillé avec les données de code postal de http://www.semaphorecorp.com/ . Même cela n'était pas précis à 100%. Par exemple, lorsque mon campus a adopté une nouvelle adresse postale et un nouveau code postal, celui-ci a été égaré. Cela dit, c’était la seule source de données à laquelle j’ai trouvé que même le nouveau code postal était HAD, si peu de temps après sa création.

J'avais une recette dans mon livre pour savoir exactement comment répondre à votre demande ... à Drupal. Il s’appuyait sur le module Google Maps Tools ( http://drupal.org/project/gmaps , à ne pas confondre avec http://drupal.org/project/gmap , également un module utile .) Vous trouverez peut-être des exemples de code utiles dans ces modules, bien que, naturellement, ils ne fonctionneront pas tels quels dans WordPress.

3
Marjorie Roswell