Last active
September 4, 2015 10:16
-
-
Save nickveenhof/f0e24c235debe32dc27b to your computer and use it in GitHub Desktop.
Solr Geospatial Search
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* Implementation of hook_form_FORM_ID_alter(). | |
* | |
* @param $form | |
* @param $form_state | |
*/ | |
function searchtraining_location_form_apachesolr_search_custom_page_search_form_alter(&$form, $form_state) { | |
// Get the element so we can tell autocomplete it needs to alter that so it | |
// adds the Javascript | |
$element = &$form['basic']['keys']; | |
apachesolr_autocomplete_do_alter($element); | |
} | |
/** | |
* Implement of hook_apachesolr_query_prepare() | |
* | |
* Prepare the query by adding parameters, sorts, etc. | |
* | |
* This hook is invoked before the query is cached. The cached query is used | |
* after the search such as for building facet and sort blocks, so parameters | |
* added during this hook may be visible to end users. | |
* | |
* This is otherwise the same as HOOKapachesolrquery_alter(), but runs before | |
* it. | |
* | |
* @param DrupalSolrQueryInterface $query | |
* An object implementing DrupalSolrQueryInterface. No need for &. | |
*/ | |
function searchtraining_location_apachesolr_query_prepare(DrupalSolrQueryInterface &$query) { | |
// http://wiki.apache.org/solr/CommonQueryParameters#q | |
// Get the search query. | |
$address = $query->getParam('q'); | |
if (!empty($address)) { | |
// Geocode an address | |
$addressmd5 = md5($address); | |
// caching strategy for the location hash versus coordinates | |
$coordinates_cache = cache_get('searchtraining_location:address:' . $addressmd5); | |
if (!empty($coordinates_cache)) { | |
$coordinates = $coordinates_cache->data; | |
} | |
else { | |
// Geocoder: https://drupal.org/project/geocoder | |
// Transforms text to coordinates using Google's API. | |
$point = geocoder('google',$address); | |
if (!empty($point)) { | |
// Geocoder found something, set it to the cache and let's use it. | |
$geoJSON = $point->out('json'); | |
$coordinates = json_decode($geoJSON); | |
cache_set('searchtraining_location:address:' . $addressmd5, $coordinates); | |
} | |
} | |
} | |
if (isset($coordinates) && !empty($coordinates->coordinates)) { | |
// http://wiki.apache.org/solr/DisMaxQParserPlugin#bq.28BoostQuery.29 | |
// Boost on the distance | |
$query->addParam('bq', '{!func}geodist()'); | |
$query->addParam('sfield', 'locs_field_location'); | |
// Solr expect the order to be different. | |
// https://wiki.apache.org/solr/SpatialSearch | |
// The field we want to use to compare our coordinates with is the location | |
// field added by apachesolr_location. The pt (point) we want to compare it | |
// with is the geocoder value we got from google and the distance is within | |
// a 100 kilometers of that coordinate (the geocoder coordinate). | |
$query->addParam('pt', $coordinates->coordinates[1] . ',' . $coordinates->coordinates[0]); | |
$query->addParam('d', 100); | |
// http://wiki.apache.org/solr/DisMaxQParserPlugin#qf.28QueryFields.29 | |
$query->addParam('qf', 'locs_field_location'); | |
// Sorting on score, score is influenced by the location distance as a | |
// boost. | |
$query->setSolrsort('score', 'desc'); | |
// Setting our Query parser to edismax | |
// http://wiki.apache.org/solr/ExtendedDisMax | |
$query->addParam('defType', 'edismax'); | |
// http://wiki.apache.org/solr/ExtendedDisMax#boost.28BoostFunction.2C_multiplicative.29 | |
// We're going to calculate the distance and give the boost a function so | |
// that the closer the distance, the higher the boost | |
$query->addParam('boost', 'recip(geodist(),2,200,20)'); | |
// http://wiki.apache.org/solr/CommonQueryParameters#fl | |
// We want to have the field_location field back in our results (fl => fields) | |
$query->addParam('fl', 'locs_field_location'); | |
// Since we want also results to return that do not have the location set | |
// we set the search to OR. Eg. Dublin OR 123,123 (Dublin) | |
$query2 = new SolrFilterSubQuery('OR'); | |
$query2->addFilter('content', $address); | |
$query2->addFilter('_query_', "\"{!geofilt}\""); | |
$query->addFilterSubQuery($query2); | |
} | |
} | |
/** | |
* Add some theming goodness to our results. | |
* | |
* Converting two coordinate sets to a distance using location functions. | |
* | |
* @see http://api.drupalize.me/api/drupal/function/locationdistancebetween/7 | |
* | |
* @param array $results | |
* @param DrupalSolrQueryInterface $query | |
*/ | |
function searchtraining_location_apachesolr_process_results(&$results, DrupalSolrQueryInterface $query) { | |
$sfield = $query->getParam('sfield'); | |
if (isset($sfield[0]) && $sfield[0] == 'locs_field_location') { | |
foreach ($results as &$result) { | |
$coordinates = $query->getParam('pt'); | |
$result_coordinates = $result['fields']['locs_field_location']; | |
if (!empty($coordinates[0]) && $result_coordinates != '0.000000,0.000000') { | |
$request_coordinates = explode(',', $coordinates[0]); | |
$result_coordinates = explode(',', $result_coordinates); | |
$request_coordinates['lat'] = $request_coordinates[0]; | |
$request_coordinates['lon'] = $request_coordinates[1]; | |
if (isset($result_coordinates[0]) && isset($result_coordinates[1])) { | |
$result_coordinates['lat'] = $result_coordinates[0]; | |
$result_coordinates['lon'] = $result_coordinates[1]; | |
$distance = location_distance_between($request_coordinates, $result_coordinates, 'km'); | |
$t_args = array( | |
'!scalar' => $distance['scalar'], | |
'!unit' => $distance['distance_unit'], | |
); | |
$result['snippet'] = $result['snippet'] . '<p class="location">'; | |
$result['snippet'] .= t('!scalar !unit from requested destination', $t_args); | |
$result['snippet'] .= '</p></br>'; | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment