I am stuck into this problem where the main requirement is to find all results from the database which are within 500 miles of the given latitude and longitude.
I have stored the lati and longi into the post_meta along with the zip code of those latitude and longitude now the idea is that when ever user searches for any zip code and select the miles from the drop down all the results matching that query should come for instance.
User makes a query like this
500 mi of 51310
The query should return all the results which are within 500 mi of the zip code 51310.
Here’s what I have done so far.
This is the main function that is actually making the query to the database.
if (isset($_GET['s']) && !empty($_GET['s'])) {
$searchterm = isset($q->query_vars['s']) ? sanitize_text_field($q->query_vars['s']) : '';
// we have to remove the "s" parameter from the query, because it will prevent the posts from being found
$q->query_vars['s'] = '';
if ($searchterm != '') {
$search_radius = $this->geocode($searchterm);
if (!$search_radius) {
return false;
}
$lat = $search_radius['lat']; // get the lat of the requested address
$lng = $search_radius['lng']; // get the lng of the requested address
// we'll want everything within, say, 30km distance
$distance = isset($_GET['within']) && !empty($_GET['within']) ? floatval($_GET['within']) : 50;
// earth's radius in km = ~6371
$radius = auto_listings_metric() == 'yes' ? 6371 : 3950;
// latitude boundaries
$maxlat = $lat + rad2deg($distance / $radius);
$minlat = $lat - rad2deg($distance / $radius);
// longitude boundaries (longitude gets smaller when latitude increases)
$maxlng = $lng + rad2deg($distance / $radius / cos(deg2rad($lat)));
$minlng = $lng - rad2deg($distance / $radius / cos(deg2rad($lat)));
// build the meta query array
$radius_array = array(
'relation' => 'AND',
);
$radius_array[] = array(
'key' => '_al_listing_lat',
'value' => array($minlat, $maxlat),
'type' => 'DECIMAL(10,5)',
'compare' => 'BETWEEN',
);
$radius_array[] = array(
'key' => '_al_listing_lng',
'value' => array($minlng, $maxlng),
'type' => 'DECIMAL(10,5)',
'compare' => 'BETWEEN',
);
return apply_filters('auto_listings_search_radius_args', $radius_array);
And to find the geocode I have written this function.
$address = urlencode(esc_html($address));
// google map geocode api url
$url = auto_listings_google_geocode_maps_url($address);
$arrContextOptions = array(
"ssl" => array(
"verify_peer" => false,
"verify_peer_name" => false,
),
);
// get the json response
$resp_json = file_get_contents($url, false, stream_context_create($arrContextOptions));
// decode the json
$resp = json_decode($resp_json, true);
//pp( $resp );
// response status will be 'OK', if able to geocode given address
if ($resp['status'] == 'OK') {
// get the lat and lng
$lat = $resp['results'][0]['geometry']['location']['lat'];
$lng = $resp['results'][0]['geometry']['location']['lng'];
// verify if data is complete
if ($lat && $lng) {
return array(
'lat' => $lat,
'lng' => $lng,
);
} else {
return false;
}
} else {
return false;
}
If I do not select the within field this code returns the exact results of that particular zip code which is good but when I select within field it displays nothing. Can someone tell me what in this code I am doing wrong.
1 Answer
The problem was when I send the query to the database it was changing my array level other than that the ‘s’ value was not going empty due to which it was preventing my records to be fetched and the third and most important one was the way I was making my query by changing my function resolved my problem.
Here is the updated code for this logic in case some one else need to make such functionality.
The main function where all the data is gathering up and making a query
public function pre_get_posts($q) {
// check if the user is requesting an admin page
if (is_admin() || !$q->is_main_query())
return;
if (!is_post_type_archive('auto-listing'))
return;
if (!is_search())
return;
$meta_query = array();
$year_query[] = $this->year_query();
$model_query[] = $this->model_query();
$condition_query[] = $this->condition_query();
$odometer_query[] = $this->odometer_query();
$price_query[] = $this->price_meta_query();
$body_type_query = $this->body_type_query();
$country_query = $this->country_query();
$transmission_query = $this->transmission_query();
$radius_query[] = $this->radius_query($q);
$query_1 = array_merge($country_query, $year_query, $model_query, $condition_query, $price_query, $odometer_query, $transmission_query);
// if our radius query fails, fall back to keyword searching
// will fail with no map API key
if (empty($radius_query[0]) || !$radius_query[0]) {
$keyword_query[] = $this->keyword_query($q);
$query_2 = $keyword_query;
} else {
$query_2 = $radius_query;
}
// if no keyword
if (empty($_GET['s'])) {
$query_1['relation'] = 'AND';
$meta_query[] = $query_1;
}
// if keyword
if (!empty($_GET['s'])) {
$query_2['relation'] = 'OR';
$meta_query[] = $query_1;
$meta_query[] = $query_2;
$meta_query['relation'] = 'AND';
}
$q->set('meta_query', $meta_query);
$q->set('tax_query', $body_type_query);
$q->set('post_type', 'auto-listing');
}
Radius query function that was actually doing the magic
public function radius_query($q) {
if (isset($_GET['s']) && !empty($_GET['s'])) {
$searchterm = isset($q->query_vars['s']) ? sanitize_text_field($q->query_vars['s']) : '';
// we have to remove the "s" parameter from the query, because it will prevent the posts from being found
$q->query_vars['s'] = '';
if ($searchterm != '') {
$search_radius = $this->geocode($searchterm);
if (!$search_radius)
return false;
$lat = $search_radius['lat']; // get the lat of the requested address
$lng = $search_radius['lng']; // get the lng of the requested address
// we'll want everything within, say, 30km distance
$distance = isset($_GET['within']) && !empty($_GET['within']) ? floatval($_GET['within']) : 0.1;
// earth's radius in km = ~6371
$radius = auto_listings_metric() == 'yes' ? 6371 : 3950;
// latitude boundaries
$maxlat = $lat + rad2deg($distance / $radius);
$minlat = $lat - rad2deg($distance / $radius);
// longitude boundaries (longitude gets smaller when latitude increases)
$maxlng = $lng + rad2deg($distance / $radius / cos(deg2rad($lat)));
$minlng = $lng - rad2deg($distance / $radius / cos(deg2rad($lat)));
// build the meta query array
$radius_array = array(
'relation' => 'AND',
);
$radius_array[] = array(
'key' => '_al_listing_lat',
'value' => array($minlat, $maxlat),
'type' => 'DECIMAL(10,5)',
'compare' => 'BETWEEN',
);
$radius_array[] = array(
'key' => '_al_listing_lng',
'value' => array($minlng, $maxlng),
'type' => 'DECIMAL(10,5)',
'compare' => 'BETWEEN',
);
return apply_filters('auto_listings_search_radius_args', $radius_array);
}
}
}
And now the final function that was fetching the lat and long from the zip code that user enters
private function geocode($address) {
// url encode the address
$address = urlencode(esc_html($address));
// google map geocode api url
$url = auto_listings_google_geocode_maps_url($address);
$arrContextOptions = array(
"ssl" => array(
"verify_peer" => false,
"verify_peer_name" => false,
),
);
// get the json response
$resp_json = file_get_contents($url, false, stream_context_create($arrContextOptions));
// decode the json
$resp = json_decode($resp_json, true);
//pp( $resp );
// response status will be 'OK', if able to geocode given address
if ($resp['status'] == 'OK') {
// get the lat and lng
$lat = $resp['results'][0]['geometry']['location']['lat'];
$lng = $resp['results'][0]['geometry']['location']['lng'];
// verify if data is complete
if ($lat && $lng) {
return array(
'lat' => $lat,
'lng' => $lng,
);
} else {
return false;
}
} else {
return false;
}
}
In order to find post based on this answer one must have the zip code stored into the post_meta table to fetch the lati and longi and the formula that is given in the code to find the max and min boundaries will get the lat and lng itself. I hope I gave the answer in detail if not please feel free to write comments.