按坐标之间的距离过滤wordpress帖子

时间:2015-04-20 15:25:29

标签: php wordpress filtering

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

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

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

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

现在我要做的就是修改它,这样当实际传递范围和位置时,帖子将被过滤,只返回位置在该位置范围内(以公里为单位)的帖子用户搜索。我似乎无法为此找到一个好的解决方案,因为我很难使用wordpress及其插件。我真的很感激我能理解的解决方案。

1 个答案:

答案 0 :(得分:7)

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

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

我将使用以下公式:

  • 计算两个lat / lng坐标之间的距离:

    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. 创建一个边界square-ish框,将帖子过滤到一个 很少是正确的。这不应该太计算 昂贵,但是可能会留下一些边缘的近似值 out,并包括一些不合适的帖子。
  2. 优化退货 只发布适合账单的那些帖子。这是计算上的 昂贵的过程,因此是第一阶段。少数帖子被排除在外 第一步仍将被排除在外。边界框可以 可能会变得更大以适应。
  3. 您要使用的查询应包含元信息: 有关这些元查询的有用指南,请参阅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代码未经测试,如果错误仍然存​​在,请道歉。一般的概念是正确的。