You are here

protected function SearchApiSolrBackend::setSpatial in Search API Solr 8.2

Same name and namespace in other branches
  1. 8.3 src/Plugin/search_api/backend/SearchApiSolrBackend.php \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend::setSpatial()
  2. 8 src/Plugin/search_api/backend/SearchApiSolrBackend.php \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend::setSpatial()
  3. 4.x src/Plugin/search_api/backend/SearchApiSolrBackend.php \Drupal\search_api_solr\Plugin\search_api\backend\SearchApiSolrBackend::setSpatial()

Adds spatial features to the search query.

Parameters

\Solarium\QueryType\Select\Query\Query $solarium_query: The solr query.

array $spatial_options: The spatial options to add.

array $field_names: The field names, to add the spatial options for.

Throws

\Drupal\search_api_solr\SearchApiSolrException

1 call to SearchApiSolrBackend::setSpatial()
SearchApiSolrBackend::search in src/Plugin/search_api/backend/SearchApiSolrBackend.php
Options on $query prefixed by 'solr_param_' will be passed natively to Solr as query parameter without the prefix. For example you can set the "Minimum Should Match" parameter 'mm' to '75%' like this:

File

src/Plugin/search_api/backend/SearchApiSolrBackend.php, line 3178

Class

SearchApiSolrBackend
Apache Solr backend for search api.

Namespace

Drupal\search_api_solr\Plugin\search_api\backend

Code

protected function setSpatial(Query $solarium_query, array $spatial_options, $field_names = []) {
  if (count($spatial_options) > 1) {
    throw new SearchApiSolrException('Only one spatial search can be handled per query.');
  }
  $spatial = reset($spatial_options);
  $solr_field = $field_names[$spatial['field']];
  $distance_field = $spatial['field'] . '__distance';
  $solr_distance_field = $field_names[$distance_field];
  $spatial['lat'] = (double) $spatial['lat'];
  $spatial['lon'] = (double) $spatial['lon'];
  $spatial['radius'] = isset($spatial['radius']) ? (double) $spatial['radius'] : 0.0;
  $spatial['min_radius'] = isset($spatial['min_radius']) ? (double) $spatial['min_radius'] : 0.0;
  if (!isset($spatial['filter_query_conditions'])) {
    $spatial['filter_query_conditions'] = [];
  }
  $spatial['filter_query_conditions'] += [
    'field' => $solr_field,
    'value' => $spatial['radius'],
    'operator' => '<',
  ];

  // Add a field to the result set containing the calculated distance.
  $solarium_query
    ->addField($solr_distance_field . ':geodist()');

  // Set the common spatial parameters on the query.
  $spatial_query = $solarium_query
    ->getSpatial();
  $spatial_query
    ->setDistance($spatial['radius']);
  $spatial_query
    ->setField($solr_field);
  $spatial_query
    ->setPoint($spatial['lat'] . ',' . $spatial['lon']);

  // Add the conditions of the spatial query. This might adust the values of
  // 'radius' and 'min_radius' required later for facets.
  $solarium_query
    ->createFilterQuery($solr_field)
    ->setQuery($this
    ->createLocationFilterQuery($spatial));

  // Tell solr to sort by distance if the field is given by Search API.
  $sorts = $solarium_query
    ->getSorts();
  if (isset($sorts[$solr_distance_field])) {
    $new_sorts = [];
    foreach ($sorts as $key => $order) {
      if ($key == $solr_distance_field) {
        $new_sorts['geodist()'] = $order;
      }
      else {
        $new_sorts[$key] = $order;
      }
    }
    $solarium_query
      ->clearSorts();
    $solarium_query
      ->setSorts($new_sorts);
  }

  // Change the facet parameters for spatial fields to return distance
  // facets.
  $facet_set = $solarium_query
    ->getFacetSet();
  if (!empty($facet_set)) {

    /** @var \Solarium\Component\Facet\Field[] $facets */
    $facets = $facet_set
      ->getFacets();
    foreach ($facets as $delta => $facet) {
      $facet_options = $facet
        ->getOptions();
      if ($facet_options['field'] != $solr_distance_field) {
        continue;
      }
      $facet_set
        ->removeFacet($delta);
      $limit = $facet
        ->getLimit();

      // @todo Check if these defaults make any sense.
      $steps = $limit > 0 ? $limit : 5;
      $step = ($spatial['radius'] - $spatial['min_radius']) / $steps;
      for ($i = 0; $i < $steps; $i++) {
        $distance_min = $spatial['min_radius'] + $step * $i;

        // @todo $step - 1 means 1km less. That opens a gap in the facets of
        //   1km that is not covered.
        $distance_max = $distance_min + $step - 1;

        // Define our own facet key to transport the min and max values.
        // These will be extracted in extractFacets().
        $key = "spatial-{$distance_field}-{$distance_min}-{$distance_max}";

        // Due to a limitation/bug in Solarium, it is not possible to use
        // setQuery method for geo facets.
        // So the key is misused to get a correct query.
        // @see https://github.com/solariumphp/solarium/issues/229
        $facet_set
          ->createFacetQuery($key . ' frange l=' . $distance_min . ' u=' . $distance_max)
          ->setQuery('geodist()');
      }
    }
  }
}