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 :
- 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,
- 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 :
- la position de l'observateur \(O\),
- l'altitude de l'observateur \(e\),
- l'azimut du centre du panorama \(a\),
- l'angle de vue horizontal \(v_h\),
- la distance maximale de visibilité \(d\),
- la largeur du panorama \(w\),
- 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.
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 :
- longitude de l'observateur (
OBSERVER_LONGITUDE
), - latitude de l'observateur (
OBSERVER_LATITUDE
), - altitude de l'observateur (
OBSERVER_ELEVATION
), - azimut central (
CENTER_AZIMUTH
), - angle de vue horizontal (
HORIZONTAL_FIELD_OF_VIEW
), - distance maximale de visibilité (
MAX_DISTANCE
), - largeur du panorama (
WIDTH
), - hauteur du panorama (
HEIGHT
), - 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 :
- le constructeur principal, prenant en argument une table associative associant à chaque valeur de l'énumération
UserParameter
la valeur du paramètre correspondant, - 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érationUserParameter
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 classePanoramaUserParameters
et l'interfacePredefinedPanoramas
(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.