Makina Blog

Le blog Makina-corpus

Web mapping avec Drupal et Leaflet


L'intégration de @LeafletJS avec #drupal est vraiment très rapide à mettre en œuvre.

Introduction

Pour le site Drupal d'un client, nous avons du développer une page listant des partenaires (type de contenu pré-existant) de 2 façons : d'abord une carte dynamique, ensuite un listing plus classique. La majorité du travail est réalisable directement dans l'interface, et nous n'avons utilisé le développement que pour améliorer l'import des données.

Voici à quoi ressemble le résultat final (partie cartographique uniquement) :

Modules utilisés

Pour la partie web mapping, notre bibliothèque de prédilection chez Makina Corpus est Leaflet, dont nous avons déjà parlé dans plusieurs articles.

Le stockage de données géographiques dans Drupal se fait avec Geofield, et le géocodage des données souvent avec Geocoder (dont nous sommes d'ailleurs co-mainteneurs). Le champ de référence du geocodage est une des questions à se poser : Adressfield ? Un simple champ texte ? Tout dépend du format des données que nous avons (qui sont alors plus ou moins facile à importer selon le champ choisi), mais aussi de comment nous allons les exploiter : si nous avons besoin de la visualisation par ville, par exemple, alors une séparation des champs (et donc, éventuellement, un AdressField) sera nécessaire.

Voici pour référence la partie d'un fichier pour drush make utilisé pour le cas présent :

projects[] = geophp
projects[] = geofield
libraries[leaflet][download][type] = "get"
libraries[leaflet][download][url] = "http://leaflet-cdn.s3.amazonaws.com/build/leaflet-0.7.3.zip"
projects[] = leaflet
projects[] = addressfield
projects[] = geocoder

Récupération des données

Les données sources provenant d'un fichier .xls, ma première idée fut d'utiliser le module Feeds, dédié à l'import de données, qui comporte une interface graphique permettant au client d'éventuellement modifier le mapping si le fichier venait à changer. C'est l'approche la plus "site builder" possible, sans aucun développement associé. Malheureusement, le fichier de données nécessitait trop de transformations (même en utilisant des modules additionnels comme Feeds Tamper, qui permet d'effectuer quelques transformations durant l'import).

Heureusement, comme souvent dans Drupal, il existe une approche "développeur" au besoin : l'utilisation du module Migrate. Il m'a suffit d'ajouter au module pré-existant un fichier mymodule.migrate.inc contenant la déclaration de ma migration pour réaliser les traitements que je voulais (notamment, tout est réalisée dans la fonction prepareRow() :

/**
 * Implements hook_migrate_api().
 */
function mymodule_migrate_api() {
  return array(
    'api' => 2,
    'groups' => array(
      'client_name' => array(
        'title' => 'Migration for the new version of the client website',
      ),
    ),
    'migrations' => array(
      'ClientPartners' => array('class_name' => 'ClientPartnersMigration', 'group_name' => 'client_name'),
    ),
  );
}

/**
 * Main migration class.
 */
class ClientPartnerMigration extends Migration {
  public function __construct() {
    parent::__construct();
    // "Real" CSV columns.
    $columns = array(
      0 => array('id', 'ID'),
      2 => array('country', 'Country'),
      3 => array('city', 'City'),
      ...
      12 => array('address', 'Address'),
      13 => array('website', 'Website'),
    );
    // "Pseudo-colums".
    $fields = array(
      'field1' => 'composite field populated by prepareRow()',
      'field2' => 'composite field populated by prepareRow()',
      'field3' => 'composite field populated by prepareRow()',
    );
    $this->source = new MigrateSourceCSV(
      DRUPAL_ROOT . '/' . drupal_get_path('module', 'mymodule') . '/partners.csv',
      $columns,
      array('header_rows' => 1),
      $fields
    );
    $this->destination = new MigrateDestinationNode('partners');
    $this->map = new MigrateSQLMap(
      $this->machineName,
      array('id' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE,)),
      MigrateDestinationNode::getKeySchema()
    );
    $this->addFieldMappings();
  }

  function prepareRow($row) {
    // Aggregate & transform data so we can use it during field mapping.
    $row->field1 = ... ;
    $row->field2 = ... ;
  }

  function addFieldMappings() {
    // Contents are published and created by admin.
    $this->addFieldMapping('uid')->defaultValue('1');
    $this->addFieldMapping('status')->defaultValue('1');
    // "Real" field mappings.
    $this->addFieldMapping('title', 'name');
    $this->addFieldMapping('field_country', 'country');
    ...
    $this->addFieldMapping('field_custom', 'field1');
  }
} 

Amélioration du géocodage

L'avantage d'utiliser une migration, c'est qu'on peut revenir en arrière et la relancer tant que les données importées ne sont pas correctes, cela m'a permis d'ajuster la précision du géocodage en ajoutant par exemple la ville et / ou le pays au champ adresse dans le cas où celui-ci ne les contenait pas déjà, passant de 80 points correctement localisés à 145 (sur 150 données entrantes).

Réalisation de la carte

Views

La carte elle-même est réalisée rapidement en utilisant le module Drupal d'intégration de Leaflet, et en créant simplement une Views listant les contenus concernés sous forme de Leaflet Map (attention, il faut ajouter impérativement à la vue le champ contenant les informations géographiques, le Geofield mentionné au début de l'article).

Fond de carte

Le module Leaflet ne contient qu'un fond de carte standard, et notre client souhaitait un fond un peu plus travaillé. De nombreux fonds sont disponibles sur internet, celui présenté sur la capture d'écran au début de l'article a été intégré en ajoutant simplement le hook suivant à notre code (c'est vraiment tout ce qu'il y a à faire, à part vider les caches, comme toujours avec Drupal) :

/**
 * Implementation of hook_leaflet_map_info().
 */
function mymodule_leaflet_map_info() {
  $default_settings = array(
    'attributionControl' => TRUE,
    'closePopupOnClick'  => TRUE,
    'doubleClickZoom'    => TRUE,
    'dragging'           => TRUE,
    'fadeAnimation'      => TRUE,
    'layerControl'       => FALSE,
    'maxZoom'            => 18,
    'minZoom'            => 1,
    'scrollWheelZoom'    => TRUE,
    'touchZoom'          => TRUE,
    'trackResize'        => TRUE,
    // Don't specify, if you want to use Auto-box.
    // 'zoom'               =>  2,
    'zoomAnimation'      => TRUE,
    'zoomControl'        => TRUE,
  );
  $map_info = array();
  $map_info['Stamen Toner Lite'] = array(
    'label' => 'Stamen Toner Lite',
    'description' => 'Stamen Toner Lite',
    'settings' => $default_settings,
    'layers' => array(
      'layer' => array(
        'urlTemplate' => '//{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png',
        'options' => array(
          'attribution' => 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.',
          'detectRetina' => TRUE,
        ),
      ),
    ),                                                                                                                                               
  );
  return $map_info;
}

Si vous cherchez d'autres exemples de fonds utilisables, vous pouvez regarder dans le module Leaflet More Maps qui en contient de très nombreux. Ici, nous avons fait le choix de la performance en n'ajoutant pas un module supplémentaire au site.

Marqueurs

Les marqueurs standard de Leaflet n'étant pas du tout en accord avec la charte graphique du site, nous avons simplement dans Views changé les options pour utiliser des marqueurs "divIcon" (permettant de les thémer par CSS) en utilisant en plus l'option "Ajouter une classe CSS" et en utilisant le champ taxonomie de notre contenu (qui nous permet de changer la couleur du marqueur selon le type de partenaire).

Ajout de la légende

La légende est alors une simple liste HTML reprenant les classes utilisées dans la carte, positionnée en "pied de page" de la vue.

Réalisation du listing attenant à la carte

Avec ce listing, nous sommes dans notre élement : une simple Vue listant les éléments, groupés par pays. L'unique subtilité est de réaliser ce listing dans la même vue, en utilisant un affichage de type attachment (incorrectement traduit par "fichier attaché" en français), qui permet aux deux affichages de partager les filtres exposés, et donc d'obtenir un rendu dynamique agréable.

Conclusion

La plus longue partie est venue cette fois encore (comme souvent dans la manipulation de données) de l'import et de la transformation de données. En effet, la mise en place de l'affichage se réalise en quelques minutes grâce à la combinaison des modules Views et Leaflet de Drupal. Vous pouvez d'ailleurs consulter le résultat final en ligne sur le site de TBS Education.

 

Si vous rencontrez des difficultés dans la mise en place de ce type de page, venez bénéficiez de nos formations à la construction de site Drupal ou à la mise en place de portail géographiques avec Leaflet.

Actualités en lien

Image
Encart article DrupalCamp 2024
06/03/2024

Makina Corpus, parte­naire de la Drupal­Camp 2024

Nous sommes fiers d’an­non­cer que Makina Corpus est le spon­sor de la Drupal­Camp à Rennes. Notre expert vous y propose une confé­rence « migrer de Drupal 7 à Drupal 10 ».

Voir l'article
Image
Capture d'une partie de carte montrant un réseau de voies sur un fond de carte sombre. Au centre, une popup affiche les information de l'un des tronçons du réseau.
28/02/2024

Géné­rer un fichier PMTiles avec Tippe­ca­noe

Exemple de géné­ra­tion et d’af­fi­chage d’un jeu de tuiles vecto­rielles en PMTiles à partir de données publiques.

Voir l'article
Image
Encart article Protomaps : Illustration d'une portion de pyramide de tuiles
14/02/2024

Protomaps, stockez vos pyramides de tuiles plus simplement

Présentation d'un nouveau format de stockage de tuiles cartographiques

Voir l'article

Inscription à la newsletter

Nous vous avons convaincus