Tutoriel sur la décompression du format VisualDEM

Johann Sorel

Article orienté SIG. Dans cet article vous verrez comment décompresser un fichier VisualDEM (extension .dem). Ce format est ancien et peu utilisé, il stocke un modèle numérique de terrain.

- Titre : Tutoriel sur la décompression du format VisualDEM

- Auteur : Johann Sorel

- Parution : 28 février 2007

- Mise à jour : 17 juin 2007

- Licence : Copyright ® 2013 Johann Sorel. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Algorithme de décompression

- II-A. Récupérer le géoréférencement

- II-B. Passer l'entête

- II-C. Récupérer chaque ligne compressée d'altitude

- II-D. Décodage RLE

- II-E. Ajustement des altitudes

- II-F. On génère le tableau

- III. Classe complète Java

- IV. Liens

- V. Note et remerciement du gabarisateur

I - Introduction

C'est quoi ce format que je n'ai jamais vu ?

Vous entrez ici dans le monde des Systèmes d'Informations Géographiques, c'est un monde à part qui comprend plus de formats que de logiciels pour les traiter. Pourquoi cette hypocrisie ? J'aimerais le savoir moi aussi, mais le fait est là, il en existe une quarantaine en format raster (en plus des formats que vous connaissez comme .bmp, .png et .jpg). Parmi ces formats, certains sont des images satellite en infrarouge, d'autres des cartes altimétriques, jusqu'à certains formats temporels ! Hé oui, on stocke l'évolution d'une image dans le temps, le format jpg semble bien pitoyable en comparaison. Certains stockent aussi des métadonnées, on trouve parmi ces formats (MrSID, ECW) des taux de compression aussi évolués que le jpeg2000. Ces formats qui ne stockent que de simples images au premier abord sont pourtant pour les professionnels, et il faut les logiciels pour les ouvrir. On n'ouvre pas un fichier .ecw (très forte compression) d'un demi-gigaoctet avec Paint ou Adobe Photoshop.

Maintenant que vous cernez mieux le domaine d'utilisation, voyons le format VisualDEM (.dem). Si vous avez eu peur je vous rassure, ce format-ci date du temps des 486, il ne fera pas plus de 100 Ko et aura une compression archaïque.

Commençons :

Tout d'abord le fichier a l'extension .dem (Digital Elevation Model ou MNT pour Modèle Numérique de Terrain en français),il stocke un modèle numérique de terrain (MNT), on peut le qualifier de « carte d'altitude ». La dalle stockée fait 258 x 258 points (je parle de points, car on ne peut pas parler ici de pixels). Le fichier stocke l'altitude en mètres, d'une valeur de 0 à 65 535 mètres. Le pas entre chaque point d'altitude est de 75 mètres. Il y a une ligne ou colonne de recouvrement entre chaque dalle, ce qui nous fait une dalle réelle de 256 x 256 points et une couverture de 19,2 par 19,2 km. La taille du fichier varie entre 5 Ko et 100 Ko.

Les données sur le géoréférencement sont stockées au format texte dans le fichier .ctp.
Pour la France, les dalles sont identifiables par leur nom qui commence par FRAN suivi de deux lettres pour la colonne et deux chiffres pour la ligne, la projection est en Lambert 2 étendu.

II - Algorithme de décompression

II-A - Récupérer le géoréférencement

Le géoréférencement est expliqué dans un fichier .ctp, malheureusement, il est difficile de trouver ce fichier de nos jours.
On va donc faire un géoréférencement brut avec le nom du fichier pour la France. On connaît la taille de la dalle et comment elles sont placées (dallage sur toute la France). Le point d'origine des dalles est situé aux coordonnées x=40 000 et y=1 690 000 sur une projection Lambert 2 étendu. Nous prendrons ce point comme origine. On sait qu'une dalle est un carré de 256 points d'altitude et qu'il y a 75 mètres entre deux points.

Les premières valeurs 0 à récupérer sont la colonne et la ligne de notre dalle, une dalle « FRANAA01 » a pour colonne 0 (AA) et ligne 0 (01).

L'algorithme (en Java) :

adresse = "FRANB212.dem";
int pas = 75;
String coord = adresse.substring(adresse.length()-8,adresse.length()-4);
coordY = Integer.valueOf( coord.substring(coord.length()-2,coord.length()) ).intValue() ;

char c1 = coord.charAt(0);
c1 = Character.toUpperCase(c1);
char c2 = coord.charAt(1);
c2 = Character.toUpperCase(c2);

switch (c1) {
case 'A' :coordX = 0; break;
case 'B' :coordX = 26; break;
case 'C' :coordX = 52; break;
}

switch (c2) {
case 'A' :coordX += 0; break;
case 'B' :coordX += 1; break;
case 'C' :coordX += 2; break;
case 'D' :coordX += 3; break;
case 'E' :coordX += 4; break;
case 'F' :coordX += 5; break;
case 'G' :coordX += 6; break;
case 'H' :coordX += 7; break;
case 'I' :coordX += 8; break;
case 'J' :coordX += 9; break;
case 'K' :coordX += 10; break;
case 'L' :coordX += 11; break;
case 'M' :coordX += 12; break;
case 'N' :coordX += 13; break;
case 'O' :coordX += 14; break;
case 'P' :coordX += 15; break;
case 'Q' :coordX += 16; break;
case 'R' :coordX += 17; break;
case 'S' :coordX += 18; break;
case 'T' :coordX += 19; break;
case 'U' :coordX += 20; break;
case 'V' :coordX += 21; break;
case 'W' :coordX += 22; break;
case 'X' :coordX += 23; break;
case 'Y' :coordX += 24; break;
case 'Z' :coordX += 25; break;
}

Il nous reste à faire l'ajustement :

coordX = coordX*pas*256 ;
coordY = (coordY-1)*pas*256  ;
System.out.println( coord +" X=" + coordX + " Y=" + coordY );

II-B - Passer l'entête

Il n'y a pas d'information particulière dans l'entête, on peut donc l'ignorer sans souci.
On va stocker nos données dans une liste. On peut se le permettre, car les fichiers dem ne sont pas volumineux, et cela simplifiera le décodage.

InputStream ips = new FileInputStream(adresse);

// on passe l'entete, 2048 octets
ips.skip(2048);

ArrayList<Integer> bloc_se = new ArrayList<Integer>();
// on stocke notre bloc de données
while ( (val = ips.read()) != -1 ){
bloc_se.add(val);
}
ips.close();

II-C - Récupérer chaque ligne compressée d'altitude

Maintenant que nous avons un bloc d'octets contenant uniquement nos données d'altitude, on va récupérer les lignes une à une. Une dalle contient 258 lignes (256 + 2 de recouvrement), pour récupérer la longueur de chaque ligne on prend les deux premiers octets. Ceux-ci indiquent le nombre N d'octets dédiés à la ligne.
On récupère les N octets dans une liste et on continue de la même manière jusqu'à avoir nos 258 lignes.

// on divise en lignes notre bloc
ArrayList<ArrayList<Integer>> bloc_pl = new ArrayList<ArrayList<Integer>>();
for( int X = 0 ; X < 258 ; X++ ){
longueur = bloc_se.get(n) * 256;  n++;
longueur = longueur + bloc_se.get(n);  n++;

ArrayList<Integer> rec = new ArrayList<Integer>();
for( int i = 0 ; i < longueur ; i++ ){
rec.add(bloc_se.get(n));
n++;
}

bloc_pl.add(rec);
}

II-D - Décodage RLE

Chacune des lignes que nous avons est compressée. Le codage est le suivant :
on prend le premier octet « VAL »:

- il est supérieur ou égal à 128, on va recopier 257-VAL fois l'octet suivant ;

- il est inférieur à 128, on recopie les VAL+1 octets suivants.

Et on continue tant qu'on a des octets non décodés.

ArrayList<ArrayList<Integer>> bloc_dc = new ArrayList<ArrayList<Integer>>();

// on decode nos lignes
int total = 0;
for( int X = 0 ; X < 258 ; X++ ){

ArrayList<Integer> arr = bloc_pl.get(X);
ArrayList<Integer> rec = new ArrayList<Integer>();

n = 0;

do{

val = arr.get(n);
n++;
total++;

if( val >= 128 ){
int decal = 257 - val;
for( int t = 0 ; t < decal ; t++ ){
rec.add(arr.get(n));
}
n++;

} else{
int decal = val + 1;
for( int t = 0 ; t < decal ; t++ ){
rec.add(arr.get(n));
n++;
}
}
}
while ( n < arr.size() );

bloc_dc.add(rec);

}

II-E - Ajustement des altitudes

Maintenant nous avons les altitudes en relatif, c'est-à-dire que sur une ligne, chaque altitude est définie en fonction de la précédente. On va donner les vraies valeurs pour chaque point.
Les deux premiers octets indiquent l'altitude de départ.
Ensuite on prend chaque octet « VAL »:

- il est supérieur à 128, altitude = altitude - (256 - val) ;

- il est égal à 128, altitude = (premier octet suivant)*256 + (deuxième octet suivant) ;

- il est inférieur à 128, altitude = altitude + val.

Et on continue jusqu'à 258 (ou qu'il n'y ait plus d'octet, le résultat est le même).

// on ajuste l'altitude
for( int X = 0 ; X < 258 ; X++ ){
ArrayList<Integer> arr = bloc_dc.get(X);
ArrayList<Integer> rec = new ArrayList<Integer>();
for( int i = 0 ; i < 258 ; i++ ){
rec.add(0);
}

int raise = 0;
n = 0;

int altitude = arr.get(n) * 256 + arr.get(n + 1);
n += 2;
rec.set(raise, altitude);
raise++;

do{
val = arr.get(n);
n++;

if( val == 128 ){
altitude = arr.get(n) * 256 + arr.get(n + 1);
n += 2;
} else if( val < 128 )
altitude += val;
else if( val > 128 )
altitude -= (256 - val);

rec.set(raise, altitude);
raise++;

grid.test(altitude);
}
while ( n < arr.size() );

bloc_alti.add(rec);
}

Voilà, nous avons maintenant une liste de liste « bloc_alti » qui contient tous nos points d'altitude.

II-F - On génère le tableau

On peut améliorer un peu cela, notamment pour un éventuel rendu 2D ou 3D.

float[][][] vertex = new float[258][258][3];
for( int X=0; X<258; X++ ){
for( int Y=0; Y<258; Y++ ){
vertex[X][Y][0] = (X*pas);
vertex[X][Y][1] = (Y*pas);
vertex[X][Y][2] = bloc_alti.get(Y).get(X);
}
}

Il ne faut pas perdre de vue qu'on a travaillé avec la projection du format visualDEM.
Pour ajuster le géoréférencement au lambert 2 étendu, ajouter 40 000 en X et 1 690 000 en Y.

III - Classe complète Java

public class DemMNT {

private int    longueur = 0;
private int    n = 0;
private int    val = 0;
private int coordX = 0;
private int coordY = 0;
private ArrayList<ArrayList<Integer>> bloc_alti = new ArrayList<ArrayList<Integer>>();
private float[][][] vertex = new float[258][258][4];
private int pas = 75;

public DemMNT( String adresse, GRIDMNT grid ){

try{
String coord = adresse.substring(adresse.length()-8,adresse.length()-4);
coordY = Integer.valueOf( coord.substring(coord.length()-2,coord.length()) ).intValue() ;

char l1 = coord.charAt(0);
l1 = Character.toUpperCase(l1);
char l2 = coord.charAt(1);
l2 = Character.toUpperCase(l2);

switch (l1) {
case 'A' :coordX = 0; break;
case 'B' :coordX = 26; break;
case 'C' :coordX = 52; break;
}

switch (l2) {
case 'A' :coordX += 0; break;
case 'B' :coordX += 1; break;
case 'C' :coordX += 2; break;
case 'D' :coordX += 3; break;
case 'E' :coordX += 4; break;
case 'F' :coordX += 5; break;
case 'G' :coordX += 6; break;
case 'H' :coordX += 7; break;
case 'I' :coordX += 8; break;
case 'J' :coordX += 9; break;
case 'K' :coordX += 10; break;
case 'L' :coordX += 11; break;
case 'M' :coordX += 12; break;
case 'N' :coordX += 13; break;
case 'O' :coordX += 14; break;
case 'P' :coordX += 15; break;
case 'Q' :coordX += 16; break;
case 'R' :coordX += 17; break;
case 'S' :coordX += 18; break;
case 'T' :coordX += 19; break;
case 'U' :coordX += 20; break;
case 'V' :coordX += 21; break;
case 'W' :coordX += 22; break;
case 'X' :coordX += 23; break;
case 'Y' :coordX += 24; break;
case 'Z' :coordX += 25; break;
}

coordX = coordX*pas*256 ;
coordY = (coordY-1)*pas*256  ;
System.out.println( coord +" X=" + coordX + " Y=" + coordY );

}catch(Exception e){
JDialog dia = new JDialog();
dia.setModal(true);

Pgeoref p = new Pgeoref(dia,this);

dia.getContentPane().add(p);

dia.pack();
dia.setResizable(false);
dia.setLocationRelativeTo(null);

dia.setVisible(true);
}

try{
InputStream ips = new FileInputStream(adresse);

//OK on passe l'entete, 2048 octets
ips.skip(2048);
System.out.println("taille entete : 2048");

ArrayList<Integer> bloc_se = new ArrayList<Integer>();
//OK on stock notre bloc de données
while ( (val = ips.read()) != -1 ){
bloc_se.add(val);
}
ips.close();
System.out.println("taille bloc sans entete : " + bloc_se.size());

//OK on divise en ligne notre bloc
ArrayList<ArrayList<Integer>> bloc_pl = new ArrayList<ArrayList<Integer>>();
long lng_diviser = 0;
for( int X = 0 ; X < 258 ; X++ ){
longueur = bloc_se.get(n) * 256;
n++;
longueur = longueur + bloc_se.get(n);
n++;
lng_diviser += longueur;

ArrayList<Integer> rec = new ArrayList<Integer>();
for( int i = 0 ; i < longueur ; i++ ){
rec.add(bloc_se.get(n));
n++;
}

bloc_pl.add(rec);
}

System.out.println("taille bloc dans lignes : " + lng_diviser);

ArrayList<ArrayList<Integer>> bloc_dc = decodage(bloc_pl);
ajustement(bloc_dc);

updateCoords();

} catch( Exception e ){
e.printStackTrace();
}

}

private ArrayList<ArrayList<Integer>> decodage( ArrayList<ArrayList<Integer>> bloc_pl){
ArrayList<ArrayList<Integer>> bloc_dc = new ArrayList<ArrayList<Integer>>();

//OK on decode nos lignes
int total = 0;
for( int X = 0 ; X < 258 ; X++ ){

ArrayList<Integer> arr = bloc_pl.get(X);
ArrayList<Integer> rec = new ArrayList<Integer>();

n = 0;
total = 0;
do{

val = arr.get(n);
//System.out.print(val + ">");
n++;
total++;

if( val >= 128 ){
int decal = 257 - val;
total += decal;
//System.out.print(decal + " ");
for( int t = 0 ; t < decal ; t++ ){
rec.add(arr.get(n));
}
n++;

} else{
int decal = val + 1;
total += decal;
//System.out.print(decal + " ");
for( int t = 0 ; t < decal ; t++ ){
rec.add(arr.get(n));
n++;
}
}
}
while ( n < arr.size() );

bloc_dc.add(rec);

}
//System.out.println("\n" + total);
return bloc_dc;
}

private void ajustement( ArrayList<ArrayList<Integer>> bloc_dc){

// on ajuste l'altitude
for( int X = 0 ; X < 258 ; X++ ){
ArrayList<Integer> arr = bloc_dc.get(X);
ArrayList<Integer> rec = new ArrayList<Integer>();
for( int i = 0 ; i < 258 ; i++ ){
rec.add(0);
}

int raise = 0;
n = 0;

int altitude = arr.get(n) * 256 + arr.get(n + 1);
n += 2;
rec.set(raise, altitude);
raise++;

grid.test(altitude);

do{

val = arr.get(n);
n++;

if( val == 128 ){
altitude = arr.get(n) * 256 + arr.get(n + 1);
n += 2;
} else if( val < 128 )
altitude += val;
else if( val > 128 )
altitude -= (256 - val);

rec.set(raise, altitude);
raise++;

grid.test(altitude);

}
while ( n < arr.size() );

bloc_alti.add(rec);
}

}

private void updateCoords(){

for( int X=0; X<258; X++ ){
for( int Y=0; Y<258; Y++ ){
vertex[X][Y][0] = (X*pas);
vertex[X][Y][1] = (Y*pas);
vertex[X][Y][2] = bloc_alti.get(Y).get(X);
}
}
}
}

IV - Liens

Il n'existe plus rien sur ce format, le seul lien que j'ai pu trouver est celui-ci.

Chez Éric Sibert
Un autre article pour le décodage du format VisualDEM

V - Note et remerciement du gabarisateur

Cet article a été mis au gabarit de developpez.com. Dans la mesure du possible, l'esprit d'origine de l'article a été conservé. Cependant, certaines adaptations ont été nécessaires. Voici le lien vers le PDF d'origine : format_VisualDem.pdf et celui vers le ZIP de l'article d'origine : format_VisualDem.zip.

Le gabarisateur remercie Fabien (f-leb) pour sa correction orthographique.