Paramètres utilisateur
Etape 8

1 Introduction

Le but de cette étape est d'écrire le code permettant de gérer le paramétrage du panorama par l'utilisateur du programme final, qui se fera au moyen de l'interface graphique.

Le problème du paramétrage du panorama ayant déjà été traité à l'étape 5, il peut sembler curieux de devoir lui consacrer une seconde étape. Cela est dû principalement à deux raisons :

  1. les unités utilisées dans le programme (radians et mètres), dont l'utilisation facilite souvent les formules, ne sont pas toujours adaptées à l'utilisateur,
  2. les plages de valeurs considérées comme acceptables pour les différents paramètres ont intérêt à être réduites dans l'interface graphique, p.ex. pour limiter la position de l'observateur à la petite partie de la Terre pour laquelle nous disposons de modèles du terrain.

Finalement, une dernière raison, plus technique et peut-être moins facile à comprendre, est que nous aurons besoin ultérieurement d'une notion d'égalité pour les paramètres du panorama, afin de déterminer si deux ensembles de paramètres sont égaux. Or la classe PanoramaParameters écrite à l'étape 5 ne dispose pas d'une notion d'égalité, et en définir une est délicat en raison de la précision élevée avec laquelle la position de l'observateur est représentée. Pour nos besoins, il est en effet inutile de considérer comme différentes des positions distantes de quelques mètres seulement.

1.1 Paramétrage du panorama

Lors de l'étape 5, les sept paramètres nécessaires au calcul d'un panorama ont été présentés. Pour mémoire, il s'agit de :

  1. la position de l'observateur \(O\),
  2. l'altitude de l'observateur \(e\),
  3. l'azimut du centre du panorama \(a\),
  4. l'angle de vue horizontal \(v_h\),
  5. la distance maximale de visibilité \(d\),
  6. la largeur du panorama \(w\),
  7. la hauteur du panorama \(h\).

Tous ces paramètres seront modifiables au travers de l'interface graphique du programme final, au détail près que la position de l'observateur sera en réalité décrite par deux paramètres distincts : la longitude et la latitude de l'observateur.

Un paramètre supplémentaire sera ajouté à cette liste : le facteur de suréchantillonnage, décrit à la section 1.2 plus bas. Comme ce facteur n'influence le calcul du panorama que de manière indirecte, il n'a pas été nécessaire de l'inclure dans les paramètres définis à l'étape 5.

Bien entendu, les valeurs acceptables pour ces différents paramètres sont limitées. Par exemple, l'angle de vue horizontal ne peut être supérieur à 360°. De plus, même si notre programme gère théoriquement une position arbitraire de l'observateur, il est préférable de ne laisser l'utilisateur choisir qu'une position qui soit dans les limites des modèles du terrain à notre disposition.

Finalement, les unités à utiliser pour ces différents paramètres varient. S'il est par exemple sensé de permettre à l'utilisateur de spécifier l'atitude d'observation en mètres, la distance maximale de visibilité s'exprime plus naturellement en kilomètres. De même, des angles exprimés en degrés sont beaucoup plus naturels pour un être humain que des angles exprimés en radians.

La table ci-dessous résume les neuf paramètres décrivant un panorama et que l'interface graphique permettra de modifier, ainsi que leurs unités et valeurs limites autorisées, choisies en fonction des considérations qui précèdent.

Paramètre Unité Min Max
Longitude de l'observateur degrés (°) 6 12
Latitude de l'observateur degrés (°) 45 48
Altitude de l'observateur mètres (m) 300 10 000
Azimut central degrés (°) 0 359
Angle de vue horizontal degrés (°) 1 360
Distance maximale kilomètres (km) 10 600
Largeur du panorama échantillons 30 16 000
Hauteur du panorama échantillons 10 4 000
Exposant de suréchantillonnage 0 2

Tous ces paramètres sont représentés par des valeurs entières, ce qui offre pour presque tous une précision suffisante à nos besoins. La seule exception est bien entendu la position de l'observateur, pour laquelle une précision d'un degré est nettement insuffisante — pour mémoire, un degré correspond à une distance d'environ 100 km à l'équateur.

Pour résoudre ce problème, nous stockerons la longitude et la latitude de l'observateur en dix-millième de degrés (1°/10 000), toujours sous forme entière. Dans l'interface utilisateur, ces valeurs seront toutefois présentées en degrés, avec exactement 4 décimales.

Cette petite astuce nous permet d'obtenir à la fois la précision souhaitée pour la position de l'observateur (environ 10 m), et une représentation entière de tous les paramètres.

1.1.1 Angle de vue vertical

Comme dit à l'étape 5, en plus des paramètres explicites listés ci-dessus, un panorama possède un paramètre implicite : l'angle de vue vertical \(v_v\). Bien entendu, cet angle doit lui aussi être compris dans des limites que nous jugeons raisonnables, fixées ici à l'intervalle ]0°, 170°].

Pour mémoire, l'angle de vue vertical \(v_v\) peut être déterminé à partir de l'angle de vue horizontal \(v_h\) et de la largeur \(w\) et de la hauteur \(h\) du panorama, au moyen de la formule suivante :

\[ v_v = \frac{h - 1}{w - 1}\,v_h \]

Etant données les limites imposées aux paramètres \(h\), \(w\) et \(v_h\), il est clair que \(v_v\) sera toujours strictement supérieur à 0. Par contre, il n'est pas garanti que \(v_v\) soit inférieur ou égal à 170°.

Dès lors, il est dans certains cas nécessaire d'ajuster un (ou plusieurs) des paramètres \(h\), \(w\) ou \(v_h\) pour garantir que \(v_v\) soit inférieur ou égal à 170°. Pour ce projet, nous avons choisi d'adapter la hauteur \(h\) du panorama, en la réduisant au besoin pour que \(v_v\) soit dans les limites acceptables. En d'autres termes, la hauteur \(h\) possède deux valeurs maximales : la première est fixe, et est donnée dans la table ci-dessus, tandis que la seconde varie et dépend de la valeur des autres paramètres. Bien entendu, la hauteur doit toujours être plus petite que le minimum de ces deux limites.

Pour déterminer la limite variable de la hauteur, il suffit de récrire l'égalité suivante pour l'exprimer en fonction de \(h\) :

\[ v_v = \frac{h - 1}{w - 1}\,v_h \le 170 \]

On obtient :

\[ h \le 170\,\frac{w - 1}{v_h} + 1 \]

Peut-on être sûr que cette limite maximale de la hauteur sera toujours supérieure à sa limite minimale, donnée dans la table plus haut et valant 10 ? Pour le vérifier, il suffit de substituer dans cette inégalité les valeurs limites de \(w\) et \(v_h\) qui minimisent sa partie de droite. On obtient alors :

\[ h \le 170\,\frac{30 - 1}{360} + 1 \approx 14.69 \]

On en conclut que, même dans le pire cas, la hauteur maximale sera plus grande que la hauteur minimale. Notez que c'est la raison pour laquelle la largeur minimale de l'image a été fixée à une valeur différente (30) que la hauteur minimale (10).

1.2 Suréchantillonnage

Jusqu'à présent, nous avons fait l'hypothèse qu'à chaque point d'un panorama correspond exactement un pixel de l'image de ce même panorama. Toutefois, pour obtenir des images de meilleure qualité, il est préférable de combiner plusieurs points du panorama pour produire un seul pixel de l'image finale, technique que l'on nomme suréchantillonnage (supersampling).

Il existe de nombreuses techniques de suréchantillonnage, mais celle que nous utiliserons dans ce projet est la plus simple d'entre elles. Elle consiste à calculer et dessiner un panorama dont les dimensions (largeur et hauteur) sont le double ou le quadruple de celles de l'image finale, puis à redimensionner l'image obtenue. Lors de ce redimensionnement, la couleur de chaque pixel est obtenu par moyenne de la couleur de 4 ou 16 pixels voisins, ce qui produit une image de meilleure qualité.

Le degré de suréchantillonnage est spécifié par un paramètre nommé \(s\) et valant 0, 1 ou 2. Ce paramètre lie la largeur \(w_p\) et la hauteur \(h_p\) du panorama calculé et la largeur \(w_i\) et la hauteur \(h_i\) de l'image finale de la manière suivante :

\begin{align*} w_p &= 2^s\, w_i\\ h_p &= 2^s\, h_i \end{align*}

L'image ci-dessous illustre l'effet du suréchantillonnage sur un extrait du panorama de l'introduction au projet. Cet effet est particulièrement visible aux alentours des crètes des montagnes, que le suréchantillonnage rend plus lisses.

supersampling.png

Figure 1 : Effet du suréchantillonnage (de gauche à droite : degré 0, 1 et 2)

1.3 Panoramas prédéfinis

Deux panoramas ont déjà été présentés dans les étapes précédentes, l'un des Alpes vues du Jura, l'autre du Niesen vu du lac de Thoune. La table ci-dessous rappelle leurs paramètres principaux (attention, l'angle de vue horizontal pour le Niesen a été augmenté !) et ceux de quatre autres panoramas prédéfinis que nous utiliserons ultérieurement.

Nom Lon. (°) Lat. (°) Alt. (m) Az. (°) AVH (°)
Niesen 7.6500 46.7300 600 180 110
Alpes du Jura 6.8087 47.0085 1380 162 27
Mont Racine 6.8200 47.0200 1500 135 45
Finsteraarhorn 8.1260 46.5374 4300 205 20
Tour de Sauvabelin 6.6385 46.5353 700 135 100
Plage du Pélican 6.5728 46.5132 380 135 60

Dans cette table, Az. est l'azimut \(a\) et AVH l'angle de vue horizontal \(v_h\).

2 Mise en œuvre Java

Comme celui de l'étape précédente, tout le code de cette étape fait partie du paquetage ch.epfl.alpano.gui.

2.1 Énumération UserParameter

L'énumération nommée p.ex. UserParameter, publique, énumère les neuf paramètres utilisateur mentionnés plus haut :

  1. longitude de l'observateur (OBSERVER_LONGITUDE),
  2. latitude de l'observateur (OBSERVER_LATITUDE),
  3. altitude de l'observateur (OBSERVER_ELEVATION),
  4. azimut central (CENTER_AZIMUTH),
  5. angle de vue horizontal (HORIZONTAL_FIELD_OF_VIEW),
  6. distance maximale de visibilité (MAX_DISTANCE),
  7. largeur du panorama (WIDTH),
  8. hauteur du panorama (HEIGHT),
  9. exposant de suréchantillonnage (SUPER_SAMPLING_EXPONENT).

A chacun de ces paramètres sont attachées les valeurs minimales et maximales, données dans la table plus haut, dans l'unité correspondante. Ainsi, les valeurs minimales et maximales attachées à OBSERVER_LONGITUDE sont 60000 et 120000, et correspondent à 6° et 12°.

Les membre de l'énumération offrent une méthode publique nommée p.ex. sanitize, prenant en argument une valeur correspondant au paramètre qu'ils représentent, et retournant la valeur valide la plus proche. Par exemple :

int saneLon1 =                  // vaut 70000
  UserParameter.OBSERVER_LONGITUDE.sanitize(7_0000);
int saneLon2 =                  // vaut 120000
  UserParameter.OBSERVER_LONGITUDE.sanitize(20_0000);

Sachez qu'en Java les énumérations peuvent posséder des attributs, des méthodes et un constructeur (privé uniquement). L'exemple ci-dessous, dont vous pouvez vous inspirer, illustre la syntaxe :

public enum Season {
  SPRING("printemps"),
  SUMMER("été"),
  AUTUMN("automne"),
  WINTER("hiver");

  private String frenchName;
  private Season(String frenchName) {
    this.frenchName = frenchName;
  }

  public String frenchName() {
    return frenchName;
  }
}

2.2 Classe PanoramaUserParameters

La classe nommée p.ex. PanoramaUserParameters, publique et immuable, représente les paramètres d'un panorama du point de vue de l'utilisateur final de l'application. En gros, elle stocke la valeur des neuf paramètres décrits à la section 1.1, sous forme d'entiers exprimés dans l'unité qui leur correspond.

Etant donné que la totalité de ces paramètres ont une valeur entière, il est possible de les stocker dans une table associative, associant à chaque valeur de l'énumération UserParameter la valeur du paramètres correspondant. Cela simplifie grandement l'écriture de la classe PanoramaUserParameters et est donc fortement conseillé.

Notez au passage que s'il est bien entendu possible d'utiliser une table de type HashMap ou TreeMap dans ce but, la bibliothèque Java offre également la classe EnumMap, spécialisée au cas où les clefs proviennent d'une énumération. Son utilisation n'est pas très différente de celle des autres tables associatives, mais sa construction est un peu complexe. L'exemple suivant l'illustre, et vous pouvez vous en inspirer :

Map<Season, String> germanSeasons =
  new EnumMap<>(Season.class);
germanSeasons.put(Season.SPRING, "Frühling");

La classe PanoramaUserParameters offre deux constructeurs :

  1. le constructeur principal, prenant en argument une table associative associant à chaque valeur de l'énumération UserParameter la valeur du paramètre correspondant,
  2. un constructeur secondaire, prenant ces paramètres sous la forme de neuf arguments individuels et ne faisant rien d'autre qu'appeler le premier constructeur.

Avant de stocker la valeur de chaque paramètre, ces constructeurs doivent les valider et, au besoin, les remplacer par la valeur valide la plus proche. Ce faisant, ils doivent tenir compte de la valeur maximale tolérée pour l'angle de vue vertical (170°) et de l'impact que cela peut avoir sur la hauteur du panorama, comme décrit plus haut.

En plus de ces constructeurs, la classe PanoramaUserParameters offre des méthodes donnant accès à la valeur des paramètres qu'elle stocke :

  • une méthode nommée p.ex. get, prenant en argument une valeur de l'énumération UserParameter et retournant la valeur du paramètre correspondant,
  • neuf méthodes nommées p.ex. observerLongitude, etc. et retournant la valeur du paramètre décrit par leur nom,

Finalement, la classe PanoramaUserParameters offre deux méthodes permettant d'obtenir les paramètres du panorama — de type PanoramaParameters, défini à l'étape 5 — correspondants aux paramètres utilisateur :

  • une méthode, nommée p.ex. panoramaParameters, retournant les paramètres du panorama tel qu'il sera calculé,
  • une méthode, nommée p.ex. panoramaDisplayParameters, retournant les paramètres du panorama tel qu'il sera affiché.

La différence entre ces deux méthodes est simplement que la première prend en compte le degré de suréchantillonnage et retourne donc des paramètres dans lesquels la largeur et la hauteur du panorama correspondent aux variables \(w_p\) et \(h_p\) de la section 1.2 ; la seconde méthode ne tient pas compte du suréchantillonnage et la largeur et la hauteur des paramètres qu'elle retourne correspondent donc aux variables \(w_i\) et \(h_i\) de cette même section.

Finalement la classe PanoramaUserParameters redéfinit les méthodes equals et hashCode afin que ses instances soient comparées de manière structurelle.

2.3 Interface PredefinedPanoramas

L'interface nommée p.ex. PredefinedPanoramas contient six champs statiques définissant les instances de PanoramaUserParameters correspondant aux panoramas prédéfinis décrits plus haut. Pour chacun d'entre eux, la largeur est fixée à 2500 échantillons, la hauteur à 800, la distance maximale à 300 km et le facteur de suréchantillonnage à 0.

3 Résumé

Pour cette étape, vous devez :

  • écrire l'énumération UserParameter, la classe PanoramaUserParameters et l'interface PredefinedPanoramas (ou des équivalents) en fonction des indications données plus haut,
  • 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.