Google fait les cartes, Spring fait le REST

Logo google mapsLa cartographie est maintenant utilisée communément sur le Web. On ne peut plus consulter un site marchand sans pouvoir calculer son itinéraire jusqu’au point de vente, se renseigner sur un pays sans pouvoir explorer sa géographie, parfois même faire une sortie jogging ou vélo sans consulter ultérieurement son parcours sur une carte…

Les deux grands fournisseurs de cartographie sont actuellement Google avec sa Google Maps API et Microsoft avec Bing Maps. La plupart du temps, leur utilisation passe par du code Javascript exécuté côté client. Pourtant, il est parfois nécessaire de faire des traitements de géolocalisation côté serveur. Pour cela, les deux fournisseurs ont prévu une API de type REST. La suite de cet article ne s’intéressera plus qu’au monde “Google” et à son API, capable de générer une réponse de type XML ou JSON.

Be API avec le REST de Google !

Les principes de base

Pas de grosse surprise pour effectuer une requête géographique : on reste sur des principes REST classiques, avec une requête pour effectuer une géolocalisation inversée (à partir d’une adresse postale) du type :

http://maps.googleapis.com/maps/api/geocode/xml?address=90+rue+Baudin+92300+Levallois+Perret&sensor=false

Cette requête, qui peut être jouée directement sur un navigateur, permet de récupérer un message XML du type :

<GeocodeResponse>
  <status>OK</status>
  <result>
    <type>street_address</type>
    <formatted_address>90 Rue Baudin, 92300 Levallois-Perret, France</formatted_address>
    <address_component>
      <long_name>90</long_name>
      <short_name>90</short_name>
      <type>street_number</type>
    </address_component>
    <address_component>
      <long_name>Rue Baudin</long_name>
      <short_name>Rue Baudin</short_name>
      <type>route</type>
    </address_component>
    <address_component>
      <long_name>Levallois-Perret</long_name>
      <short_name>Levallois-Perret</short_name>
      <type>locality</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>Hauts-de-Seine</long_name>
      <short_name>92</short_name>
      <type>administrative_area_level_2</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>&#xCE;le-de-France</long_name>
      <short_name>IdF</short_name>
      <type>administrative_area_level_1</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>France</long_name>
      <short_name>FR</short_name>
      <type>country</type>
      <type>political</type>
    </address_component>
    <address_component>
      <long_name>92300</long_name>
      <short_name>92300</short_name>
      <type>postal_code</type>
    </address_component>
    <geometry>
      <location>
        <lat>48.8989222</lat>
        <lng>2.2877605</lng>
      </location>
      <location_type>RANGE_INTERPOLATED</location_type>
      <viewport>
        <southwest>
          <lat>48.8975800</lat>
          <lng>2.2864066</lng>
        </southwest>
        <northeast>
          <lat>48.9002780</lat>
          <lng>2.2891046</lng>
        </northeast>
      </viewport>
      <bounds>
        <southwest>
          <lat>48.8989222</lat>
          <lng>2.2877507</lng>
        </southwest>
        <northeast>
          <lat>48.8989358</lat>
          <lng>2.2877605</lng>
        </northeast>
      </bounds>
    </geometry>
  </result>
</GeocodeResponse>

Le message résultat est immédiatement compréhensive à un esprit humain. C’est sans doute pour cela qu’il n’est absolument pas documenté par Google, qui ne fournit pas même un XSD…  🙁

REST ou JSON ?

Google préconise plutôt l’utilisation du service JSON.

JSON est clairement une notation beaucoup plus compacte que le verbeux XML et donc plus efficace. Cependant, elle n’offre pas tout l’attirail de XML, notamment en ce qui concerne la description des signatures de service et le contrôle des formats des messages. J’ai toujours tendance à privilégier XML pour les échanges de serveur à serveur et JSON pour les interactions internes à un système (interrogation AJAX d’un client web sur son serveur par exemple).

On est ici sur des échanges serveur à serveur, donc va pour le XML !

Comment conjuguer REST et Spring

La classe RestTemplate

Spring fournit la classe RestTemplate, qui elle-même fournit des méthodes de haut niveau pour invoquer les 6 méthodes principales du protocole HTTP. Elle simplifie énormément la programmation de la partie cliente d’un appel REST en gérant toute la plomberie habituelle liée à l’utilisation du protocole HTTP et la conversion des objets Java.

La lecture de la doc Spring conduit vite à la constatation que l’appel du service Google maps précédent peut se faire sous la forme :

	restTemplate.getForObject(
		"http://maps.googleapis.com/maps/api/geocode/xml?address={address}&sensor=false",
		String.class,"90 rue Baudin, 92300 Levallois Perret");

L’encapsulation de JAXB 2

Le code suivant est simple et de bon aloi, mais ne résout pas la problématique du mapping du résultat (ici un flux XML) vers le graphe d’objets Java.

Heureusement, et c’est là qu’entre la magie du RESTTemplate, il peut être configuré à l’aide d’un marshaller fourni par Spring :

org.springframework.oxm.jaxb.Jaxb2Marshaller

Rien de plus à ajouter que de référencer cette classe, à la fois comme marshaller et comme unmarshaller pour retrouver son modèle métier directement mappé sur le flux XML ! C’est de la pure magie, mais c’est bon !

Un petit projet vaut mieux qu’un long post de blog…

La Mise en place Maven

Il n’existe pas à ma connaissance d’archetype tout prêt pour faire du Spring. Pour notre exemple, nous partirons donc d’un archetype simple, en tapant la commande suivante :

 mvn archetype:generate \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DgroupId=fr.ippon.gmap \
  -DartifactId=restgmap \
  -Dversion=1.0-SNAPSHOT

On obtient ainsi un projet restgmap, dans lequel nous allons nous empresser de modifer le fichier pom.xml pour notamment ajouter les dépendances à Spring :

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.log4j</groupId>
      <artifactId>com.springsource.org.apache.log4j</artifactId>
      <version>1.2.15</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>com.springsource.org.apache.commons.logging</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-oxm</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>org.springframework.web</artifactId>
      <version>${spring.version}</version>
    </dependency>
  </dependencies>
  <properties>
    <spring.version>3.0.5.RELEASE</spring.version>
  </properties>
  <repositories>
    <repository>
      <id>com.springsource.repository.bundles.release</id>
      <name>SpringSource Enterprise Bundle Repository - SpringSource Bundle Releases</name>
      <url>http://repository.springsource.com/maven/bundles/release</url>
    </repository>
    <repository>
      <id>com.springsource.repository.bundles.external</id>
      <name>SpringSource Enterprise Bundle Repository - External Bundle Releases</name>
      <url>http://repository.springsource.com/maven/bundles/external</url>
    </repository>
  </repositories>

La création du modèle

L’analyse de la réponse fournie lors d’une interrogation du service de géolocalisation permet assez facilement de construire un modèle objet comme suit :

Modèle de géolocalisation

Ce modèle en poche, les annotations JAXB se font très facilement même s’il aurait été plus simple de faire tout cela à partir d’une XSD, directement fournie par Google…

On a par exemple la classe Geometry comme suit :

package fr.ippon.gmap.model.gmap;

import javax.xml.bind.annotation.XmlElement;

public class Geometry {
    @XmlElement
    private GeoPosition location;
    @XmlElement
    private Viewport viewport;
    @XmlElement
    private Viewport bounds;

    public GeoPosition getLocation() {
        return location;
    }
}

Pour avoir toutes les informations sur les classes annotées, vous pouvez vous référerez au code qui est disponible sur GitHub à l’adresse : https://github.com/bpinel/restgmap

Le service et son test unitaire

Le reste du code est tout ce qu’il y a de plus standard. Et je vous laisse découvrir le service et les tests unitaires dans le projet git.

Tweet about this on TwitterShare on FacebookGoogle+Share on LinkedIn

3 réflexions au sujet de « Google fait les cartes, Spring fait le REST »

  1. Cela fait plusieurs année que j’ai décidé de ne plus passer par Google pour geocoder / reverse geocoder et j’ai développer mon propre framework avec mes APIs Rest. il se nomme Gisgraphy et le tout est open source et gratuit. j’utilise les données de Openstreetmap et Geonames. je dois dire qu’après 5 années de recherche les résultats commencent a être vraiment pertinents.en espérant que cela aide quelqu’un 😉

  2. Bonjour,
    Je ne connaissais pas Gisgraphy, mais le projet a l’air bien intéressant. D’autant plus que sur un plan autre que technique, la dépendance à Google peut poser problème dans certains pays pas trop ouvert…

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


*