Skip to content

Instantly share code, notes, and snippets.

@akshuvo
Created April 11, 2020 16:31
Show Gist options
  • Save akshuvo/4c37df4bd128eb801b7739748ee3cd65 to your computer and use it in GitHub Desktop.
Save akshuvo/4c37df4bd128eb801b7739748ee3cd65 to your computer and use it in GitHub Desktop.
Sort by distance WordPress post meta/ WordPress Query / pre_get_posts
<?php if(!defined('ABSPATH')) { die(); } // Include in all php files, to prevent direct execution
/**
* Plugin Name: WP Geo Query
* Plugin URI: https://gschoppe.com/wordpress/geo-searches/
* Description: Adds location search support to WP_Query, making it easy to create completely custom "Find Location" pages.
* Author: Greg Schoppe
* Author URI: https://gschoppe.com
* Version: 1.0.0
**/
if( !class_exists('GJSGeoQuery') ) {
class GJSGeoQuery {
public static function Instance() {
static $instance = null;
if ($instance === null) {
$instance = new self();
}
return $instance;
}
private function __construct() {
add_filter( 'posts_fields' , array( $this, 'posts_fields' ), 10, 2 );
add_filter( 'posts_join' , array( $this, 'posts_join' ), 10, 2 );
add_filter( 'posts_where' , array( $this, 'posts_where' ), 10, 2 );
add_filter( 'posts_orderby', array( $this, 'posts_orderby' ), 10, 2 );
}
// add a calculated "distance" parameter to the sql query, using a haversine formula
public function posts_fields( $sql, $query ) {
global $wpdb;
$geo_query = $query->get('geo_query');
if( $geo_query ) {
if( $sql ) {
$sql .= ', ';
}
$sql .= $this->haversine_term( $geo_query ) . " AS geo_query_distance";
}
return $sql;
}
public function posts_join( $sql, $query ) {
global $wpdb;
$geo_query = $query->get('geo_query');
if( $geo_query ) {
if( $sql ) {
$sql .= ' ';
}
$sql .= "INNER JOIN " . $wpdb->prefix . "postmeta AS geo_query_lat ON ( " . $wpdb->prefix . "posts.ID = geo_query_lat.post_id ) ";
$sql .= "INNER JOIN " . $wpdb->prefix . "postmeta AS geo_query_lng ON ( " . $wpdb->prefix . "posts.ID = geo_query_lng.post_id ) ";
}
return $sql;
}
// match on the right metafields, and filter by distance
public function posts_where( $sql, $query ) {
global $wpdb;
$geo_query = $query->get('geo_query');
if( $geo_query ) {
$lat_field = 'latitude';
if( !empty( $geo_query['lat_field'] ) ) {
$lat_field = $geo_query['lat_field'];
}
$lng_field = 'longitude';
if( !empty( $geo_query['lng_field'] ) ) {
$lng_field = $geo_query['lng_field'];
}
$distance = 20;
if( isset( $geo_query['distance'] ) ) {
$distance = $geo_query['distance'];
}
if( $sql ) {
$sql .= " AND ";
}
$haversine = $this->haversine_term( $geo_query );
$new_sql = "( geo_query_lat.meta_key = %s AND geo_query_lng.meta_key = %s AND " . $haversine . " <= %f )";
$sql .= $wpdb->prepare( $new_sql, $lat_field, $lng_field, $distance );
}
return $sql;
}
// handle ordering
public function posts_orderby( $sql, $query ) {
$geo_query = $query->get('geo_query');
if( $geo_query ) {
$orderby = $query->get('orderby');
$order = $query->get('order');
if( $orderby == 'distance' ) {
if( !$order ) {
$order = 'ASC';
}
$sql = 'geo_query_distance ' . $order;
}
}
return $sql;
}
public static function the_distance( $post_obj = null, $round = false ) {
echo self::get_the_distance( $post_obj, $round );
}
public static function get_the_distance( $post_obj = null, $round = false ) {
global $post;
if( !$post_obj ) {
$post_obj = $post;
}
if( property_exists( $post_obj, 'geo_query_distance' ) ) {
$distance = $post_obj->geo_query_distance;
if( $round !== false ) {
$distance = round( $distance, $round );
}
return $distance;
}
return false;
}
private function haversine_term( $geo_query ) {
global $wpdb;
$units = "miles";
if( !empty( $geo_query['units'] ) ) {
$units = strtolower( $geo_query['units'] );
}
$radius = 3959;
if( in_array( $units, array( 'km', 'kilometers' ) ) ) {
$radius = 6371;
}
$lat_field = "geo_query_lat.meta_value";
$lng_field = "geo_query_lng.meta_value";
$lat = 0;
$lng = 0;
if( isset( $geo_query['latitude'] ) ) {
$lat = $geo_query['latitude' ];
}
if( isset( $geo_query['longitude'] ) ) {
$lng = $geo_query['longitude'];
}
$haversine = "( " . $radius . " * ";
$haversine .= "acos( cos( radians(%f) ) * cos( radians( " . $lat_field . " ) ) * ";
$haversine .= "cos( radians( " . $lng_field . " ) - radians(%f) ) + ";
$haversine .= "sin( radians(%f) ) * sin( radians( " . $lat_field . " ) ) ) ";
$haversine .= ")";
$haversine = $wpdb->prepare( $haversine, array( $lat, $lng, $lat ) );
return $haversine;
}
}
GJSGeoQuery::Instance();
}
if( !function_exists( 'the_distance' ) ) {
function the_distance( $post_obj = null, $round = false ) {
GJSGeoQuery::the_distance( $post_obj, $round );
}
}
if( !function_exists( 'get_the_distance' ) ) {
function get_the_distance( $post_obj = null, $round = false ) {
return GJSGeoQuery::get_the_distance( $post_obj, $round );
}
}
<?php
// pre get posts filter
add_filter('pre_get_posts','better_editions_archive');
function better_editions_archive( $query ) {
if( $query->query['post_type'] == 'community_post' ){
/*$meta_query = array(
array(
'lat_clause' => array(
'key' => 'lat',
'compare' => 'EXISTS'
)
),
array(
'lng_clause' => array(
'key' => 'lng',
'compare' => 'EXISTS'
)
)
);
$query->set('meta_query', $meta_query);
$query->set('orderby', array('lat_clause' => 'ASC', 'lng_clause' => 'ASC'));*/
$query->set('geo_query', array(
'lat_field' => 'lat',// this is the name of the meta field storing latitude
'lng_field' => 'lng', // this is the name of the meta field storing longitude
'latitude' => get_user_current_location('lat'), // this is the latitude of the point we are getting distance from
'longitude' => get_user_current_location('lng'),// this is the longitude of the point we are getting distance from
'distance' => 30,// this is the maximum distance to search
'units' => 'km'// this supports options: miles, mi, kilometers, km
));
$query->set( 'orderby', 'distance' );
$query->set('order', 'ASC');
//$query->set('orderby', array('lat_clause' => 'ASC', 'lng_clause' => 'ASC'));
}
print_r('<pre>');
//print_r( $query );
print_r('</pre>');
return $query;
}
//WP QUERY
$query = new WP_Query(array(
'post_type' => 'community_post',
'geo_query' => array(
'lat_field' => 'lat',// this is the name of the meta field storing latitude
'lng_field' => 'lng', // this is the name of the meta field storing longitude
'latitude' => get_user_current_location('lat'), // this is the latitude of the point we are getting distance from
'longitude' => get_user_current_location('lng'),// this is the longitude of the point we are getting distance from
'distance' => 30,// this is the maximum distance to search
'units' => 'km'// this supports options: miles, mi, kilometers, km
),
'orderby' => 'distance', // this tells WP Query to sort by distance
'order' => 'ASC'
));
@DavePodosyan
Copy link

Thank you!

@JiveDig
Copy link

JiveDig commented May 23, 2023

This is great, thank you! The only issue I'm hitting is that get_the_distance() doesn't seem to work on the main archive queries, I can only get it to work in a secondary query via WP_Query. Is anyone getting it to work by hooking into the blog or CPT archive?

@akshuvo
Copy link
Author

akshuvo commented May 23, 2023

@JiveDig you can check your pre_get_posts hook/action. Pls check if you're getting correct distance in get_the_distance() function.

@JiveDig
Copy link

JiveDig commented May 23, 2023

When the geo query is run via pre_get_posts, get_the_distance() returns early because property_exists( $post_obj, 'geo_query_distance' ) is false. The correct posts are shown though, only the distance is empty.

My pre_get_posts filter has this:

// Set geo query.
$query->set( 'orderby', 'distance' );
$query->set( 'order', 'ASC' );
$query->set( 'geo_query',
	[
		'lat_field' => 'location_lat',
		'lng_field' => 'location_lng',
		'latitude'  => $lat,
		'longitude' => $lng,
		'distance'  => $dist, // @int The maximum distance to search.
		'units'     => 'miles', // Supports options: miles, mi, kilometers, km.
	]
);

@JiveDig
Copy link

JiveDig commented May 25, 2023

Got it. The issue was that I was passing get_post( get_the_ID() ), which doesn't have the geo_query_distance property. I switched to the global $post object and now it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment