【问题标题】:Filter wordpress posts by distance between coordinates按坐标之间的距离过滤wordpress帖子
【发布时间】:2015-06-27 10:09:36
【问题描述】:

我要做的是通过 2 个坐标之间的距离过滤一堆 wordpress 帖子。用户输入的坐标、范围和类别在 URL 中传递,如下所示:

/?cat=0&s=5041GW&range=250&lat=51.5654368&lon=5.071263999999928

然后有一些帖子(不是全部)具有我使用插件高级自定义字段创建的经纬度字段。这些是我传递给 get_posts 以获取按类别过滤的帖子的参数:

   $args = array(
        'posts_per_page'   => 24,
        'category'         => $_GET["cat"],
        'orderby'          => 'post_date',
        'order'            => 'DESC',
        'post_type'        => 'adressen',
        'post_status'      => 'publish',
    );

现在我要做的是修改它,以便在实际传递范围和位置时,将过滤帖子以仅返回位置在用户搜索的位置范围(以公里为单位)内的帖子为了。我似乎无法为此找到一个好的解决方案,因为我很难使用 wordpress 及其拥有的插件。我非常感谢我能理解的解决方案。

【问题讨论】:

  • WordPress 可能不是此应用程序的合适框架。您可能应该考虑使用更通用的应用程序框架。
  • 告诉我吧,伙计。我讨厌为这种事情使用 wordpress :( 遗憾的是我的双手被束缚了。

标签: php wordpress filtering


【解决方案1】:

这在计算上可能相当昂贵。最直接的方法是获取所有符合条件的帖子,然后遍历所有帖子,丢弃指定范围之外的帖子。

出现困难是因为米和纬度/经度之间没有线性映射。这取决于你在地球上的哪个位置。有关详细信息,请参阅this questionPHPcoord library 的存在是为了为您进行此计算,但由于我提出的答案略微近似,我将使用 this website 中描述的近似方法,使用 Haversine formula

我将使用以下公式:

  • 计算两个纬度/经度坐标之间的公里距离:

    x = Δλ ⋅ cos φm
    y = Δφ
    d = R ⋅ √(x² + y²)
    

    其中 φ 是以弧度为单位的纬度,λ 是以弧度为单位的经度,R 是地球的半径(平均半径 = 6,371km)

  • 在给定起始纬度、距离和方位的情况下计算目的地:

    φ2 = asin( sin φ1 ⋅ cos δ + cos φ1 ⋅ sin δ ⋅ cos θ )
    λ2 = λ1 + atan2( sin θ ⋅ sin δ ⋅ cos φ1, cos δ − sin φ1 ⋅ sin φ2 )
    

    其中 θ 是方位角(从北顺时针方向),δ 是角距离 d/R,d 是行进距离。见atan2


因此,我们将定义以下辅助函数:

const R = 6371; // km

function distance_between_points_rad($lat1, $lng1, $lat2, $lng2){
    // latlng in radians
    $x = ($lng2-$lng1) * cos(($lat1+$lat2)/2);
    $y = ($lat2-$lat1);
    // return distance in km
    return sqrt($x*$x + $y*$y) * R;
}

function get_destination_lat_rad($lat1, $lng1, $d, $brng){
    return asin( sin($lat1)*cos($d/R) +
                    cos($lat1)*sin($d/R)*cos($brng) );
}

function get_destination_lng_rad($lat1, $lng1, $d, $brng){
    $lat2 = get_destination_lat_rad($lat1, $lng1, $d, $brng);
    return $lng1 + atan2(sin($brng)*sin($d/R)*cos($lat1),
                         cos($d/R)-sin($lat1)*sin($lat2));
}

function get_bounding_box_rad($lat, $lng, $range){
    // latlng in radians, $range in km
    $latmin = get_destination_lat_rad($lat, $lng, $range, 0);
    $latmax = get_destination_lat_rad($lat, $lng, $range, deg2rad(180));
    $lngmax = get_destination_lng_rad($lat, $lng, $range, deg2rad(90));
    $lngmin = get_destination_lng_rad($lat, $lng, $range, deg2rad(270));
    // return approx bounding latlng in radians
    return array($latmin, $latmax, $lngmin, $lngmax);
}

function distance_between_points_deg($lat1, $lng1, $lat2, $lng2){
    // latlng in degrees
    // return distance in km
    return distance_between_points_rad(
        deg2rad($lat1), deg2rad($lng1), deg2rad($lat2), deg2rad($lng2) );
}

function get_bounding_box_deg($lat, $lng, $range){
    // latlng in degrees, $range in km
    return array_map(rad2deg,
        get_bounding_box_rad(deg2rad($lat), deg2rad($lng), $range));
}

(Runnable in ideone)

现在,一般流程应该是:

  1. 创建一个方形边界框,将帖子过滤成一个 很少有人说是对的。这不应该太计算 昂贵,但这是一个近似值,可能会留下一些边缘职位 出,并包括一些不适合的帖子。
  2. 细化返回的 只发给符合要求的人。这是一个计算 昂贵的过程,因此是第一阶段。被排除在外的少数帖子 第一步仍将被排除。边界框可以 可能会变大以适应。

您要使用的查询应包含元信息: 请参阅 here 以获取其中一些元查询的有用指南

$lat1 = $_GET['lat']; // degrees
$lng1 = $_GET['lng']; // degrees
$range = $_GET['range']; // km

// get the approximate bounding box
$bbox = get_bounding_box_deg($lat1, $lng1, $range);

// query the posts
$args = array(
    'posts_per_page'   => 24,
    'category'         => $_GET["cat"],
    'orderby'          => 'post_date',
    'order'            => 'DESC',
    'post_type'        => 'adressen',
    'post_status'      => 'publish',
    'meta_query' => array(
        'relation' => 'AND',
        array(
            'key' => 'lat',
            'value' => array( $bbox[0], $bbox[1] ),
            'type' => 'numeric',
            'compare' => 'BETWEEN'
        ),
        array(
            'key' => 'lng',
            'value' => array( $bbox[2], $bbox[3] ),
            'type' => 'numeric',
            'compare' => 'BETWEEN'
        )
    )
);
$the_query = new WP_Query( $args );

然后帖子在循环中被过滤掉:

// Then filter the posts down in the loop
if ( $the_query->have_posts() ) {
    while ( $the_query->have_posts() ) {
        $the_query->the_post();
        $custom_fields = get_post_custom();
        if (isset($custom_fields['lat']) && isset($custom_fields['lng'])){
             $lat2 = $custom_fields['lat'];
             $lng2 = $custom_fields['lng'];
             $dist = distance_between_points_deg($lat1, $lng1, $lat2, $lng2);
             if ($dist <= $range){
                 // post is in range
             } else {
                 // post out of range, discard
             }
        } else {
            // post has no latlng coords
        }
    }
} else {
    // no posts found
}
/* Restore original Post Data */
wp_reset_postdata();

WordPress 代码未经测试,如有错误,敬请见谅。不过,一般概念是正确的。

【讨论】:

  • 感谢您的回答!我很感激。不幸的是,我已经想到了与此类似的解决方案。就像您还指出的那样,这不是最佳的解决方案性能。我正在使用的项目需要过滤掉 5000 个帖子。我实际上希望有更有效的解决方案。当然,我很欣赏这个答案,如果没有其他选择,我会接受它;)
  • 感谢您的编辑!但我真的不明白这里的距离是如何计算的......请记住 $_GET["range"] 是以公里为单位的距离。
  • @FrankKluyt 我知道您现在可能正在探索其他可能性,但我认为这是一个有趣的项目 - 请参阅我最新的重写;)
  • 虽然它可能不是我正在寻找的确切答案,但我喜欢你的边界框想法。我接受了这个答案。谢谢你的努力! :)
  • 我很难理解如何应用它。即使接近我的坐标,边界框也不会返回值,如果我插入 53.215 和 6.53 作为 lat lng 坐标,我会得到像 0.9 和 0.11 这样的数字。在循环中应用过滤器会导致错误的分页。有解决办法吗?
猜你喜欢
  • 2019-02-15
  • 1970-01-01
  • 2021-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-01-19
  • 2010-11-23
相关资源
最近更新 更多