Fond de carte OpenStreetMap

JaVelo – étape 7

1. Introduction

Cette étape a pour but principal d'écrire deux classes : une première permettant de télécharger le fond de carte depuis les serveurs OpenStreetMap ; et une seconde permettant de produire un fichier au format GPX à partir d'un itinéraire.

2. Concepts

2.1. Fond de carte OpenStreetMap

Comme l'illustre la copie d'écran de la figure 1 ci-dessous, l'interface graphique de JaVelo montre une carte sur laquelle il est possible de placer des points de passage. Une fois ceux-ci placés, le meilleur itinéraire les reliant est calculé et affiché sur la carte, en rouge.

javelo-gui;64.png
Figure 1 : L'interface graphique de JaVelo

L'image de la carte est celle d'OpenStreetMap, visible également sur le site Web principal du projet. Afin de l'afficher dans l'interface, JaVelo la télécharge directement depuis les serveurs mis à disposition par le projet OpenStreetMap.

Ce téléchargement est moins simple à effectuer qu'on pourrait le penser, car l'image constituant la carte n'est pas disponible en un seul morceau, mais est découpée en petites images — nommées tuiles — qui doivent être téléchargées individuellement puis assemblées.

2.2. Tuiles OpenStreetMap

Comme nous l'avons vu à l'étape 2, la carte OSM au niveau de zoom \(z\) est une image carrée de \(2^{z + 8}\) pixels de côté représentant la quasi-totalité de la planète — seules les latitudes au-delà de ±85° n'y figurent pas.

Aux niveaux de zoom faibles (0 à 4 environ), cette image est de taille raisonnable. Toutefois, aux niveaux de zoom élevés, elle devient énorme. Par exemple, au niveau 19, le nombre total de pixels dans l'image vaut :

\[ \left(2^{19 + 8}\right)^2 = 2^{54} = 18\,014\,398\,509\,481\,984 \approx 18\cdot 10^{15} \]

soit plus de 18 pétapixels.

Sachant que chacun de ces pixels est représenté par une valeur de 32 bits (4 octets) contenant sa couleur empaquetée, l'image totale a une taille de 72 pétaoctets, ce qui est conséquent. Pour donner un ordre de grandeur, la capacité du disque SSD d'un bon ordinateur portable actuel est d'environ 1 téraoctets, et il en faudrait donc 72 000 pour stocker l'image de la carte OSM au niveau 19.

Il n'est donc clairement pas réaliste de représenter la carte OSM à un niveau de zoom donné au moyen d'une seule image. Au lieu de cela, l'image est découpée en petites images carrées nommées tuiles (tiles), et ces tuiles sont ensuite assemblées pour obtenir l'image correspondant à une zone du monde donnée.

Les tuiles OSM font 256 pixels de côté, ce qui se trouve être la taille de la carte entière au niveau de zoom 0. Cette carte-là est donc constituée d'une seule tuile. Au niveau de zoom 1, la carte est constituée de 4 tuiles ; au niveau de zoom 2, de 16 tuiles ; et ainsi de suite. Les tuiles sont indexées par deux index, (X, Y), la tuile du coin en haut à gauche ayant l'index (0, 0). Ces conventions sont illustrées dans l'image ci-dessous, qui montre les quatre tuiles OSM formant la carte au niveau de zoom 1, avec leurs index.

osm-tiles-z1;16.png
Figure 2 : Les quatre tuiles formant la carte OSM au niveau de zoom 1

À la §2.3.1 de l'étape 2, nous avons décrit le système de coordonnées utilisé pour désigner la position d'un point sur l'image d'une carte. Pour un niveau de zoom \(z\) donné, il existe bien entendu une correspondance directe entre les coordonnées \((x_z, y_z)\) d'un point dans ce système de coordonnées et les index \((X_z, Y_z)\) de la tuile le contenant, exprimée par les équations suivantes :

\[ X_z = \left\lfloor\frac{x_z}{256}\right\rfloor\hspace{2em} Y_z = \left\lfloor\frac{y_z}{256}\right\rfloor \]

Ainsi, nous avions vu à l'étape 2 que le Chêne de Napoléon a les coordonnées WebMercator (arrondies) suivantes au niveau de zoom 19 :

\[x_{19} = 69\,561\,722\hspace{2em} y_{19} = 47\,468\,099 \]

Il en découle que, au niveau de zoom 19, les index de la tuile le contenant sont :

\begin{align} X_{19} &= \left\lfloor\frac{69\,561\,722}{256}\right\rfloor = 271\,725\\[0.5em] Y_{19} &= \left\lfloor\frac{47\,468\,099}{256}\right\rfloor = 185\,422 \end{align}

Cette tuile est visible ci-dessous.

185422.png
Figure 3 : La tuile OSM d'index (271725, 185422) au niveau de zoom 19

2.3. Serveur de tuiles

Les tuiles OpenStreetMap sont stockées sur des ordinateurs gérés par le projet, que l'on nomme serveurs de tuiles (tile servers). Ces ordinateurs sont connectés à Internet et offrent la possibilité de télécharger les tuiles au moyen du protocole HTTPS — le protocole du Web.

Le serveur de tuiles OSM principal porte le nom tile.openstreetmap.org, et c'est lui qui fournit, entre autres, les tuiles visibles sur le site OSM principal. Nous l'utiliserons également pour JaVelo. D'autres serveurs de tuile existent, qui donnent souvent accès à des tuiles dessinées dans un style différent de celui utilisé par le serveur principal. Ils sont répertoriés sur la page Tile servers du Wiki OSM.

Pour obtenir l'image d'une tuile, il suffit de déterminer son URL1 et d'effectuer une requête HTTPS au serveur de tuile pour obtenir l'image correspondante. L'URL d'une tuile est déterminée par son niveau de zoom et ses index, et a la forme suivante :

https://tile.openstreetmap.org/<zoom>/<X>/<Y>.png

<zoom> représente le niveau de zoom, <X> l'index X de la tuile et <Y> son index Y. Par exemple, l'URL suivante :

https://tile.openstreetmap.org/19/271725/185422.png

permet d'obtenir l'image de la tuile d'index (271725, 185422) au niveau de zoom 19, présentée à l'image 3. Ces images sont fournies au format PNG, un format d'image fréquemment utilisé sur le Web.

2.4. Cache de tuiles

Grâce aux serveurs de tuiles, il est possible de télécharger très facilement n'importe quelle tuile d'une carte OSM. Ce téléchargement a toutefois un coût, puisqu'il implique le transfert, via Internet, de données situées sur un ordinateur distant.

Pour cette raison, un programme comme JaVelo doit impérativement limiter autant que possible le téléchargement direct de tuiles depuis le serveur. Pour ce faire, lors du premier téléchargement d'une tuile, il la stocke localement — en mémoire et/ou sur disque — afin d'éviter de devoir la télécharger à nouveau la prochaine fois qu'elle sera nécessaire.

L'emplacement dans lequel on stocke ainsi des ressources distantes afin de pouvoir y accéder plus rapidement lors de la prochaine utilisation s'appelle généralement un cache (ou mémoire cache, cache memory) en informatique.

Pour JaVelo, nous stockerons les tuiles OSM dans deux caches de tuiles : un cache mémoire de relativement petite taille, et un cache disque de très grande taille.

2.4.1. Cache mémoire

Un cache mémoire stocke, directement dans la mémoire du programme, des valeurs qui sont chères à obtenir. Dans le cas de JaVelo, le cache de tuile stocke, sous la forme d'objets Java représentant des images, un certain nombre de tuiles obtenues à l'origine depuis le serveur OSM.

Ce cache a une taille limitée, car la mémoire à disposition d'un programme l'est également. Sachant qu'une tuile OSM fait 256 pixels de côté, la mémoire nécessaire au stockage de ses pixels, en octets, est de :

\[ 256^{2} \times 4 = 262\,144 \]

donc environ 260 kilooctets. En gardant 100 tuiles en mémoire, on utilise donc environ 26 mégaoctets, ce qui semble être une limite raisonnable.

2.4.2. Cache disque

Un cache mémoire a l'avantage d'être extrêmement rapide, mais l'inconvénient d'être limité en capacité. Il est donc fréquemment judicieux d'utiliser, en plus d'un cache mémoire de faible capacité, un cache disque de plus grande capacité. Comme son nom l'indique, ce cache stocke ses données sur le « disque » – disque dur ou SSD — de l'ordinateur, dans des fichiers.

Le cache disque de JaVelo stocke les images des tuiles sous la forme de fichiers image au format PNG, puisque c'est celui qui est utilisé par les serveurs de tuile. Les tuiles sont stockées dans des répertoires organisés ainsi :

  • au niveau supérieur de la hiérarchie se trouve un répertoire par niveau de zoom,
  • chacun de ces répertoires contient un sous-répertoire par index X de tuile,
  • chacun de ces sous-répertoires contient un fichier PNG par tuile.

Par exemple, sachant que, au niveau de zoom 19, la tuile contenant le Chêne de Napoléon a les index (271725, 185422), le cache disque stocke son image dans un fichier nommé 185422.png qui se trouve dans un répertoire nommé 271725 situé lui-même dans un répertoire nommé 19. Cette hiérarchie peut être représentée schématiquement ainsi :

19
└── 271725
   └── 185422.png

2.5. Fichiers GPX

À l'étape précédente, nous avons vu comment produire un fichier au format KML contenant un itinéraire calculé par JaVelo. Le format KML n'est toutefois pas le seul format existant pour représenter des données géographiques. En particulier, les récepteurs GPS et de nombreuses applications cartographiques utilisent généralement un autre format, nommé GPX.

Les formats KML et GPX sont tous deux basés sur le format XML (pour extensible markup language), un format textuel extensible permettant de décrire des données organisées en hiérarchie, qu'il convient de décrire brièvement.

2.5.1. Format XML

Un document XML est composé d'éléments (elements), souvent appelés nœuds (nodes), imbriqués les uns dans les autres afin de former une hiérarchie.

Les différents types d'éléments ont un nom qui les identifient. Ils peuvent posséder des attributs (attributes), qui leur sont attachés directement, ainsi que des enfants (children), qui sont d'autres éléments imbriqués dans eux. En plus de ses enfants, un élément peut également contenir du texte brut imbriqué.

Dans un document XML, le début d'un élément est signalé au moyen d'une balise ouvrante (start tag) composée du nom de l'élément entouré de caractères « plus petit » (<) et « plus grand » (>). Les éventuels attributs de l'élément sont placés avant le caractère « plus grand ». Chaque attribut est composé d'un nom et d'une valeur, les deux étant séparés par un signe « égal » (=), et la valeur est placée entre guillemets.

La fin d'un élément est signalé au moyen d'une balise fermante (end tag), qui est presque identique à la balise ouvrante, si ce n'est que le caractère « plus petit » est suivi d'une barre oblique (/). Les enfants d'un élément, y compris l'éventuel texte brut, sont placés entre les deux balises.

Par exemple, l'extrait de document XML suivant :

<rtept lat="46.51907" lon="6.56158">
  <ele>396.88</ele>
</rtept>

est constitué d'un élément rtept doté de deux attributs :

  • lat, qui vaut 46.51907,
  • lon, qui vaut 6.56158,

et d'un enfant. Cet enfant est un élément ele dont le fils est le texte brut 396.88.

Le format XML étant extensible, les types d'éléments utilisables dans un document ne sont pas prédéfinis et peuvent être quelconques. Un fichier GPX est donc un cas particulier de fichier XML utilisant un certain nombre d'éléments qui lui sont propres. Ceux qui nous intéressent sont décrits ci-dessous.

2.5.2. Format GPX

Un document GPX peut contenir un assez grand nombre de types d'éléments différents, mais seule une petite partie d'entre eux nous intéresse ici. Il s'agit de :

gpx
la racine du document, contenant tous les autres éléments,
metadata
contenant les méta-données du document, comme son nom,
name
représentant le nom du document,
rte
représentant un itinéraire,
rtept
représentant un point d'un itinéraire,
ele
représentant l'altitude d'un point.

L'élément rte, qui représente un itinéraire, est constitué d'une séquence de points. Chacun d'entre eux est représenté par un élément rtept comportant deux attributs, lat et lon, donnant la position du points en degrés, dans le système WGS 84. L'élément rtept peut contenir un élément ele donnant l'altitude, en mètres, du point.

Schématiquement, la structure d'un document GPX peut donc être représentée ainsi :

gpx
├── metadata
│  └── name
└── rte
   ├── rtept
   │  └── ele
   ├── rtept
   │  └── ele
   …

L'extrait ci-dessous montre la structure d'un fichier GPX décrivant l'itinéraire allant de l'EPFL à Sauvabelin utilisé en exemple à l'étape 6. Seuls les deux premiers points sont inclus. La toute première ligne donne quelques informations au sujet du format du fichier, et la ligne suivante représente l'élément gpx à la racine du document. Il n'est pas nécessaire de comprendre la signification des attributs qui lui sont attachés — creator, version, xmlns:xsi, etc. Le reste devrait être facilement compréhensible.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<gpx creator="JaVelo"
     version="1.1"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation=
     "http://www.topografix.com/GPX/1/1
      http://www.topografix.com/GPX/1/1/gpx.xsd"
     xmlns="http://www.topografix.com/GPX/1/1">
  <metadata>
    <name>Route JaVelo</name>
  </metadata>
  <rte>
    <rtept lat="46.51907" lon="6.56158">
      <ele>396.88</ele>
    </rtept>
    <rtept lat="46.51887" lon="6.56148">
      <ele>397.67</ele>
    </rtept>
    <!-- … nombreux autres points -->
  </rte>
</gpx>

Les fichiers GPX peuvent être utilisés dans de nombreuses applications cartographiques. Par exemple, la copie d'écran ci-dessous montre ce qu'affiche l'application swisstopo pour iOS lorsqu'on y importe le fichier GPX de l'itinéraire EPFL - Sauvabelin.

javelo-swisstopo-ios.jpg
Figure 4 : Un itinéraire JaVelo sur l'application swisstopo pour iOS

3. Mise en œuvre Java

Cette étape est la première de la seconde partie du projet, durant laquelle vous serez plus libres et moins guidés que durant la première.

En particulier, vous avez maintenant le droit de modifier ou augmenter l'interface publique des classes et interfaces proposées, pour peu bien entendu que vous ayez une bonne raison de le faire. Pour faciliter la correction, nous vous demandons néanmoins de respecter les noms de paquetages et de classes, interfaces, enregistrements et types énumérés donnés.

D'autre part, nous nous attendons à ce que vous lisiez et compreniez la documentation des parties de la bibliothèque Java que vous devez utiliser.

La plupart des classes de cette étape sont à définir dans un nouveau sous-paquetge nommé gui et destiné à contenir la totalité du code lié à l'interface graphique de JaVelo — gui étant l'acronyme de graphical user interface.

Une grande partie du code gérant l'interface graphique utilise la bibliothèque JavaFX, qui ne fait pas partie de la bibliothèque standard Java et doit donc être installée séparément. Pour ce faire, suivez les instructions que nous donnons dans notre guide.

3.1. Enregistrement TileManager.TileId

L'enregistrement TileId, imbriqué dans la classe TileManager décrite à la section suivante, représente l'identité d'une tuile OSM. Il possède trois attributs, qui sont :

  • le niveau de zoom de la tuile,
  • l'index X de la tuile,
  • l'index Y de la tuile.

TileId offre une méthode publique et statique, nommée p. ex. isValid, prenant en argument ces trois attributs (zoom et index X/Y) et retournant vrai si — et seulement si — ils constituent une identité de tuile valide.

3.2. Classe TileManager

La classe TileManager du sous-paquetage gui, publique et finale, représente un gestionnaire de tuiles OSM. Son rôle est d'obtenir les tuiles depuis un serveur de tuile et de les stocker dans un cache mémoire et dans un cache disque.

Son constructeur prend en arguments :

  1. le chemin d'accès au répertoire contenant le cache disque, de type Path,
  2. le nom du serveur de tuile (p.ex. tile.openstreetmap.org).

La seule méthode publique offerte par TileManager, nommée p. ex. imageForTileAt, prend en argument l'identité d'une tuile (de type TileId) et retourne son image (de type Image de la bibliothèque JavaFX).

L'image est cherchée tout d'abord dans le cache mémoire, et si elle s'y trouve, est simplement retournée. Sinon, elle est cherchée dans le cache disque, et si elle s'y trouve, elle est chargée, placée dans le cache mémoire et retournée. Sinon, elle est obtenue depuis le serveur de tuiles, placée dans le cache disque, chargée, placée dans le cache mémoire et enfin retournée.

3.2.1. Conseils de programmation

  1. Limitation de la taille du cache mémoire

    Le cache mémoire doit contenir un maximum de 100 images. Lorsqu'il est plein, il faut donc supprimer l'une des images qu'il contient, mais pas n'importe laquelle ! Il semble en effet plus judicieux de choisir celle utilisée le moins récemment (least-recently used ou LRU en anglais), car c'est probablement la moins utile de toutes.

    La bibliothèque Java offre la classe LinkedHashMap, qui est une table associative similaire à HashMap mais qui offre un constructeur permettant de demander à ce que les éléments soient parcourus du moins récemment accédé au plus récemment accédé. Utilisez-le pour facilement trouver l'élément du cache à supprimer lorsqu'il est plein.

  2. Téléchargement d'une tuile

    Il est très facile d'obtenir un flot d'entrée fournissant les données d'une tuile grâce à la classe URLConnection, comme l'illustre l'extrait de programme suivant :

    URL u = new URL(
      "https://tile.openstreetmap.org/19/271725/185422.png");
    URLConnection c = u.openConnection();
    c.setRequestProperty("User-Agent", "JaVelo");
    InputStream i = c.getInputStream();
    

    Bien entendu, il est impératif de fermer le flot retourné par getInputStream, ce qui n'a pas été fait ci-dessus. Pour cela, utilisez la notation try-with-resource.

    Pour sauvegarder la tuile dans le cache disque, il suffit d'obtenir un flot de sortie écrivant dans le fichier désiré, puis d'utiliser la méthode transferTo pour transférer les données du flot d'entrée vers le flot de sortie.

    Une fois la tuile placée dans le cache disque, il est facile de la charger et d'obtenir l'image JavaFX correspondante grâce au constructeur de Image prenant un flot d'entrée en argument.

    Notez que certaines méthodes de la classe Files pourraient vous être utiles pour gérer le cache disque, en particulier exists et createDirectories.

3.3. Enregistrement MapViewParameters

L'enregistrement MapViewParameters du sous-paquetage gui, public, représente les paramètres du fond de carte présenté dans l'interface graphique. Il possède trois attributs, qui sont :

  • le niveau de zoom,
  • la coordonnée x du coin haut-gauche de la portion de carte affichée,
  • la coordonnée y du coin haut-gauche de la portion de carte affichée.

Les coordonnées x et y sont exprimées dans le système de coordonnées Web Mercator de l'image au niveau de zoom donné. Par exemple, pour la carte affichée dans l'interface de la figure 1, ces trois attributs sont :

  • niveau de zoom : 10,
  • coordonnée x du coin haut-gauche : 135735,
  • coordonnée y du coin haut-gauche : 92327.

La classe PointWebMercator peut être utilisée pour déterminer les coordonnées WGS 84 du coin haut-gauche :

PointWebMercator p = PointWebMercator.of(10, 135735, 92327);
double lon = Math.toDegrees(p.lon()); // ~ 6.40366
double lat = Math.toDegrees(p.lat()); // ~46.88366

Ce point est indiqué par le marqueur bleu sur cette carte, et on peut vérifier visuellement qu'il se trouve bien à la position du coin haut-gauche de la portion de carte affichée dans l'interface de la figure 1.

MapViewParameters offre les méthodes publiques suivantes :

  • une méthode, nommée p. ex. topLeft, qui retourne les coordonnées du coin haut-gauche sous la forme d'un objet de type Point2D — le type utilisé par JavaFX pour représenter les points,
  • une méthode, nommée p. ex. withMinXY, qui retourne une instance de MapViewParameters identique au récepteur, si ce n'est que les coordonnées du coin haut-gauche sont celles passées en arguments à la méthode,
  • une méthode, nommée p. ex. pointAt, qui prend en arguments les coordonnées x et y d'un point, exprimées par rapport au coin haut-gauche de la portion de carte affichée à l'écran, et retourne ce point sous la forme d'une instance de PointWebMercator,
  • deux méthodes, nommées p. ex. viewX et viewY, qui prennent en argument un point Web Mercator et retournent la position x ou y correspondante, exprimée par rapport au coin haut-gauche de la portion de carte affichée à l'écran.

Le but des méthodes pointAt et viewX / viewY, qui sont inverses l'une de l'autre, est de convertir entre le système de coordonnées de l'image de la carte OpenSteetMap et celui de la portion affichée à l'écran.

Par exemple, avec les paramètres donnés plus haut en exemple, la méthode pointAt appliquée aux coordonnées (0, 0) retourne simplement le point Web Mercator correspondant au coin haut-gauche de la portion de carte affichée, c.-à-d. le point p de l'extrait de code. À l'inverse, les méthodes viewX et viewY appliquées à ce point p retournent toutes deux 0.

3.4. Enregistrement Waypoint

L'enregistrement Waypoint du sous-paquetage gui, public, représente un point de passage. Il possède deux attributs :

  • la position du point de passage dans le système de coordonnées suisse,
  • l'identité du nœud JaVelo le plus proche de ce point de passage.

Waypoint ne possède aucune autre méthode publique que celles définies automatiquement par Java pour les enregistrements.

3.5. Classe GpxGenerator

La classe GpxGenerator du sous-paquetage routing, publique et non instanciable, représente un générateur d'itinéraire au format GPX. Elle offre deux méthodes publiques (et statiques) :

  • la première, nommée p. ex. createGpx, qui prend en arguments un itinéraire et le profil de cet itinéraire et retourne le document GPX (de type Document) correspondant,
  • la seconde, nommée p. ex. writeGpx, qui prend en arguments un nom de fichier, un itinéraire et le profil de cet itinéraire et écrit le document GPX correspondant dans le fichier, ou lève IOException en cas d'erreur d'entrée/sortie.

Les document GPX produits par ces deux méthodes doivent avoir les mêmes points que l'itinéraire qui leur est passé, et l'altitude de chacun de ces points doit être obtenue du profil.

3.5.1. Conseils de programmation

Les classes du paquetage javax.xml permettant de travailler avec les documents XML ne sont malheureusement pas très bien conçues, et il en va de même du format XML lui-même. Pour cette raison, nous vous fournissons ci-dessous des exemples de code dont vous pouvez vous inspirer.

  1. Méthode createGpx

    L'extrait de code ci-dessous crée, dans la variable doc, un embryon de document GPX constitué des éléments gpx, metadata et name. Il ne vous reste plus qu'à y ajouter l'élément rte représentant la route, constitué d'éléments rtept possédant un élément ele donnant l'altitude du point.

    Document doc = newDocument(); // voir plus bas
    
    Element root = doc
      .createElementNS("http://www.topografix.com/GPX/1/1",
    		   "gpx");
    doc.appendChild(root);
    
    root.setAttributeNS(
      "http://www.w3.org/2001/XMLSchema-instance",
      "xsi:schemaLocation",
      "http://www.topografix.com/GPX/1/1 "
      + "http://www.topografix.com/GPX/1/1/gpx.xsd");
    root.setAttribute("version", "1.1");
    root.setAttribute("creator", "JaVelo");
    
    Element metadata = doc.createElement("metadata");
    root.appendChild(metadata);
    
    Element name = doc.createElement("name");
    metadata.appendChild(name);
    name.setTextContent("Route JaVelo");
    

    La méthode newDocument utilisée pour créer un nouveau document peut être définie ainsi :

    private static Document newDocument() {
      try {
        return DocumentBuilderFactory
          .newDefaultInstance()
          .newDocumentBuilder()
          .newDocument();
      } catch (ParserConfigurationException e) {
        throw new Error(e); // Should never happen
      }
    }
    

    Notez que cette méthode utilise un bloc try/catch afin de rattraper les exceptions de type ParserConfigurationException et de lever une autre exception de type Error contenant l'exception originale. Cela a été fait car l'exception en question ne devrait jamais être levée (par newDocumentBuilder) étant donné qu'on utilise ici la configuration par défaut, qui doit être valide.

    Le fait de rattraper ces exceptions qui ne devraient jamais être levées en pratique, mais qui sont de type checked, et de les emballer dans une exception (Error) de type unchecked permet d'éviter de devoir annoter inutilement les méthodes utilisant ce code avec des throws. Et si l'exception devait néanmoins être levée pour une raison ou pour une autre, elle ferait planter le programme plutôt que d'être ignorée silencieusement.

  2. Méthode writeGpx

    Une fois le document GPX créé au moyen de la méthode createGpx, il n'est pas non plus très simple de l'écrire dans un fichier car il faut passer par une instance d'une classe Transformer. L'exemple ci-dessous, à compléter, illustre cela en écrivant le document doc dans l'écrivain w.

    Document doc = …;
    Writer w = …;
    
    Transformer transformer = TransformerFactory
      .newDefaultInstance()
      .newTransformer();
    transformer.setOutputProperty(OutputKeys.INDENT, "yes");
    transformer.transform(new DOMSource(doc),
    		      new StreamResult(w));
    

    Notez qu'ici aussi, les méthode newTransformer et transform peuvent lever des exceptions de type checked qui ne devraient jamais être levées en pratique. Utilisez la même technique que ci-dessus pour les « transformer » en exceptions de type Error.

3.6. Tests

À partir de cette étape, aucun fichier de vérification de signatures ne vous est fourni, étant donné que la signature des différentes méthodes n'est plus spécifiée en détail. Pour la même raison, aucun test unitaire ne sera plus fourni à l'avenir, à vous d'écrire les vôtres. Notez que cela est fortement recommandé en général.

Nous vous donnons néanmoins ci-après quelques conseils pour tester certaines parties de cette étape.

3.6.1. Classe TileManager

Pour tester votre gestionnaire de tuiles, vous pouvez l'utiliser pour obtenir la tuile contenant le Chêne de Napoléon et vérifier qu'elle est bien stockée dans le cache disque.

Pour ce faire, il vous faut écrire une toute petite application JavaFX, et comme nous n'avons pas encore examiné cette bibliothèque en cours, nous vous fournissons l'exemple suivant dont vous pouvez vous inspirer :

public final class TestTileManager extends Application {
  public static void main(String[] args) { launch(args); }

  @Override
  public void start(Stage primaryStage) throws Exception {
    TileManager tm = new TileManager(
      Path.of("."), "tile.openstreetmap.org");
    Image tileImage = tm.imageForTileAt(
      new TileManager.TileId(19, 271725, 185422));
    Platform.exit();
  }
}

Une fois cette application exécutée, vous devriez avoir, dans le répertoire de votre projet, un dossier nommé 19, contenant un sous-dossier nommé 271725, contenant un fichier nommé 185422.png. En ouvrant ce fichier, vous devriez voir l'image de la figure 3.

3.6.2. Classe GpxGenerator

Pour voir si les fichiers GPX que vous générez sont valides, vous pouvez utiliser le site GPX file viewer. Lorsque vous y téléchargez un fichier GPX, il affiche à la fois le profil déterminé au moyen des données de Google Maps et le profil contenu dans le fichier GPX lui-même. Les deux devraient être similaires mais ne sont que rarement identiques en raison des données différentes utilisées par Google Maps.

4. Résumé

Pour cette étape, vous devez :

  • écrire les classes TileManager, MapViewParameters, Waypoint et GpxGenerator selon les indications données ci-dessus,
  • tester votre code,
  • documenter la totalité des entités publiques que vous avez définies.

Aucun rendu n'est à faire pour cette étape avant le rendu final. N'oubliez pas de faire régulièrement des copies de sauvegarde de votre travail en suivant nos indications à ce sujet.

Notes de bas de page

1

Une URL, ou adresse Web, est une chaîne de caractères identifiant une ressource (page, image, etc.) sur le Web. Par exemple, l'URL https://www.epfl.ch/ identifie la page principale du site de l'EPFL, https://cs108.epfl.ch/ identifie la page principale de ce cours, etc.