Reputation: 14440
I have a a bunch of posts in WordPress. These posts are in a category called "Restaurants". Each post has a custom meta key/value stored with it like so:
$post_geocode = get_post_meta($post->ID, 'geocode', true);
// $post_geocode = '34.0510613,-118.244705'
You have to be logged into WordPress to use my site, so each user has a Geocode stored for them, like so:
$user_geocode = get_user_meta( $user->ID, 'geocode', true );
// $user_geocode = '34.043925,-118.2424291'
So I want to be able to get posts and rank them in order to their proximity to the users location.
Ideally it would use the WP Query class. But I am happy to change the way the geocodes are stored if needed.
The best answer would look something like this:
// This is how I manage the type of restaurant you'll see (eg: Mexican, Italian etc)
$cat_query = array(
'taxonomy' => 'category',
'field' => 'slug',
'terms' => $restaurant_category,
'operator' => 'IN'
);
// Go and get the posts
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => -20,
'geocode' => $user_geocode, // WOuld love this to work!!!
'orderby' => 'geocode', // Would love this to work!!!
'fields' => 'ids',
'tax_query' => array(
'relation' => 'AND',
$cat_query,
)
);
$posts = get_posts($args);
Upvotes: 2
Views: 3332
Reputation: 1986
You're definitely going to need to store your values differently. When I've done this I've used a table something along the lines of
CREATE TABLE `geo_db` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`post_id` int(11) DEFAULT NULL,
`lat` float DEFAULT NULL,
`lng` float DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `post_id` (`post_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
You could also use a single column of GEOMETRY type, but this is fine for comparing geo points. Then in your plugin, you'll modify the sql used in WP_Query
function geo_posts_fields( $sql, WP_Query $wp_query ){
global $wpdb;
// get your user coords here. explode $user_geocode or store in custom db as well.
$lat = 45;
$lng = -122;
if( $wp_query->query_vars['orderby'] == 'geocode' )
$sql .= $wpdb->prepare( ", ROUND( ( 3959 * acos( cos( radians(geo_db.lat) ) * cos( radians( %f ) )
* cos( radians(%f) - radians(geo_db.lng)) + sin(radians(geo_db.lat))
* sin( radians(%f)))), 1 ) AS `distance` ", $lat, $lng, $lat );
return $sql;
}
function geo_posts_join( $sql, WP_Query $wp_query ){
global $wpdb;
if( $wp_query->query_vars['orderby'] == 'geocode' )
$sql .= " LEFT JOIN geo_db ON $wpdb->posts.ID = geo_db.post_id ";
return $sql;
}
function geo_posts_orderby( $sql, WP_Query $wp_query ){
if( $wp_query->query_vars['orderby'] == 'geocode' )
$sql = " `distance` ASC, ".$sql;
return $sql;
}
function geo_posts_request( $sql, WP_Query $wp_query ){
// just to see whats going on
var_dump( $sql );
return $sql;
}
add_filter( 'posts_fields', 'geo_posts_fields', 10, 2 );
add_filter( 'posts_join', 'geo_posts_join', 10, 2 );
add_filter( 'posts_orderby', 'geo_posts_orderby', 10, 2 );
add_filter( 'posts_request', 'geo_posts_request', 10, 2 );
Finally, you'll need to register a query var in order to use orderby as an argument with get_posts, I'd recommend making a new WP_Query
$geo_posts = new WP_Query( array(
'post_type' => 'any',
'orderby' => 'geocode'
) );
while( $geo_posts->have_posts() ): $geo_post->the_post(); // etc
Upvotes: 1
Reputation: 7228
If you save your post data in the database (split your coordinates into two fields latitude and logitude) you can use geodetic form of the law of cosines to find distance between points .Reference
cos(d/R)=cos(lat1)cos(lat2)cos(long2-long1)+sin(lat1)sin(lat2)
Where R = radius of earth (6367 kms,3959 miles)
The following SQL query will provide distance in kms,where $center_lat&$center_lng is the users location.
$postids=$wpdb->get_col( $wpdb->prepare("SELECT name, lat, lng,
( 6367 * acos( cos( radians(%f) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( lat ) ) ) ) AS distance FROM mytable HAVING distance < %d ORDER BY distance LIMIT 0 , 5",
$center_lat,$center_lng,$center_lat,$radius));
if ( $postids )
{
foreach ( $postids as $id )
{
echo $id->name;
echo $id->distance;
//etc
}
}
The query is based on code I have used here but modified with information found here. I don't have access to WordPress to check.
Upvotes: 2