Haben Sie Fragen? 04267 / 234427

Umkreissuche in Drupal 8 erstellen

In Drupal 7 sind "fertige" Proximity Search Lösungen via Location oder auch Geofield+Geocoder verfügbar. Leider haben diese Module für Drupal 8 momentan noch keine funktionierenden Plugins für die Views Exposed Filter. Als Alternative steht uns in D8 das Modul Geolocation zur Verfügung, welches eine funktionierende Views Integration für eine Umkreissuche mitbringt. Die Sache hat nur einen kleinen Haken: Der Exposed Filter erwartet Längen- und Breitengrad als Input Value.

default-proximity-search-geofield.png

Längen- und Breitengrade interessieren Anwender natürlich nicht, deshalb wird ein custom module die Aufgabe des Geocodings übernehmen (Geocoding: Umwandeln von Standortdaten wie Strasse, Ort oder auch PLZ in Geodaten wie Längen- und Breitengrad) und den Proximtity Views Filter von Geolocation füttern. Ich benutze hier den Geocoding Service von Google, da er meiner Meinung nach die besten Ergebnisse sehr schnell zurück liefert.

Für unsere Umkreissuche in Drupal 8 brauchen wir also:

  1. Google Geocoding API Schlüssel
  2. Drupal Geolocation Module letzte stable Version
  3. Views (im Core)
  4. unser custom module (ist unten auch zum Download angehängt)

How To

1. Google Geocoding API Key

Erstelle bei Google Developers ein Projekt und aktiviere darin die Google Maps Geocoding API. Anschließend musst du noch einen API Key generieren. Wenn du im Geolocation Form Field das Google Geocoder Widget nutzen möchtest, dann aktiviere auch gleich die Google Maps JavaScript API.

2. Geolocation Module installieren

drush:
drush dl geolocation && drush en geolocation

oder mit drupal console:

drupal module:install geolocation

oder halt via Web Oberfläche in Drupal.
In den Geolocation Settings unter config/services/geolocation trägst du den API Key, den du bei Google generiert hast, ein.

3. Drupal Entity für deine Locations erstellen

Erstelle dein Entity (Inhaltstyp) für deine Locations und füge ein Feld vom Typ "Geolocation" hinzu. Nenne das Feld "Geodaten" (Maschinenname: field_geodaten), wenn du das custom Module nicht extra anpassen willst. Hier werden bei der Node Erstellung je nach Einstellung Längen- und Breitengrad oder Standortdaten eingegeben.

Erstelle zwei, drei Entities mit Geodaten zum Testen.

4. Die View für die Umkreissuche erstellen

Erstelle eine View für deinen Inhaltstypen mit den Geodaten. Nenne die View "Proximity Search" (Maschinenname: proximity_search), wenn du das custom module nicht extra anpassen willst.

Gib deinen Views Displays unter Erweitert -> Andere einen eigenen Maschinen Namen:
Display Type Page: Searchpage
Display Type Block: Searchblock

Füge einen Exposed Filter "Proximity (field_geodaten)" hinzu. Einstellungen:

  • Diesen Filter für Seitenbesucher freigeben, so dass sie die Optionen selbst wählen können
  • Einzelner Filter
  • Als Operator: "ist weniger als oder gleich"
  • Bezeichner: umkreis

Damit haben wir schon mal eine funktionierende Umkreissuche.

Hinweis: Wenn du ein Block Display mit exposed Filter(n) verwendest, musst du im View Display unter Erweitert ->Ajax verwenden aktivieren!

5. Custom Modul zur Geocodierung und Anpassung des exposed Filter Forms

Wie eingangs erwähnt, ist die Eingabe von Lat / Long natürlich indiskutabel. Unser custom module "better_proximity_search" hat folgende Aufgaben:

  • Hinzufügen eines Textfeldes zur freien Eingabe des Ortes oder der PLZ
  • Geocodieren der Standorteingaben in Latitude und Longitude
  • Die Auswahl des Umkreises in einer Select- List statt freier Eingabe
  • Setzen eines default Umkreises von 5 km, da sonst immer gerne der altbekannte "unzulässige Eingabe"- Fehler auftaucht

In einem form_alter() hook modifizieren wir das Views exposed filter form und das Geocoding erfolgt in unserer custom Controller Class:

better_proximity_search.module:

<?php

/**
 * @file
 * Contains better_proximity_search.module..
 */

use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Implements hook_help().
 */
function better_proximity_search_help($route_name, RouteMatchInterface $route_match) {
  switch (
$route_name) {
   
// Main module help for the better_proximity_search module.
   
case 'help.page.better_proximity_search':
     
$output = '';
     
$output .= '<h3>' . t('About') . '</h3>';
     
$output .= '<p>' . t('Ergaenzt Geoloction Umkreis Suche mit Views') . '</p>';
      return
$output;

    default:
  }
}

/**
 * Implements hook_form_alter().
 *
 * 1. Hinzufügen einer Validierungsfunktion für das Geocoding
 * 2. Hinzufügen eines Location Textfeldes zur Location Eingabe
 * 3. Ändern des Umkreis Feldes zu Dropdown
 */
function better_proximity_search_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {

  if (isset(
$form['#id']) && ($form['#id'] == 'views-exposed-form-proximity-search-searchpage' || $form['#id'] == 'views-exposed-form-proximity-search-searchblock')) {
   
$form['#validate'][] = 'Drupal\better_proximity_search\Controller\ProximityController::proximity_search_form_validate';
   
// Kein User Access für lat und lng Felder -> Werte werden programmatisch gesetzt
   
$form['lat']['#access'] = FALSE;
   
$form['lng']['#access'] = FALSE;
   
// Textfeld zur Eingabe der Location
   
$form['location'] = array(
     
'#type' => 'textfield',
     
'#weight' => 20,
     
'#title' => t('Please enter location or postal code'), // Title kann entfernt werden, wenn nicht gewünscht
     
'#placeholder' => t('e.g. Hamburg or 20345')
    );
   
// Umkreis Dropdown mit Option List
   
$form['umkreis'] = array(
     
'#type' => 'select',
     
'#options' => array(
        
'' => t('proximity'),
       
'10' => '10 km',
       
'25' => '25 km',
       
'50' => '50 km',
       
'75' => '75 km',
       
'100' => '100 km',
       
'150' => '150 km',
       
'200' => '200 km',
       
'250' => '250 km',
       
'300' => '300 km',
       
'500' => '500 km',
       
'1000' => '1000 km'
     
),
     
'#weight' => 30
   
);
  }
}
?>

ProximityController.php

<?php

namespace Drupal\better_proximity_search\Controller;

use
Drupal\Core\Controller\ControllerBase;
use
Drupal\Core\Form\FormStateInterface;

/**
 * Class ProximityController.
 *
 * @package Drupal\better_proximity_search\Controller
 *
 */
class ProximityController extends ControllerBase {
 
// Form validate Funktion --> aufgerufen in better_proximity_search_form_alter()
 
public static function proximity_search_form_validate(&$form, FormStateInterface $form_state) {

   
$input = &$form_state->getUserInput();
   
// User Input für custom location Feld prüfen
   
if (!empty($input['location'])) { //Wenn Input, dann:
      // Google API Key aus den Geolocation Settings holen
     
$api_key = \Drupal::config('geolocation.settings')->get('google_map_api_key');
     
// Ersetzen von Leerzeichen aus dem User Location Input mit "+"
     
$user_location = urlencode($form_state->getValue('location'));
     
// Google Geocoder Request. Hinweis: Anfragen sind für Deutschland optimiert ('&components=country:DE')
      // Wenn dies nicht erwünscht ist, entferne: . '&components=country:DE' oder setze das Länderkürzel deiner Wahl
     
$request_url = '<a href="https://maps.googleapis.com/maps/api/geocode/json?address='">https://maps.googleapis.com/maps/api/geocode/json?address='</a> . $user_location . '&key=' . $api_key . '&components=country:DE';
     
$response_json = file_get_contents($request_url);
      // Umwandeln des JSON Objekts in ein PHP Array
     
$response = json_decode($response_json, TRUE);

      // Wenn Geocodierung ok, setzen der Werte für Längen- und Breitengrad
      if (
$response['status'] == 'OK') {
       
$form_state->setValue('lat', $response['results'][0]['geometry']['location']['lat']);
       
$form_state->setValue('lng', $response['results'][0]['geometry']['location']['lng']);
        // Wenn kein Umkreis gewählt wurde, werden 5 km als default gesetzt.
        if (empty(
$input['umkreis'])) {
         
$form_state->setValue('umkreis', '5');
        }
      }
      // Fehlermeldung, wenn die Google API kein Ergebnis liefert
      elseif (
$response['status'] == 'ZERO_RESULTS') {
        drupal_set_message(t('Your desired location is not available. Pleas check your data input '), 'error');
      }
    }
  }
}
?>

Das Modul wie üblich installieren und ggf. Anpassungen vornehmen.

Downloads:

Kommentar

Vom Feinsten !!! Vielen Dank für das Modul und die klasse Anleitung. Hat mein Problem gelöst.

Hallo Guido, warum veröffentlicht ihr nicht euer custom modul auf drupal.org?

@rusty: Weil für ein contrib module noch viel mehr notwendig ist und meine Idee eher in die Richtung geht, die Integration den den Leuten von geolocation anzubieten. Dieses custom module ist eher als "Übergangslösung" für D8 zu verstehen. Wenn es eine funktionierende Lösung gibt, ist es halt überflüssig. Hauptsache, wer jetzt schnell eine Lösung braucht, kommt damit weiter....

Wer vor dem Problem steht, das es derzeit nicht funktioniert: Dieser Patch hiflt: https://www.drupal.org/node/2840700 Dazu unbedingt! Im Controller für das Form-Array müssen die Keys aus Bezeichner + '-lng' und Bezeichner + 'lat' zusammengesetzt werden. Für das Beispiel oben heißt das: controller: $form_state->setValue('lat', $response['results'][0]['geometry']['location']['umkreis-lat']); $form_state->setValue('lng', $response['results'][0]['geometry']['location']['umkreis-lng']); module: $form['umkreis-lat']['#access'] = FALSE; $form['umkreis-lng']['#access'] = FALSE;

Hallo Guido, das Modul liefert alle Ergebnisse, die es gibt. Ich habe leere Drupal 8 Seite erstellt und dann Ihre Anweisungen implementiert. Nur Maschinen Name habe ich nicht geändert. Die Umkreissuche bekommt das Respons von API aber liefert alle drei Entities (Artikel mit Geodaten) die ich hinzugefügt habe. Haben Sie eine Idee warum? Die Seite ist vollkommen leer.