如何在WordPress中实现基于位置的(邮政编码)搜索?


19

我正在一个本地企业目录站点上工作,该站点将对业务条目使用自定义帖子类型。字段之一将是“邮政编码”。如何设置基于位置的搜索?

我希望访问者能够输入他们的邮政编码并选择一个类别,并显示具有一定半径的所有企业,或者显示所有按距离排序的企业。我看到几个声称可以做到这一点的插件,但它们不支持WordPress 3.0。有什么建议么?


2
我开始赏金,因为这是一个有趣且具有挑战性的问题。我对自己有一些想法……但是我想看看是否有人可以提出更优雅(更易于构建)的东西。
EAMann

谢谢EAMann。这可能会有所帮助:briancray.com/2009/04/01/…–
马特

我目前在这里仅有的建议是利用诸如
Pods之

@ NetConstructor.com-我不会为此推荐Pods;与“自定义帖子类型”相比,Pods确实提供了此问题,这确实没有好处。
MikeSchinkel 2010年

@matt:尽管该站点尚未最终确定或部署,但我最近已经实现了与此类似的功能。实际上也有很多。我计划在某个时候将其打包为商店定位器插件,但是我还不能将其发布为一般解决方案。离线与我联系,如果您找不到所需的答案,我可能会为您提供帮助。
MikeSchinkel

Answers:


10

我将通过使用数据库索引最小化实际距离计算的数量来修改gabrielk链接的博客文章的答案

如果您知道用户的坐标,并且知道最大距离(例如10公里),则可以绘制一个边界框,该框为20公里乘20公里,当前位置在中间。获取这些边界坐标,仅查询这些经度和纬度之间的存储。请勿在数据库查询中使用三角函数,因为这将阻止使用索引。(因此,如果商店位于边界框的东北角,则您可能离您12公里,但是我们下一步将其丢弃。)

仅计算要退回的几家商店的距离(根据鸟类的飞行情况或实际驾驶方向,如果您愿意)。如果您拥有大量商店,这将大大缩短处理时间。

对于相关的查找(“给最近的十家商店”),您可以执行类似的查找,但是要进行初始距离猜测(因此,您以10公里乘10公里的区域开始,如果没有足够的商店,则将其扩展为20公里乘20公里,依此类推)。对于这个初始距离,您可以一次计算出整个区域内的商店数量并使用它。或记录所需的查询数量并随时间调整。

在Mike的相关问题处添加了完整的代码示例,这是一个扩展,可为您提供最接近的X位置(快速且未经测试):

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();

8

首先,您需要一个看起来像这样的表:

zip_code    lat     lon
10001       40.77    73.98

...针对每个邮政编码填充。如果要查找,可以通过添加城市和州字段来对此进行扩展。

然后可以为每个商店提供一个邮政编码,当您需要计算距离时,可以将经/纬度表加入商店数据。

然后,您将查询该表以获取商店的纬度和经度以及用户的邮政编码。一旦获得它,就可以填充数组并将其传递给“获取距离”功能:

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

这是作为概念证明,而不是我实际上建议实现的代码。例如,如果您有10,000个商店,则查询所有商店并根据每个请求对它们进行循环和排序将是一项非常昂贵的操作。


结果可以缓存吗?另外,查询一个商业上可用的(如果有的话)邮政编码数据库是否更容易?
马特2010年

1
@matt-查询商业或免费提供的内容是什么意思?缓存应该始终是可能的,请参见codex.wordpress.org/Transients_API
2010年

@hakre:没关系,我想我现在在说我的话。我说的是使用邮政编码数据库(USPS,Google Maps ...)来获取距离,但是我没有意识到他们可能不存储距离,他们只是存储邮政编码和坐标,并且它将由我来计算。
马特2010年

3

MySQL文档还包含有关空间扩展的信息。奇怪的是,标准distance()函数不可用,但请查看以下页面:http : //dev.mysql.com/tech-resources/articles/4.1/gis-with-mysql.html, 以获取有关如何“转换”的详细信息将两个POINT值转换为LINESTRING,然后计算其长度。”

请注意,每个供应商都可能会提供不同的纬度和经度来表示邮政编码的“质心”。还应该知道,没有真正定义的邮政编码“边界”文件。每个供应商都有自己的一组边界,这些边界大致匹配组成USPS邮政编码的特定地址列表。(例如,在某些“边界”中,您需要包括街道的两侧,而在其他边界中,只需包括其中的一面。)供商广泛使用的邮政编码列表区域(ZCTA),“不能准确地描述邮政编码交付区域,并且不包括用于邮件传递的所有邮政编码” http://www.census.gov/geo/www/cob/zt_metadata.html

许多市区商业都有自己的邮政编码。您将需要尽可能完整的数据集,因此请确保找到一个包含“点”邮政编码(通常是企业)和“边界”邮政编码的邮政编码列表。

我有使用http://www.semaphorecorp.com/中的邮政编码数据的经验。即使那也不是100%准确。例如,当我的校园使用新的邮寄地址和新的邮政编码时,该邮编放错了位置。就是说,这是我发现的唯一数据源,即使在创建后不久,它也根本没有提供新的邮政编码。

我的书中有一个食谱,确切地说明了如何满足您的需求……在Drupal中。它依赖于Google Maps Tools模块(http://drupal.org/project/gmaps,不要与http://drupal.org/project/gmap混淆,也是一个有价值的模块。)您可能会找到一些有用的示例这些模块中的代码,尽管如此,它们当然不会在WordPress中开箱即用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.