Affichage de l'itinéraire
JaVelo – étape 9
1. Introduction
Cette neuvième étape a pour but d'écrire les classes permettant de gérer l'affichage de l'itinéraire sur le fond de carte.
2. Concepts
Dans l'interface de JaVelo, l'itinéraire est représenté par une ligne rouge reliant les points de passage entre eux. Une position le long de cet itinéraire peut de plus être mise en évidence par un disque blanc à bord rouge.
Ces deux éléments sont visibles dans la copie d'écran ci-dessous qui montre un itinéraire allant de l'EPFL à Sauvabelin en passant par Lutry. La position située à 1000 m le long de l'itinéraire est mise en évidence.
3. Mise en œuvre Java
3.1. Classe RouteBean
La classe RouteBean
du sous-paquetage gui
, publique et finale, est un bean JavaFX regroupant les propriétés relatives aux points de passage et à l'itinéraire correspondant. Son unique constructeur public prend en argument un calculateur d'itinéraire, de type RouteComputer
, utilisé pour déterminer le meilleur itinéraire reliant deux points de passage.
Les propriétés publiques de RouteBean
sont :
- la liste (observable) des points de passage, nommée p. ex.
waypoints
, de typeObservableList<Waypoint>
, - l'itinéraire permettant de relier les points de passage, nommé p. ex.
route
, qui est en lecture seule et donc de typeReadOnlyObjectProperty<Route>
, - la position mise en évidence, nommée p. ex.
highlightedPosition
, de typeDoubleProperty
, - le profil de l'itinéraire, nommé p. ex.
elevationProfile
, qui est en lecture seule et donc de typeReadOnlyObjectProperty<ElevationProfile>
.
Les seules propriétés modifiables depuis l'extérieur sont la liste des points de passage et la position mise en évidence. Les deux autres propriétés — l'itinéraire et son profil — sont en lecture seule car leur valeur est déterminée par la liste des points de passage.
Lorsque la liste des points de passage ne contient pas au moins 2 éléments, ou s'il existe au moins une paire de points de passage entre lesquels aucun itinéraire ne peut être trouvé, alors ni l'itinéraire ni son profil n'existent, et les propriétés correspondantes contiennent null
.
La propriété contenant la position mise en évidence contient soit un nombre valide donnant cette position — exprimée en mètres depuis le départ de l'itinéraire — soit NaN
dans le cas où aucune position ne doit être mise en évidence.
Comme tout bean JavaFX, RouteBean
devrait normalement offrir plusieurs méthodes publiques pour chacune de ses propriétés. Par exemple, les méthodes suivantes devraient être fournies pour celle contenant la position mise en évidence :
- une méthode nommée
highlightedPositionProperty
, retournant la propriété elle-même, de typeDoubleProperty
; - une méthode nommée
highlightedPosition
, retournant le contenu de la propriété, de typedouble
; - une méthode nommée
setHighlightedPosition
, prenant une valeur de typedouble
et la stockant dans la propriété.
Si nous suivions à la lettre les conventions des beans JavaFX, toutes ces méthodes devraient être définies pour chacune des propriétés, à l'exception de la dernière méthode qui ne devrait pas l'être pour les propriétés accessibles en lecture seule. Toutefois, vous verrez que nous n'utiliserons pas la totalité de ces méthodes dans le projet, et nous vous conseillons donc de les ajouter à votre bean au fur et à mesure que vous constatez leur utilité.
3.1.1. Conseils de programmation
- Propriétés en lecture seule
Les propriétés contenant l'itinéraire et son profil ne doivent être accessibles qu'en lecture seule depuis l'extérieur du bean, car leur valeur est déterminée par la liste des points de passage.
Toutefois, le bean lui-même doit pouvoir modifier le contenu de ces propriétés, et doit donc les stocker sous la forme d'attributs de type
ObjectProperty<…>
, et non pasReadOnlyObjectProperty<…>
.Cela n'est toutefois pas un problème, car
ObjectProperty<…>
est un sous-type deReadOnlyObjectProperty<…>
, et il est donc possible de :- stocker la propriété dans un attribut de type
ObjectProperty<…>
, - fournir une méthode d'accès à la propriété qui la retourne sous la forme d'une valeur de type
ReadOnlyObjectProperty<…>
.
Bien entendu, rien n'empêche un utilisateur du bean de contourner cette protection en effectuant un transtypage de la valeur retournée par la méthode d'accès vers le type
ObjectProperty<…>
, puis en modifiant la valeur contenue dans la propriété. Cela n'est toutefois pas important, notre but ici étant de nous prévenir contre les erreurs d'inattention, et pas de nous protéger contre un adversaire mal intentionné. - stocker la propriété dans un attribut de type
- Calcul de l'itinéraire
L'itinéraire et son profil doivent être recalculés chaque fois que les points de passage changent, ce qui peut se faire facilement en installant un auditeur sur la liste contenant ces derniers. Lors d'un changement, le meilleur itinéraire (simple) reliant chaque point de passage à son successeur est déterminé, et ces itinéraires sont combinés en un unique itinéraire multiple.
Le calcul d'un itinéraire étant une opération coûteuse, il est toutefois important d'éviter le recalcul d'itinéraires déjà calculés. Pour ce faire, nous vous conseillons d'utiliser un petit cache mémoire représenté par une table associant à une paire de nœuds le meilleur itinéraire (simple) les reliant.
Chaque fois qu'un itinéraire (simple) doit être calculé, la table est consultée, et si elle contient l'itinéraire, il en est simplement extrait. Sinon, il est calculé au moyen du calculateur d'itinéraire, et le résultat inséré dans la table.
Bien entendu, il ne faut pas que la table grossisse de manière incontrôlée, et nous vous conseillons donc soit de n'y stocker que les itinéraires (simples) correspondant à l'itinéraire (multiple) courant, soit de limiter sa taille en procédant de la même manière que pour le cache mémoire des tuiles.
- Calcul du profil
Le profil de l'itinéraire est bien entendu calculé au moyen de la classe
ElevationProfileComputer
écrite à l'étape 5. Afin d'obtenir un profil détaillé mais de taille raisonnable, la distance maximale entre deux échantillons que nous utiliserons est de 5 mètres.
3.2. Classe RouteManager
La classe RouteManager
du sous-paquetage gui
, publique et finale, gère l'affichage de l'itinéraire et (une partie de) l'interaction avec lui. Elle possède un unique constructeur public prenant en arguments :
- le bean de l'itinéraire,
- une propriété JavaFX, en lecture seule, contenant les paramètres de la carte affichée,
- un « consommateur d'erreurs » permettant de signaler une erreur.
En plus de ce constructeur public, RouteManager
n'offre qu'une seule méthode publique, nommée p. ex. pane
, retournant le panneau JavaFX contenant la ligne représentant l'itinéraire et le disque de mise en évidence.
3.2.1. Conseils de programmation
- Hiérarchie JavaFX
L'itinéraire lui-même est représenté par une instance de
Polyline
portant l'identitéroute
, qu'on lui attache au moyen de la méthodesetId
.Polyline
est la classe JavaFX représentant ce que l'on nomme parfois une polyligne, c.-à-d. une suite de segments de droite reliés entre eux. Une polyligne est caractérisée par une séquence de points, qui ici sont simplement les points aux extrémités des arêtes de l'itinéraire.Le disque représentant la position mise en évidence est représenté quant à lui par une instance de
Circle
d'un rayon de 5 unités et portant l'identitéhighlight
.Ces deux éléments sont les seuls enfants du panneau contenant l'itinéraire, qui est une instance de
Pane
. Comme celui contenant les points de passage, ce panneau doit être configuré de manière à ne pas bloquer les événements des nœuds se trouvant derrière lui, en appelant la méthodesetPickOnBounds
avecfalse
en argument.Il faut noter que la polyligne et le disque sont toujours présents dans le graphe de scène, mais il ne sont pas toujours visible. Par exemple, si aucun itinéraire n'existe — c.-à-d. si la propriété
route
du bean contientnull
— alors ni l'un ni l'autre ne sont visibles. - Positionnement de l'itinéraire
Afin de ne pas devoir recréer la totalité de la polyligne représentant l'itinéraire à chaque glissement de la carte, il convient de choisir judicieusement le système de coordonnées dans lequel les positions de ses points sont exprimées.
Nous vous conseillons d'utiliser pour cela le système de coordonnées de l'image de la carte au niveau de zoom courant. Cela fait, pour que la polyligne apparaisse à la bonne position à l'écran, il suffit de la placer en changeant ses propriétés
layoutX
etlayoutY
en tenant compte des coordonnées du coin haut-gauche de la portion de la carte visible. - Gestion des événements
RouteManager
ne doit détecter et gérer qu'un seul type d'événement, le clic sur le disque représentant la position mise en évidence, qui provoque l'ajout d'un nouveau point de passage. Ce point de passage est situé à l'endroit du clic, et le nœud qui lui est associé est le nœud de l'itinéraire le plus proche de la position mise en évidence. S'il existe déjà un point de passage pour ce nœud, alors aucun point n'est ajouté, et l'erreur suivante est signalée :Un point de passage est déjà présent à cet endroit !
Notez que pour déterminer les coordonnées du clic, vous ne pouvez pas comme d'habitude utiliser uniquement les méthodes
getX
etgetY
de l'événement souris, car les valeurs qu'elles retournent sont exprimées dans le système de coordonnées du nœud source, ici le disque représentant la position mise en évidence. Il faut donc transformer ces coordonnées dans le système du panneau contenant ce disque, ce qui se fait facilement au moyen de la méthodelocalToParent
.En plus de ce gestionnaire d'événements,
RouteManager
doit mettre en place des auditeurs JavaFX afin de :- positionner et/ou rendre (in)visible le disque indiquant la position mise en évidence lorsque celle-ci change, lorsque la route change, ou lorsque les paramètres de la carte changent,
- reconstruire totalement et/ou rendre (in)visible la polyligne représentant l'itinéraire lorsque ce dernier change, ou lorsque le niveau de zoom de la carte change,
- repositionner — sans la reconstruire — la polyligne représentant l'itinéraire lorsque la carte a été glissée mais que son niveau de zoom n'a pas changé.
Pour différencier les deux derniers cas, souvenez-vous que les auditeurs attachés au moyen de
addListener
reçoivent trois arguments qui sont, dans l'ordre : la propriété qui a changé, son ancienne valeur, et sa nouvelle valeur. Il est donc facile d'obtenir les anciens et nouveaux paramètres de la carte afin de déterminer si le niveau de zoom a changé ou non.La visibilité d'un nœud JavaFX est déterminée par sa propriété
visible
, qui peut être changée au moyen de la méthodesetVisible
.
3.3. Tests
Pour tester cette étape, vous pouvez augmenter le programme de test de l'étape précédente afin de :
- créer une instance de
RouteBean
, - remplacer la liste de points de passage (
waypoints
) par celle de cette instance deRouteBean
, - forcer la valeur de la propriété
highlightedPosition
de ce bean à une valeur constante quelconque, p. ex. 1000 m, - créer une instance de
RouteManager
, - ajouter le panneau de cette instance de
RouteManager
comme dernier fils de laStackPane
nomméemainPane
.
Une fois ces changements effectués, votre programme de test devrait se comporter comme illustré dans la vidéo ci-dessous.
4. Résumé
Pour cette étape, vous devez :
- écrire les classes
RouteBean
etRouteManager
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.