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

时间:2010-09-27 作者:matt

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

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

3 个回复
SO网友:Jan Fabry

我会修改the answer from gabrielkthe linked blog post 通过使用database indexesminimizing the number of actual distance calculations.

如果知道用户的坐标和最大距离(比如10km),可以绘制一个20km×20km的边界框,当前位置在中间。获取这些边界坐标并query only stores between these latitudes and longitudes. 不要在数据库查询中使用三角函数,因为这将阻止使用索引。(因此,如果商店位于边界框的东北角,您可能会在离您12公里的地方找到一家商店,但我们会在下一步将其扔掉。)

只计算退回的几家店铺的距离(当鸟儿飞起来时,或者根据您的喜好计算实际的驾驶方向)。如果您有大量商店,这将大大缩短处理时间。

对于相关的查找(“给出十个最近的商店”),您可以进行类似的查找,但要有一个初始距离猜测(因此您从10km×10km的区域开始,如果没有足够的商店,则将其扩展到20km×20km,以此类推)。对于这个初始距离,您可以计算一次总面积上的门店数量,然后使用它。或者记录所需查询的数量,并随着时间的推移进行调整。

我添加了一个full code example at Mike\'s related question, 这里有一个扩展,可以提供最接近的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();

SO网友:gabrielk

首先,您需要一个如下所示的表:

zip_code    lat     lon
10001       40.77    73.98
。。。为每个邮政编码填充。如果希望以这种方式查找,可以通过添加城市和州字段来扩展此功能。

然后,可以给每个商店一个邮政编码,当需要计算距离时,可以将lat/long表与商店数据连接起来。

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

$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);
}
这是一种概念证明,而不是我实际上建议实现的代码。例如,如果您有10000家店铺,那么查询所有店铺并在每个请求中循环和排序它们将是一项非常昂贵的操作。

SO网友:Marjorie Roswell

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

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

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

我有使用zipcode数据的经验http://www.semaphorecorp.com/. 即使这样也不是百分之百准确。例如,当我的校园有了新的邮寄地址和邮政编码时,邮政编码就错了。这就是说,它是我发现的唯一一个拥有新邮政编码的数据源,在它创建后不久。

我在书中有一个食谱,就是如何满足你的要求。。。在Drupal中。它依赖于谷歌地图工具模块(Google Maps Tools module(http://drupal.org/project/gmaps, 不要混淆http://drupal.org/project/gmap, 也是一个值得学习的模块。)您可能会在这些模块中找到一些有用的示例代码,当然,它们在WordPress中是无法直接使用的。

结束

相关推荐