Performing a Radial Search with WP_Query in WordPress

January 27th, 2014 - Posted by Steve Marks to MySQL, PHP, Web Development.

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:

  `post_id` bigint(20) unsigned NOT NULL,
  `lat` float NOT NULL,
  `lng` float NOT NULL

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'] ) 
    // 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
	        'lat' => $_POST['lat_field_name'],
	        'lng' => $_POST['lng_field_name']
	    array( 'post_id' => $post_id ), 
        // We do not already have a lat lng for this post. Insert row
	        'post_id' => $post_id,
	        'lat' => $_POST['lat_field_name'],
	        'lng' => $_POST['lng_field_name']
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 )
    global $wpdb;

    // 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.

This entry was posted on Monday, January 27th, 2014 at 10:09 pm by +Steve Marks and is filed under MySQL, PHP, Web Development. You can follow any responses to this entry through the RSS 2.0 feed.

Fear not, we won't publish this

Comments (6)
  1. @Uwe B – Sure thing :) Send them through to me at and I’ll take a look in a short while. If you can send me just your theme, plugins and a DB dump that would be grand. Or if it’s easier just to send me the whole site then that’s fine too

  2. Uwe B. says:

    Hi Steve, first of all thank you for sharing this code. The first part works fine for me, but it seems that i do something wrong in part two or three.

    I added the filter to my functions.php exactly like its written down here and use it in my template file like you demonstrate it. I changed the lat and lng of the filter to a location that is in my database and used a range of 100 miles, but still got no results.

    Is it possible to send you my files per mail?

  3. @Dharmang – I have tried many times to incorporate this using meta fields only. If you find a solution to this (that’s still efficient) I’d love to hear it :)

  4. Dharmang Andhariya says:

    I think, though I am not sure, it should be possible through meta fields only, I mean no need to introduce table lat_lng_post.

  5. @Luke – Good spot. I’ve amended the code. Thanks for letting me know :)

  6. Luke says:

    Hi, great snippet.

    I think there might be an issue with your ‘location_posts_where’ function though, there are two closing ‘}’ and only one opening ‘{‘

    Trying to get this working on a project and I’m seeing no results for some reason :/