Quantcast
Channel: BIOSTALL » PHP
Viewing all articles
Browse latest Browse all 57

Performing a Radial Search with WP_Query in WordPress

$
0
0

If you’re building a site in WordPress that performs some kind of radial search, it can be difficult to know how to do this using the standard functionality included through use of the built-in WP_Query class.

I found myself in this exact scenario recently on a site I was developing, and was surprised to see that there was very little written about the matter. I could have quickly hacked a custom query together but decided to investigate and see if there was a viable way of achieveing this, whilst maintaining the benefits of using WP_Query.

What is a Radial Search?

Before we begin, let’s just clarify what is meant by a radial search. A radial search is where we know the latitude and longitude co-ordinates of a centre point, and wish to find all the points that fall within a set radius of this point.

To perform a radial search we need three things:

1) The co-ordinates of the centre point
2) The co-ordinates of the points that we wish to search for
3) A calculation to perform the actual radial search

Let’s start by ensuring that we have the co-ordinates and are storing them correctly within our database.

Storing the Co-ordinates

In my scenario, I had a custom post type set up in WordPress that stored information about various locations in custom fields. One of these bits of information were the co-ordinates for the location in question.

Storing Lat and Lng in WordPress Custom Fields

Storing these co-ordinates in a custom field worked fine for displaying them within WordPress, and allowing me to edit them when needed. What this did mean however was that they were stored in the wp_post_meta database table, which ultimately made it difficult to perform the query that we will be coming to later. You’ll see why a little later on…

Storing Co-ordinates Somewhere Else

In order for the radial calculation to be easier and quicker, I first needed to store these co-ordinates in a separate table. They would remain in the wp_post_meta table, but also be saved to a separate table whenever the post was edited.

The schema for my new table, lat_lng_post, looked like so:

Lat Lng Table Schema
And the query to create it for your convenience:

CREATE TABLE IF NOT EXISTS `lat_lng_post` (
  `post_id` bigint(20) unsigned NOT NULL,
  `lat` float NOT NULL,
  `lng` float NOT NULL
) ENGINE=InnoDB;

Saving Co-ordinates Somewhere Else

Now that we have our extra database table setup to store the co-ordinates in a ‘flatter’ relationship, we need to ensure that these are updated whenever the post is changed.

Fortunately, through use of the WordPress hooks, we can catch this event and update our new table accordingly. I’ve include below what my hook looked like in the site’s functions.php file:

function save_lat_lng( $post_id ) 
{
    global $wpdb;
    
    // Check that we are editing the right post type
    if ( 'location' != $_POST['post_type'] ) 
    {
        return;
    }
    
    // Check if we have a lat/lng stored for this property already
    $check_link = $wpdb->get_row("SELECT * FROM lat_lng_post WHERE post_id = '" . $post_id . "'");
    if ($check_link != null) 
    {
        // We already have a lat lng for this post. Update row
        $wpdb->update( 
	    'lat_lng_post', 
	    array( 
	        'lat' => $_POST['lat_field_name'],
	        'lng' => $_POST['lng_field_name']
	    ), 
	    array( 'post_id' => $post_id ), 
	    array( 
	        '%f',
	        '%f'
	    )
        );
    }
    else
    {
        // We do not already have a lat lng for this post. Insert row
        $wpdb->insert( 
	    'lat_lng_post', 
	    array( 
	        'post_id' => $post_id,
	        'lat' => $_POST['lat_field_name'],
	        'lng' => $_POST['lng_field_name']
	    ), 
	    array( 
	        '%d', 
	        '%f',
	        '%f'
	    ) 
        );
    }
}
add_action( 'save_post', 'save_lat_lng' );

Note: If you’re using the code above, remember to change the names of the $_POST fields to match your site setup.

Upon saving posts we will now have the co-ordinates stored in the wp_post_meta table, and now our own lat_lng_post table.

Integrating WP_Query

Now comes the fun bit where we get to expand WP_Query so that it runs on our new table and performs the radial search we’re aiming for.

The first problem with WP_Query is that you can’t write bespoke SQL for it, which we need to do because we have our own custom table. As a result, our attention should turn to a filter called ‘posts_where‘ which allows us to modify the WHERE part of the final SQL query ran.

Again, I’ve included below what my call to WP_Query looks like with the filter being called, as well as the actual posts_where filter itself.

First, the call to WP_Query:

// Declare the query arguments
$args = array(
    'post_type' => 'location'
);

// Add our filter before executing the query
add_filter( 'posts_where' , 'location_posts_where' );

// Execute the query
$location_query = new WP_Query( $args );

// Remove the filter just to be sure its
// not used again by non-related queries
remove_filter( 'posts_where' , 'location_posts_where' );

And our filter in functions.php:

function location_posts_where( $where )
{
    // Specify the co-ordinates that will form
    // the centre of our search
    $lat = '50.12335';
    $lng = '-1.344453';
    
    $radius = 10; // (in miles)
    
    // Append our radius calculation to the WHERE
    $where .= " AND $wpdb->posts.ID IN (SELECT post_id FROM lat_lng_post WHERE
         ( 3959 * acos( cos( radians(" . $lat . ") )
                        * cos( radians( lat ) )
                        * cos( radians( lng )
                        - radians(" . $lng . ") )
                        + sin( radians(" . $lat . ") )
                        * sin( radians( lat ) ) ) ) <= " . $radius . ")";
    }
    
    // Return the updated WHERE part of the query
    return $where;
}
Run a Test

That's it. The only remaining thing left to do is to run a test and see your code come to life.

As long as co-ordinates exist in your lat_lng_post database table that are within 10 miles of the central latitude and longitude you should see results come back.


Viewing all articles
Browse latest Browse all 57

Trending Articles