Drupal

Drupal module to use the geo.gouv.fr geolocation API in a form (instead of Google geocoding :)

Published on 15 July 2021
Image illustrating the use of the geocoding API of the Open Data platform in France
Here is the code for a second module currently being contributed on drupal.org. Before proceeding, we are releasing the code and the process. It makes it possible to do without Google's geocoding API for addresses in France.

The project

On the occasion of the redesign of the CGT unionization form, we wanted to offer users a simpler journey. The aim of this project is to simplify the entry of their mailing address.

Users here have an autocompletion field when entering their address. This feature, often provided by (paid) geocoding services from Google, is here offered via the API of the French Open Public Data Platform. By filling in the address field, the module suggests addresses (street number + street name) and then completes the supplementary address fields (postal code, city, etc.)

Module purpose

The module is available here: 
https://www.drupal.org/project/api_adresses_open_data_france -

The code is available here: 
https://git.drupalcode.org/project/api_sirene_open_data_france/commit/4d7abc1 -

The module can be used to simplify any registration that requires information about addresses available on French territory.

The Address API

https://geo.api.gouv.fr/adresse
As part of the Public Data Service, many datasets are open. The ADDRESS database can be freely and freely accessed!

This service queries the address API developed by Étalab.
This website and its API are open-source: you can download the code on GitHub.
The SIRENE reference data is available on Data.gouv.fr.

The demo

Autocompletion from an address field:

Creating the route

Creation of the route for the Ajax callback in the module's .routing.yml file:

bluedropfr_syndicalisation_new.address_autocomplete:
  path: '/cgt-syndicalisation/address-autocomplete'
  defaults:
    _controller: '\Drupal\bluedropfr_syndicalisation_new\Controller\AddressAjaxController::address_autocomplete'
    _title: 'Autocomplete'
    _format: json
  requirements:
    _access: 'TRUE'

The manager file

A Manager file is created (to be extended) - For now, it only contains the methods necessary for address search and autocompletion of fields.
GeoAPIGouvFrManager.php

error($msg);
      return;
    }
    $this->client = \Drupal::httpClient();
  }

  /**
   * Do CURL request with authorization.
   *
   * @param string $resource
   *   A request action of api.
   * @param string $method
   *   A method of curl request.
   * @param Array $inputs
   *   A data of curl request.
   *
   * @return array
   *   An associate array with respond data.
   */
  private function executeCurl($resource, $method, $inputs) {
    if (!function_exists('curl_init')) {
      $msg = 'Geo API gouv.fr requires CURL module';
      \Drupal::logger('bluedropfr_syndicalisation_new')->error($msg);
      return NULL;
    }
    $api_url = self::API_URL . "/" . $resource;

    $options = [
      'headers' => [
        'Content-Type' => 'application/json'
      ],
    ];

    if (!empty($inputs)) {
      
      if($method == 'GET'){
        $api_url.= '?' . self::arrayKeyfirst($inputs) . '=' . array_shift($inputs);
        foreach($inputs as $param => $value){
            $api_url.= '&' . $param . '=' . $value;
        }
      }else{
        //POST request send data in array index form_params.
        //$options['body'] = $inputs;
      }
    }

    try {
      $clientRequest = $this->client->request($method, $api_url, $options);
      $body = $clientRequest->getBody();
    } catch (RequestException $e) {
      \Drupal::logger('bluedropfr_syndicalisation_new')->error('Curl error: @error', ['@error' => $e->getMessage()]);
    }

    return Json::decode($body);
  }

  /**
   * Get Request of API.
   *
   * @param string $resource
   *   A request action.
   * @param string $input
   *   A data of curl request.
   *
   * @return array
   *   A respond data.
   */
  public function curlGet($resource, $inputs) {
    return $this->executeCurl($resource, "GET", $inputs);
  }

  /**
   * Post Request of API.
   *
   * @param string $resource
   *   A request action.
   * @param string $inputs
   *   A data of curl request.
   *
   * @return array
   *   A respond data.
   */
  public function curlPost($resource, $inputs) {
    return $this->executeCurl($resource, "POST", $inputs);
  }

  /**
   * Search place by street number, city name, street name...
   *
   * @param array $options
   *   An array of search options.
   *
   * @return array
   *   An array of search results.
   */
  public function searchPlace($options) {
    return $this->curlGet("search", $options);
  }

  /**
   * Get campaigns by type.
   *
   * @param string $type
   *   A campaign type.
   *
   * @return array
   *   An array of options.
   */
  public function buildOptionsSearchAutocomplete($searchString, $type, $limit = 5, $autocomplete = 1) {
    $options = [
        "q" => $searchString,
        "type" => $type,
        "autocomplete" => $autocomplete,
        "limit" => $limit,
    ];
    return $options;
  }
  
  /**
   * Function to return first element of the array, compatability with PHP 5, note that array_key_first is only available for PHP > 7.3.
   *
   * @param array $array
   *   Associative array.
   *
   * @return string
   *   The first key data.
   */
  public static function arrayKeyfirst($array){
    if (!function_exists('array_key_first')) {
        foreach($array as $key => $unused) {
            return $key;
        }
        return NULL;
    }else{
        return array_key_first($array);
    }
  }

}

The controller

A controller serves as a callback for the Ajax call.
AddressAjaxController.php

request->get('name_startsWith');
    $type = "housenumber"; // can be housenumber, street, locality or municipality.

    $geoApi = new GeoAPIGouvFrManager();
    $return = $geoApi->searchPlace($geoApi->buildOptionsSearchAutocomplete($search_string, $type, 10));

    return new JsonResponse(json_encode($return['features']), 200, [], true);
  }
  
}

The javascript component for user interaction

(function ($) {
  $(document).ready(function() {
    $('#edit-adresse-rue').autocomplete({
			source : function(requete, reponse){ // both arguments represent the necessary data for the plugin
			$.ajax({
			        url : Drupal.url('cgt-syndicalisation/address-autocomplete'), // calls the JSON script
				dataType : 'json', // specify that the data type is JSON
				type: "POST",
				data : {
            			//variable sent with the request to the server
					name_startsWith : $('#edit-adresse-rue').val(), // supplies the string typed in the search field
				},
				success : function(donnee){
            			//donnee is the variable received from the server with the results
					reponse($.map(donnee, function(objet){
							return {'label':objet.properties.label,
'value':objet.properties.name,
'postcode':objet.properties.postcode,
'id':objet.properties.id, 
'city':objet.properties.city,}; // return this form of suggestion
						}));
					}
				});
			}
		});

	$('#edit-adresse-rue').on( "autocompleteselect", function( event, ui ) {
		var postcode = ui.item.postcode;
		var city = ui.item.city;
		$('#edit-code-postal').val(postcode);
		$('#edit-ville').val(city);
	});

  });
})(jQuery);

For more information on the API's JSON attributes: https://github.com/geocoders/geocodejson-spec/tree/master/draft

Try it out!
Drupal API Addresses Open Data France Module

Many thanks to @elie for this contribution!!

?>

Read more articles on Drupal