commit f60262e62ab9dbf2a5d1f0c672f9577590bd544b
Author: Marc Souviron <marc.souviron@vous-et-nous.com>
Date:   Thu Mar 29 18:25:18 2012 +0200

    Première version enregistrée

diff --git a/class/index.php b/class/index.php
new file mode 100644
index 0000000..284d196
--- /dev/null
+++ b/class/index.php
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
+  <title>Test des classes</title>
+  <link rel="stylesheet" media="screen" href="css/all.css" />
+  <script src="js/pano.js"></script>
+</head>
+<body>
+  <h1>Test des classes</h1>   
+  <?php
+   require 'site_point.class.php';
+   require 'sites_dir.class.php';
+
+   $base_dir = '../tiles';
+   $dir = new sites_dir($base_dir);
+
+   foreach($dir->get_sites() as $pt) {
+     printf("<h2>Nom : <em>%s</em></h2>\n", $pt->get_name());
+     echo '<pre>';
+     print_r($pt->get_params());
+     print_r($pt->get_magnifications());
+     echo '</pre>';
+     $lat = 43.61034;
+     $lon = 1.45553;
+     $alt = 200;
+     $res = $pt->coordsToCap($lat, $lon, $alt);
+     printf("<h3>Cap vers %lf, %lf, %lf = (dist : %lf, cap : %lf, élévation : %lf)</h3>\n", $lat, $lon, $alt, $res[0], $res[1], $res[2]);
+     $lat = 43.60698;
+     $lon = 1.46725;
+     $alt = 300;
+     $res = $pt->coordsToCap($lat, $lon, $alt);
+     printf("<h3>Cap vers %lf, %lf, %lf = (dist : %lf, cap : %lf, élévation : %lf)</h3>\n", $lat, $lon, $alt, $res[0], $res[1], $res[2]);
+   }
+  ?>
+  <p><a href="http://validator.w3.org/check?uri=referer">page xHTML validé !</a></p>
+</body>
+</html>
diff --git a/class/site_point.class.php b/class/site_point.class.php
new file mode 100644
index 0000000..7e3a5e5
--- /dev/null
+++ b/class/site_point.class.php
@@ -0,0 +1,84 @@
+<?php
+class site_point {
+  private $base_dir;
+  private $name = false;
+  private $prefix = false;
+  private $params = false;
+  private $zooms;
+
+  public function __construct($dir) {
+    $this->base_dir = $dir;
+    $dir_fd = opendir($this->base_dir);
+    while (false !== ($file = readdir($dir_fd))) {
+       if (preg_match('/(.*)_[0-9]+_[0-9]+_[0-9]+\.jpg$/', $file, $reg)) {
+	 $this->prefix = $reg[1];
+	 break;
+       }
+    }
+    closedir($dir_fd);
+    if ($this->prefix === false) return false;
+    $pfname = $this->base_dir.'/'.$this->prefix.'.params';
+    if (is_file($pfname)) {
+      $this->params = @parse_ini_file($pfname);
+    }
+  }
+  
+  public function get_params() {
+    return $this->params;
+  }
+  
+  public function get_name() {
+    return basename($this->base_dir);
+  }
+
+  public function get_prefix() {
+    return $this->prefix;
+  }
+  
+  public function get_magnifications() {
+    $dir_fd = opendir($this->base_dir);
+    while (false !== ($file = readdir($dir_fd))) {                // extraction des paramètres de grossissement par le serveur
+       //echo $file;
+       if (preg_match('/(.*)_([0-9]+)_([0-9]+)_([0-9]+)\.jpg$/', $file, $reg)) {
+	 $prefix = $reg[1];
+	 if ($prefix == $this->prefix) {
+	   $zoom = (int)$reg[2];
+	   $posx = (int)$reg[3]+1;
+	   $posy = (int)$reg[4]+1;
+	   if (!isset($zoom_array[$zoom]['nx']) || $zoom_array[$zoom]['nx'] < $posx) $zoom_array[$zoom]['nx'] = $posx;
+	   if (!isset($zoom_array[$zoom]['ny']) || $zoom_array[$zoom]['ny'] < $posy) $zoom_array[$zoom]['ny'] = $posy;
+	 }
+       }
+    }
+    $this->zooms = $zoom_array;
+    return $this->zooms;
+  }
+
+  public function coordsToCap($lat, $lon, $alt) {
+    if (!isset($this->params['latitude']) || !isset($this->params['longitude'])) return false;
+    $rt = 6371;  // Rayon de la terre
+    $alt1 = isset($this->params['altitude']) ? $this->params['altitude'] : $alt;
+    $lat1 = $this->params['latitude']*M_PI/180;
+    $lon1 = $this->params['longitude']*M_PI/180;
+    $alt2 = $alt;
+    $lat2 = $lat * M_PI/180;
+    $lon2 = $lon * M_PI/180;
+
+    $dLat = $lat2-$lat1;
+    $dLon = $lon2-$lon1;
+    
+    $a = sin($dLat/2) * sin($dLat/2) + sin($dLon/2) * sin($dLon/2) * cos($lat1) * cos($lat2); 
+    $angle = 2 * atan2(sqrt($a), sqrt(1-$a));
+    $d = $angle * $rt;                    // distance du point en Kms
+    
+    $y = sin($dLon)*cos($lat2);
+    $x = cos($lat1)*sin($lat2) - sin($lat1)*cos($lat2)*cos($dLon);
+    $cap = atan2($y, $x);                 // cap pour atteindre le point en radians
+
+    $e = atan2(($alt2 - $alt1)/1000 - $d*$d/(2*$rt), $d);  // angle de l'élévation en radians
+    //    printf("%s, %s, %s, %s\n",$lat1, $this->params['latitude'], $lat, $dLat);
+
+    return array($d, $cap*180/M_PI, $e*180/M_PI);   // les résultats sont en degrés
+  }
+  
+}
diff --git a/class/sites_dir.class.php b/class/sites_dir.class.php
new file mode 100644
index 0000000..9e732f0
--- /dev/null
+++ b/class/sites_dir.class.php
@@ -0,0 +1,19 @@
+<?php
+class sites_dir {
+  private $base_dir;
+
+  public function __construct($dir) {
+    $this->base_dir = $dir;
+  }
+  
+  public function get_sites() {
+    $dir_fd = opendir($this->base_dir);
+    $point_list = array();
+    while (false !== ($point_dir = readdir($dir_fd))) {
+      $pt = new site_point($this->base_dir.'/'.$point_dir);
+      if ($pt->get_prefix() !== false) $point_list[] = $pt;
+    }
+    return $point_list;
+  }
+
+}
diff --git a/css/all.css b/css/all.css
new file mode 100644
index 0000000..a3a6706
--- /dev/null
+++ b/css/all.css
@@ -0,0 +1,125 @@
+* {
+    padding:0;
+    margin:0;
+}
+
+body {
+    overflow:hidden;
+}
+
+#mon-canvas {
+    background-color:#44F;
+    margin:auto;
+    display:block;
+}
+
+#info {
+    display:none;
+    background-color:#FF0;
+    border:1px red solid;
+    position:absolute;
+    margin:1em;
+    padding:0.2em 0.5em;
+    border-radius:0.5em;
+}
+
+#insert {
+    display:none;
+    background-color:#F88;
+    border:1px red solid;
+    position:absolute;
+}
+
+label {
+    display:block;
+    clear:both;
+}
+
+legend {
+    opacity:1;
+    background-color:#F88;
+    border:solid #F00 1px;
+    border-radius:0.5em;
+    padding:0 1em;
+}      
+
+fieldset#control {
+    text-shadow:2px 2px 2px #000, -2px -2px 2px #000;
+    color:#FFF;
+    float:left;
+    position:absolute;
+    top:1em;
+    left:2em;
+    background-color:rgba(128,128,128,0.5);
+    border:solid #F00 1px;
+    border-radius:0.5em;
+    padding:0.2em 0.5em;
+}
+
+input {
+    vertical-align:middle;
+}
+input[type="number"] {
+    border-radius:1em;
+    display:block;
+    float:right;
+}
+
+#res>:not(ul), #control, #params, #info {
+    user-select:none;
+    -khtml-user-select:none;
+    -o-user-select:none;
+    -moz-user-select:-moz-none;
+    -webkit-user-select:none;
+}
+
+div#params {
+    padding:0.2em 0.5em;
+    border-radius:0.5em;
+    background-color:rgba(128,128,128,0.5);
+    border:solid #00F 1px;
+    position:absolute;
+    top:1em;
+    right:2em;
+    float:right;
+}
+
+div#params em {
+    display:block;
+    float:right;
+    background-color:#88F;
+    border-radius:0.5em;
+    padding:0 0.5em;
+    color:#FFF;
+}
+
+div#params em:nth-child[2] {
+    display:inline;
+}
+
+.validators {
+    text-align:center;
+    background-color:#CCC;
+    border:solid 1px #F55;
+    border-width:0 0 1px 0;
+    display:none;
+}
+
+#res {
+    padding:0.2em 0.4em;
+    color:#FF8;
+    position:absolute;
+    bottom:1em;
+    right:2em;
+    background-color:rgba(0,0,0,0.3);
+    border:solid #FFF 1px;
+}
+
+#res:empty {padding:0;}
+
+#res li {
+    list-style-type: none;
+    color:#FFF;
+    background-color:rgba(100,0,0,0.5);
+
+}
diff --git a/images/tsf.ico b/images/tsf.ico
new file mode 100644
index 0000000..537417b
Binary files /dev/null and b/images/tsf.ico differ
diff --git a/images/tsf.png b/images/tsf.png
new file mode 100644
index 0000000..495940b
Binary files /dev/null and b/images/tsf.png differ
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..d222849
--- /dev/null
+++ b/index.php
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
+  <title>Liste des panoramas</title>
+  <link rel="stylesheet" media="screen" href="css/all.css" />
+  <script src="js/pano.js"></script>
+</head>
+<body>
+  <h1>Liste des panoramas</h1>   
+  <?php
+   require 'class/site_point.class.php';
+   require 'class/sites_dir.class.php';
+   echo '<ul>';
+   if(isset($_GET['dir']) && is_dir($_GET['dir'])) {
+     $base_dir = $_GET['dir'];
+   } else {
+     $base_dir = 'tiles';
+   }
+
+   $dir = new sites_dir($base_dir);
+
+   foreach($dir->get_sites() as $pt) {
+     $params = $pt->get_params();
+     if (isset($params['titre'])) {
+       $cmt = $params['titre'];
+     } else {
+       $cmt = sprintf('fichier <samp>%s/%s</samp>', $pt->get_name(), $pt->get_prefix());
+     }
+     printf ('<li><a href="panorama.php?dir=%s&amp;panorama=%s">%s</a></li>'."\n", $base_dir, $pt->get_name(), $cmt);
+   }
+   echo '</ul>';
+  ?>
+  <p><a href="http://validator.w3.org/check?uri=referer">page xHTML validé !</a></p>
+</body>
+</html>
diff --git a/js/pano.js b/js/pano.js
new file mode 100644
index 0000000..d4c40fd
--- /dev/null
+++ b/js/pano.js
@@ -0,0 +1,754 @@
+if (img_prefix == undefined) var img_prefix = './tiles/ttn_mediatheque/mediatheque_70';
+if (to_cap == undefined) var to_cap = 0;
+if (to_ele == undefined) var to_ele = 0;
+if (to_zoom == undefined) var to_zoom = 0;
+if (cap == undefined) var cap = 0;
+if (cap_min == undefined) var cap_min = cap;
+if (cap_max == undefined) var cap_max = cap_min+360;
+if (ref_points == undefined) var ref_points = new Array();
+if (image_loop == undefined) var image_loop = true;
+
+var debug_mode = false;
+var canvas;
+var cntext;
+var point_list = new Array();
+var zoom = 0;
+var zooms = new Array();
+var prev_zm;
+var zm;
+var tile = {width:256, height:256};
+var ntiles = {x:228, y:9};
+var border_width = 2;
+var imageObj = new Array();
+
+var last  = {x:0,y:0};
+var shift = {x:0,y:0};
+var mouse = {x:0,y:0};
+var speed = {x:0,y:0};
+var canvas_pos = {x:0,y:0};
+var tmt;
+var is_located = false;
+var point_colors = {'pano_point' : '255,128,128',
+		    'ref_point'  : '128,128,255',
+		    'loc_point'  : '128,255,128',
+		    'unlocated'  : '255,255,255'};
+var test = {x:0, y:0, i:100};
+
+function nmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres naturels N.
+    return Math.floor((val%div+div)%div);   // il y a peut être plus simple, mais en attendant .... 
+}
+
+function fmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres réels positifs.
+    return (val%div+div)%div;               // il y a peut être plus simple, mais en attendant .... 
+}
+
+function distort_canvas(p, x, y) {
+    if (p == 0) distort = 0;
+    else {
+	cntext.save();
+	distort++;
+        cntext.clearRect(0, 0, canvas.width, 2*canvas.height);
+	var ratio = (canvas.width-2*distort)/canvas.width;
+	var shift = canvas.height/2*(1-ratio);
+	cntext.scale(1, ratio);
+	if (p == 1) cntext.translate(0, 0);
+	else if (p == -1) cntext.translate(0, 0);
+	draw_image(x, y);
+	cntext.restore();
+	document.getElementById('res').innerHTML = 'distort : ' + distort + ' shift ' + shift + ' ratio : ' + ratio + '<br/>';
+    }
+}
+
+function draw_image(ox, oy) {
+    var ref_vals  = {x:last.x, y:last.y, zoom:zoom};
+    ox = nmodulo(ox-canvas.width/2, zm.im.width);        // pour placer l'origine au centre du canvas 
+    oy = Math.floor(oy-canvas.height/2);                 // pas de rebouclage vertical
+
+    cntext.clearRect(0, 0, canvas.width, canvas.height);
+    cntext.fillStyle = "rgba(128,128,128,0.8)";
+    
+    if (canvas.height > zm.im.height) {
+	var fy = Math.floor((oy+canvas.height/2-zm.im.height/2)/(tile.height*zm.ntiles.y))*zm.ntiles.y;
+	if (fy < 0) fy = 0; 
+	var ly = fy + zm.ntiles.y;
+    } else {
+	var fy = Math.floor(oy/tile.height);
+	var ly = Math.floor((oy+canvas.height+tile.height-1)/tile.height+1);
+	if (fy < 0) fy = 0; 
+	if (ly > zm.ntiles.y) ly = zm.ntiles.y; 
+    }
+
+    for (var j=fy; j<ly; j++) {
+	var delta_y = (Math.floor(j/zm.ntiles.y) - Math.floor(fy/zm.ntiles.y)) * (tile.height - zm.last_tile.height);
+	var dy = j*tile.height - oy - delta_y;
+	var ny = j%ntiles.y;
+	var wy = zm.tile.width;
+	if (ny == zm.ntiles.y - 1) wy = zm.last_tile.height;
+
+	var cpx = 0;
+	var i = 0;
+	var Nx = zm.ntiles.x;
+	while (cpx < ox+canvas.width) {
+	    var cur_width = zm.tile.width;
+	    if (i%Nx == zm.ntiles.x-1) cur_width = zm.last_tile.width;
+	    if (cpx >= ox-cur_width) {
+		var nx = i%Nx;
+		var idx = nx+'-'+ny+'-'+ref_vals.zoom;
+		if (imageObj[idx] && imageObj[idx].complete) {
+		    draw_tile(idx, cpx-ox, dy); // l'image est déja en mémoire, on force le dessin sans attendre.
+		} else {
+		    var fname = get_file_name(nx, ny, ref_vals.zoom);
+		    imageObj[idx] = new Image();
+		    imageObj[idx].src = fname;
+		    var ts = zm.get_tile_size(nx, ny);
+		    cntext.fillRect(cpx-ox, dy, ts.width, ts.height);
+		    imageObj[idx].addEventListener('load', (function(ref, idx, dx, dy, ox, oy, ts) {
+			return function() {        // closure nécéssaire pour gestion assynchronisme !!!
+			    draw_tile_del(ref, idx, dx, dy, ox, oy, ts.width, ts.height);
+			};
+		    })(ref_vals, idx, cpx-ox, dy, ox, oy, ts), false);
+		}
+//		load_image(zoom, nx, ny, shx, shy, cpx-ox, dy, ox, oy);
+	    }
+	    cpx += cur_width;
+	    i++;
+	}
+    }
+    drawDecorations(ox, oy);
+}
+
+function draw_tile_del(ref, idx, tx, ty, ox, oy, twidth, theight) {
+    if (ref.zoom == zoom && ref.x == last.x && ref.y == last.y) {
+	draw_tile(idx, tx, ty);
+	drawDecorations(ox, oy, tx, ty, twidth, theight);
+    }
+}
+
+function draw_tile(idx, ox, oy) {
+    var img = imageObj[idx];
+    cntext.drawImage(img, ox, oy);
+}
+
+function drawDecorations(ox, oy, tx, ty, twidth, theight) {
+    if (twidth) {
+	cntext.save();
+	cntext.beginPath();
+        cntext.rect(tx, ty, twidth, theight);
+        cntext.clip();
+    } 
+    var wgrd = zm.im.width/360;
+    var od = ((ox+canvas.width/2)/wgrd)%360;
+    var el = (zm.im.height/2 - (oy+canvas.height/2))/wgrd;
+    cntext.fillStyle = "rgba(0,128,128,0.9)";
+    cntext.strokeStyle = "rgb(255,255,255)";
+    cntext.lineWidth = 1;
+    cntext.fillRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    cntext.strokeRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (zm.pt_list[i]['type'] != 'unlocated') {
+	    cntext.fillStyle = 'rgba('+point_colors[zm.pt_list[i]['type']]+',0.5)';
+	    var cx = nmodulo(zm.pt_list[i]['xc'] - ox, zm.im.width);
+	    var cy = zm.pt_list[i]['yc'] - oy;
+	    cntext.beginPath();
+	    cntext.arc(cx, cy, 20, 0, 2*Math.PI, true);
+	    cntext.fill();
+	}
+    }
+
+    //cntext.font = "20pt Arial";
+    //cntext.fillRect(0, 0, 200, 20);
+    //cntext.fillStyle = "rgb(255,0,0)";
+    //cntext.fillText(od.toFixed(2), 5, 20);
+    //for (i=0; i<canvas.width/wgrd; i++) {
+	//cntext.strokeRect(i*wgrd, 0, wgrd, 20);
+    //}
+    if (twidth) {
+	cntext.restore();
+    }
+}
+
+function get_file_name(x, y, z) { // recherche du fichier correspondant au zoom et à la position
+    var prm = [z, x, y];
+    var fname = img_prefix;
+    for (var i = 0; i < prm.length; i++) {
+	fname += '_';
+	if (prm[i] < 10) fname += '00';
+	else if (prm[i] < 100) fname += '0';
+	fname += prm[i];
+    }
+    fname += '.jpg';
+    return fname;
+}
+
+function keys(key) {
+    hide_links();
+    evt = key || event;
+    evt.preventDefault();
+    evt.stopPropagation();
+    if (!key) {
+	key = window.event;
+	key.which = key.keyCode;
+    }
+//    alert(key);
+//    if (!evt.shiftKey) return;
+    switch (key.which) {
+    case 66: // b
+	alert(key.pageX);
+	test.x=tile.width*(ntiles.x-3);
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 67: // c
+	test.x=0;
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    case 36: // home
+	putImage(0, zm.im.height/2);
+	return;
+    case 35: // end
+	putImage(last.x+zm.im.width/2, last.y);
+	return;
+    case 39: // left
+	putImage(last.x+40, last.y);
+	return;
+    case 40: // up
+	putImage(last.x, last.y+20);
+	return;
+    case 37: // right
+	putImage(last.x-40, last.y);
+	return;
+    case 38: // down
+	putImage(last.x, last.y-20);
+	return;
+    case 33: // pageup
+	zoom_control.value--;
+	change_zoom()
+	return;
+    case 34: // pagedown
+	zoom_control.value++;
+	change_zoom()
+	return;
+    default:
+//	alert(key.which)
+	return;
+    }
+}
+
+function onImageClick(e) {
+    hide_contextmenu();
+    shift.x = last.x;
+    shift.y = last.y;
+    speed.x = 0;
+    speed.y = 0;
+    mouse.x = e.pageX;
+    mouse.y = e.pageY;
+    clearTimeout(tmt);  //arrêt de l'éffet eventuel d'amorti en cours.
+    canvas.addEventListener('mousemove', stickImage, false);
+    canvas.addEventListener('mouseup', launchImage, false);
+    //canvas.addEventListener('mouseout', launchImage, false);
+    canvas.style.cursor='move';
+    //document.onmousemove = stickImage;
+    document.onmouseup = launchImage;
+    hide_links();
+}
+
+
+function stickImage(e) {
+    var xs = mouse.x - e.pageX + shift.x;
+    var ys = mouse.y - e.pageY + shift.y;
+    speed.x = xs - last.x;  //mémorisation des vitesses horizontales
+    speed.y = ys - last.y;  //et verticales lors de ce déplacement
+    putImage(xs, ys);
+}
+
+function launchImage(e) {
+    distort_canvas(0);
+    canvas.removeEventListener('mousemove', stickImage, false);
+    //document.onmousemove = null;
+    shift.x = e.pageX - mouse.x + shift.x;
+    shift.y = e.pageY - mouse.y + shift.y;
+    tmt = setTimeout(inertialImage, 100);
+}
+
+function putImage(x, y) { // est destiné à permettre l'effet d'amortissement par la mémorisation de la position courante.
+    if (!zm.is_updated) return; 
+    if (x >= zm.im.width) {   // rebouclage horizontal
+	shift.x -= zm.im.width;
+	x -= zm.im.width;
+    } else if (x < 0) {
+	shift.x += zm.im.width;
+	x += zm.im.width;
+    }
+    if (y >= zm.im.height) {   // pas de rebouclage vertical mais blocage
+	//distort_canvas(1, x, y);
+	shift.y = zm.im.height-1;
+	y = zm.im.height-1;
+    } else if (y < 0) {
+	//distort_canvas(-1, x, y);
+	shift.y = 0;
+	y = 0;
+    }
+
+    last.x = x;
+    last.y = y;
+    draw_image(x, y);
+}
+
+function inertialImage() {
+    speed.x *= 0.9;
+    speed.y *= 0.9;
+    if (Math.abs(speed.x) > 2 || Math.abs(speed.y) > 2) {
+	putImage(last.x+speed.x, last.y+speed.y);
+	tmt = setTimeout(inertialImage, 100);
+    } else {
+	show_links();
+    }
+}
+
+function tri_ref_points(v1, v2) {
+    return v1['x'] - v2['x'];
+}
+
+
+
+function tzoom(zv) {
+    this.value = zv;
+    this.ntiles = {x:0,y:0};
+    this.tile = {width:0,height:0};
+    this.last_tile = {width:0,height:0};
+    this.max_tile = {width:0,height:0};
+    this.im = {width:0,height:0};
+    this.is_updated = false;
+
+    this.refresh = function() {
+	this.im.visible_width = this.tile.width*(this.ntiles.x-1)+this.last_tile.width;
+	this.is_updated = true;
+
+	this.im.width = this.im.visible_width;
+	this.im.height = this.tile.height*(this.ntiles.y-1)+this.last_tile.height;
+	if (this.last_tile.width > this.tile.width) this.max_tile.width = this.im.last_tile.width;
+	else this.max_tile.width = this.tile.width;
+	if (this.last_tile.height > this.tile.height) this.max_tile.height = this.im.last_tile.height;
+	else this.max_tile.height = this.tile.height;
+
+	var ord_pts = new Array();
+	i=0;
+	for(var label in ref_points) {
+	    ord_pts[i++] = ref_points[label]
+	}
+	ord_pts = ord_pts.sort(tri_ref_points);
+	is_located = i > 1 || image_loop && i > 0;
+
+	var alpha_domain = {start:0, end:360}; 
+	this.pixel_y_ratio = this.im.width/360;
+	if (is_located) {
+	    this.ref_pixels = new Array;
+	    this.ref_pixels[0] = new Array();    // Attention il faut compter un intervalle de plus !
+	    for (var i=0; i < ord_pts.length; i++) { // premier parcours pour les paramètres cap/x
+		this.ref_pixels[i+1] = new Array();
+		this.ref_pixels[i+1].x = Math.floor(ord_pts[i].x*this.im.width);
+		this.ref_pixels[i+1].cap = fmodulo(ord_pts[i].cap, 360);
+		if (i != ord_pts.length-1) {
+		    this.ref_pixels[i+1].ratio_x = (ord_pts[i+1].x - ord_pts[i].x)/fmodulo(ord_pts[i+1].cap - ord_pts[i].cap, 360)*this.im.width;
+		}
+	    }
+	    if (image_loop == true) {
+		var dpix = this.im.width;
+		var dangle = 360;
+		if (ord_pts.length > 1) {
+		    dpix = this.im.width - this.ref_pixels[this.ref_pixels.length-1].x + this.ref_pixels[1].x;
+		    dangle = fmodulo(this.ref_pixels[1].cap - this.ref_pixels[this.ref_pixels.length-1].cap, 360);
+		}
+		this.ref_pixels[0].ratio_x = dpix/dangle;
+		this.ref_pixels[ord_pts.length].ratio_x = this.ref_pixels[0].ratio_x;
+		dpix = this.im.width - this.ref_pixels[ord_pts.length].x;
+		this.ref_pixels[0].cap = fmodulo(this.ref_pixels[ord_pts.length].cap + dpix / this.ref_pixels[0].ratio_x, 360);
+	    } else {
+		this.ref_pixels[0].ratio_x = this.ref_pixels[1].ratio_x;
+		this.ref_pixels[ord_pts.length].ratio_x = this.ref_pixels[ord_pts.length-1].ratio_x;
+		this.ref_pixels[0].cap = fmodulo(this.ref_pixels[1].cap - this.ref_pixels[1].x / this.ref_pixels[1].ratio_x, 360);
+		alpha_domain.start = this.ref_pixels[0].cap;
+		alpha_domain.end = fmodulo(this.ref_pixels[ord_pts.length].cap+(this.im.width-this.ref_pixels[ord_pts.length].x)/this.ref_pixels[ord_pts.length].ratio_x, 360);
+		this.pixel_y_ratio = this.im.width/fmodulo(alpha_domain.end-alpha_domain.start, 360);
+	    }
+	    this.ref_pixels[0].x = 0;
+
+	    for (var i=0; i < ord_pts.length; i++) { // second parcours pour les paramètres elevation/y
+		this.ref_pixels[i+1].shift_y = Math.floor(this.pixel_y_ratio*ord_pts[i].ele - ord_pts[i].y*this.im.height);
+		if (i != ord_pts.length-1) {
+		    var next_shift = Math.floor(this.pixel_y_ratio*ord_pts[i+1].ele - ord_pts[i+1].y*this.im.height);
+		    this.ref_pixels[i+1].dshft_y = (next_shift - this.ref_pixels[i+1].shift_y)/(this.ref_pixels[i+2].x - this.ref_pixels[i+1].x);
+		}
+	    }
+
+	    if (image_loop == true) {
+		var dpix  = this.im.width;
+		var delt = 0;
+		if (ord_pts.length > 1) {
+		    dpix  = this.im.width - this.ref_pixels[this.ref_pixels.length-1].x + this.ref_pixels[1].x;
+		    delt = this.ref_pixels[this.ref_pixels.length-1].shift_y - this.ref_pixels[1].shift_y;
+		}
+		this.ref_pixels[0].dshft_y = delt/dpix;
+		this.ref_pixels[ord_pts.length].dshft_y = this.ref_pixels[0].dshft_y;
+		dpix = this.im.width - this.ref_pixels[ord_pts.length].x;
+		this.ref_pixels[0].shift_y = this.ref_pixels[ord_pts.length].shift_y - dpix*this.ref_pixels[0].dshft_y;
+	    } else {
+		this.ref_pixels[0].shift_y = this.ref_pixels[1].shift_y;
+		this.ref_pixels[ord_pts.length].shift_y = this.ref_pixels[ord_pts.length-1].shift_y;
+		this.ref_pixels[0].dshft_y = 0;
+		this.ref_pixels[ord_pts.length].dshft_y = 0;
+	    }
+
+	    if (debug_mode) {
+		var res = document.getElementById('res');
+		res.innerHTML = 'liste des '+this.ref_pixels.length+' valeurs de correction (image = '+this.im.width+'x'+this.im.height+') zoom = '+this.value+':<br/>';
+		for (var i=0; i < this.ref_pixels.length; i++) { // pour le debug
+		    res.innerHTML += '<p>point '+i+' :</p><ul>';
+		    for (var key in this.ref_pixels[i]) { // pour le debug
+			res.innerHTML += '<li>'+key + '['+i+'] = '+this.ref_pixels[i][key]+'</li>';
+		    }
+		    if (i != this.ref_pixels.length-1) {
+			var tx0 = this.ref_pixels[i+1].x-1;
+			//var ty0 = this.ref_pixels[i+1].shift_y;
+			var ty0 = 0;
+		    } else {
+			var tx0 = this.im.width-1;
+			var ty0 = 0;
+		    }
+		    res.innerHTML += '</ul><p>test sur : '+tx0+','+ty0+'</p>';
+		    var tst = this.get_cap_ele(tx0, ty0);
+		    res.innerHTML += '<p>cap:'+tst.cap+', shift:'+tst.ele+'</p>';
+		    var tst2 = this.get_pos_xy(tst.cap, tst.ele);
+		    res.innerHTML += '</ul><p>x:'+tst2.x+', y:'+tst2.y+'</p>';
+		}
+	    }
+	}
+
+	this.pt_list = new Array();
+	for (var i=0; i<point_list.length; i++) {
+	    var lbl = point_list[i][0];
+	    var dst = point_list[i][1];
+	    var cap = point_list[i][2];
+	    var ele = point_list[i][3];
+	    var lnk = point_list[i][4];
+	    var typ = 'unlocated';
+	    var rxy = this.get_pos_xy(cap, ele);
+	    var is_visible = fmodulo(cap - alpha_domain.start, 360) <= fmodulo(alpha_domain.end - alpha_domain.start -0.0001, 360)+0.0001 && is_located;
+
+	    this.pt_list[i] = new Array();
+	    if (ref_points[lbl] != undefined) {
+		typ = 'ref_point';
+		if (!is_located) rxy = {x:ref_points[lbl].x*this.im.width, y:ref_points[lbl].y*this.im.height}
+	    } else if(lnk == '' && is_visible) {
+		typ = 'loc_point';
+	    } else if(is_visible) {
+		typ = 'pano_point';
+		lnk += '&to_zoom='+this.value;
+	    }
+	    this.pt_list[i]['type'] = typ;
+	    this.pt_list[i]['cap'] = cap;
+	    this.pt_list[i]['ele'] = ele;
+	    this.pt_list[i]['dist'] = dst;
+	    this.pt_list[i]['label'] = lbl;
+	    this.pt_list[i]['lnk'] = lnk;
+	    this.pt_list[i]['xc'] = rxy.x;
+	    this.pt_list[i]['yc'] = Math.floor(this.im.height/2 - rxy.y);
+	}
+    }
+    
+    this.get_tile_size = function(nx, ny) {
+	var res = {width:0, height:0};
+	if (nx == this.ntiles.x-1) res.width = this.last_tile.width;
+	else res.width = this.tile.width;
+	if (ny == this.ntiles.y-1) res.height = this.last_tile.height;
+	else res.height = this.tile.height;
+	return res;
+    }
+    
+    this.get_cap_ele = function(px, py) {               // recherche d'un cap et d'une élévation à partir d'un pixel X,Y.
+	if (is_located) {
+	    for (var i=0; i < this.ref_pixels.length; i++) {
+		if (i == this.ref_pixels.length - 1 || px < this.ref_pixels[i+1].x) {
+		    var dpix = px-this.ref_pixels[i].x;
+		    var cp = fmodulo(this.ref_pixels[i].cap + dpix/this.ref_pixels[i].ratio_x, 360);
+		    var el = (py+this.ref_pixels[i].shift_y+this.ref_pixels[i].dshft_y*dpix)/this.pixel_y_ratio;
+		    return {cap:cp, ele:el};
+		}
+	    }
+	} else {
+	    var cp = 360*px/this.im.width;
+	    var el = 360*py/this.im.height;
+	    return {cap:cp, ele:el};
+	}
+    }
+    
+    this.get_pos_xy = function(cap, ele) {                  // recherche des coordonnées pixel à partir d'un cap et d'une élévation.
+	if (is_located) {
+	    var dcap = fmodulo(cap-this.ref_pixels[0].cap, 360);
+	    for (var i=0; i < this.ref_pixels.length; i++) {
+		if (i == this.ref_pixels.length - 1 || dcap < fmodulo(this.ref_pixels[i+1].cap-this.ref_pixels[0].cap, 360)) {
+		    var px = this.ref_pixels[i].x + this.ref_pixels[i].ratio_x*fmodulo(cap - this.ref_pixels[i].cap, 360);
+		    var dpix = px-this.ref_pixels[i].x;
+		    var py = this.pixel_y_ratio*ele - this.ref_pixels[i].shift_y - this.ref_pixels[i].dshft_y*dpix;
+		    return {x:px, y:py};
+		}
+	    }
+	} else {
+	    var px = fmodulo(cap, 360)/360*this.im.width;
+	    var py = ele/360*this.im.height;
+	    return {x:px, y:py};
+	}
+    }
+}
+
+function reset_zooms () {
+    for(i=0; i<zooms.length; i++) zooms[i].is_updated = false;
+    zm.refresh();
+}
+
+function wheel_zoom (event) {
+    var zshift = {x:0, y:0};
+    if (event.pageX != undefined && event.pageX != undefined) {
+	zshift.x = event.pageX-canvas.width/2-canvas_pos.x;
+	zshift.y = event.pageY-canvas.height/2-canvas_pos.y;
+    }
+    event.preventDefault();
+    if (event.wheelDelta < 0 && zoom_control.value < zoom_control.max) {
+	zoom_control.value++;
+	change_zoom(zshift.x, zshift.y);
+    } else if (event.wheelDelta > 0 && zoom_control.value > zoom_control.min) {
+	zoom_control.value--; 
+	change_zoom(zshift.x, zshift.y);
+    }
+}
+
+function change_zoom(shx, shy) {
+    var zoom_control = document.getElementById("zoom_ctrl");
+    var v = zoom_control.value;
+
+    prev_zm = zm;
+
+    if (zooms[v]) {
+	if (!zooms[v].is_updated) zooms[v].refresh();
+    } else {
+	zooms[v] = new tzoom(v);
+    }
+
+    if (zooms[v].is_updated) {
+	if (shx == undefined || shy == undefined) {
+	    shx=0;
+	    shy=0;
+	}
+	zm = zooms[v];
+	var px = (last.x+shx)*zm.im.width/prev_zm.im.width - shx;
+	var py = (last.y+shy)*zm.im.height/prev_zm.im.height - shy;
+	if (py < zm.im.height && py >= 0) {
+	    zoom = zm.value;
+	    tile = zm.tile;
+	    ntiles = zm.ntiles;
+	    putImage(px, py);
+	} else {
+	    zm = prev_zm;
+	    zoom_control.value = zm.value;
+	}
+    }
+}
+
+function change_angle() {
+    var elvtn_control = document.getElementById('elvtn_ctrl');
+    var angle_control = document.getElementById('angle_ctrl');
+    var resxy = zm.get_pos_xy(angle_control.value, elvtn_control.value);
+    var pos_x = resxy.x;
+    var pos_y = Math.floor(zm.im.height/2 - resxy.y);
+    putImage(pos_x, pos_y);
+}
+
+function check_prox(x, y, r) {   //verification si un point de coordonnées x, y est bien dans un cercle de rayon r centré en X,Y. 
+    return Math.sqrt(x*x + y*y) < r;
+}
+
+function check_links(e) {
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (is_located && zm.pt_list[i]['type'] == 'pano_point') {
+	    if (check_prox(zm.pt_list[i]['xc']-pos_x, zm.pt_list[i]['yc']-pos_y, 20)) {
+		if (zm.pt_list[i]['lnk'] != '') window.location = zm.pt_list[i]['lnk'];
+		break;
+	    }
+	}
+    }
+}
+
+function display_links(e) {
+    var info = document.getElementById('info');
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    //var cap = ((pos_x/zm.im.width)*360).toFixed(2);
+    var res = zm.get_cap_ele(pos_x, zm.im.height/2 - pos_y);
+    //var ele = ((zm.im.height/2 - pos_y)/zm.im.width)*360;
+    info.innerHTML = 'élévation : '+res.ele.toFixed(2)+'<br/>cap : '+res.cap.toFixed(2);
+    info.style.top = e.pageY+'px';
+    info.style.left = e.pageX+'px';
+    info.style.backgroundColor = '#FFC';
+    info.style.display = 'block';
+    canvas.style.cursor='crosshair';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (is_located || zm.pt_list[i]['type'] == 'ref_point') {
+	    if (check_prox(zm.pt_list[i]['xc']-pos_x, zm.pt_list[i]['yc']-pos_y, 20)) {
+		info.innerHTML = zm.pt_list[i]['label'];
+		if (zm.pt_list[i]['dist'] < 10) var dst = Math.round(zm.pt_list[i]['dist']*1000)+' m';
+		else var dst = zm.pt_list[i]['dist'].toFixed(1)+' kms';
+		info.innerHTML += '<br/> à ' + dst;
+		info.style.backgroundColor = 'rgb('+point_colors[zm.pt_list[i]['type']]+')';
+		canvas.style.cursor='auto';
+		break;
+	    }
+	}
+    }
+}
+
+function hide_links() {
+    canvas.removeEventListener( "mousemove", display_links, false);
+    var info = document.getElementById('info');
+    info.style.display = 'none';
+}
+
+function show_links() {
+    canvas.addEventListener( "mousemove", display_links, false);
+//    var info = document.getElementById('info');
+//    info.style.display = 'block';
+}
+
+function hide_contextmenu() {
+    document.getElementById('insert').style.display = 'none';
+}
+
+function manage_ref_points(e) {
+    //event.preventDefault();
+    //event.stopPropagation();
+    var insrt = document.getElementById('insert');
+    document.getElementById('do-cancel').onclick = hide_contextmenu;
+    insrt.style.left = e.pageX+'px';
+    insrt.style.top = e.pageY+'px';
+    insrt.style.display = 'block';
+    var sel_pt = document.getElementById('sel_point');
+    var do_insert = document.getElementById('do-insert');
+    var do_delete = document.getElementById('do-delete');
+    var pos_x = nmodulo(last.x + e.pageX - canvas_pos.x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + e.pageY - canvas_pos.y - canvas.height/2;
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (zm.pt_list[i]['type'] == 'ref_point') {
+	    if (check_prox(zm.pt_list[i]['xc']-pos_x, zm.pt_list[i]['yc']-pos_y, 20)) {
+		sel_pt.value = zm.pt_list[i]['label'];
+	    }
+	}
+    }
+    do_delete.onclick = function() {delete_ref_point(insrt)};
+    do_insert.onclick = function() {insert_ref_point(insrt, e.pageX-canvas_pos.x, e.pageY-canvas_pos.y)};
+    return false;
+}
+
+function insert_ref_point(el, x, y) {
+    var label;
+    el.style.display = 'none';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	label = zm.pt_list[i]['label'];
+	if(label == document.getElementById('sel_point').value) {
+	    var posx = nmodulo(last.x + x - canvas.width/2, zm.im.width)/zm.im.width;
+	    var posy = 0.5 - (last.y + y - canvas.height/2)/zm.im.height;
+	    var pval = {x:posx, y:posy, cap:zm.pt_list[i]['cap'], ele:zm.pt_list[i]['ele'], label:label};
+	    ref_points[label] = pval;
+	    document.getElementById('res').innerHTML = '<h4>Dernier point entré</h4>';
+	    document.getElementById('res').innerHTML += '<p>reference["'+label+'"] = '+posx.toFixed(5)+','+posy.toFixed(5)+'</p>';
+	    reset_zooms();
+ 	    putImage(last.x, last.y);
+	    break;
+	}
+    }
+    show_result();
+}
+
+function show_result(clear_before) {
+    var res = document.getElementById('res');
+    var strg = '';
+    for (var lbl in ref_points) {
+	strg += '<li>reference["'+lbl+'"] = '+ref_points[lbl].x.toFixed(5)+','+ref_points[lbl].y.toFixed(5)+'</li>';
+    }
+    if (strg) strg = '<h3>Liste de tous les points de référence</h3>\n<ul>' + strg + '</ul>';
+    if (clear_before) res.innerHTML = strg;
+    else res.innerHTML += strg;
+}
+
+function delete_ref_point(el) {
+    el.style.display = 'none';
+    delete ref_points[document.getElementById('sel_point').value];
+    reset_zooms();
+    putImage(last.x, last.y);
+    show_result(true);
+}
+
+function clean_canvas_events(e) {
+    canvas.removeEventListener('mousemove', stickImage, false);
+    document.getElementById('info').style.display = 'none';
+    speed.x = 0;
+    speed.y = 0;
+}
+
+canvas_set_size = function() {
+    canvas.style.border = border_width+"px solid red";
+    canvas.width = window.innerWidth-2*border_width;
+    canvas.height = window.innerHeight-2*border_width;
+    canvas_pos.x = canvas.offsetLeft+border_width;
+    canvas_pos.y = canvas.offsetTop+border_width;
+}
+
+canvas_resize = function() {
+    canvas_set_size();
+    putImage(last.x, last.y);
+}
+
+window.onload = function() {
+    canvas = document.getElementById("mon-canvas");
+    cntext = canvas.getContext("2d");
+    canvas_set_size();
+    canvas.addEventListener("click", check_links, false);
+    //canvas.addEventListener("oncontextmenu", manage_ref_points, false);
+    canvas.oncontextmenu = manage_ref_points;
+    canvas.addEventListener("mouseout" , clean_canvas_events, false);
+    show_links();
+
+    var max_zoom = zooms.length - 1;
+    zoom_control = document.getElementById("zoom_ctrl");
+    zoom_control.onchange = change_zoom;
+    zoom_control.max = max_zoom;
+    if (to_zoom > max_zoom) to_zoom = Math.floor(max_zoom/2);
+    zm = zooms[to_zoom];
+    zoom_control.value = to_zoom;
+    zm.refresh();
+
+    zoom = zm.value;
+    tile = zm.tile;
+    ntiles = zm.ntiles;
+
+    angle_control = document.getElementById("angle_ctrl");
+    angle_control.value = to_cap;
+    angle_control.onchange = change_angle;
+    angle_control.onclick = change_angle;
+    elvtn_control = document.getElementById("elvtn_ctrl");
+    elvtn_control.value = to_ele;
+    elvtn_control.onchange = change_angle;
+    elvtn_control.onclick = change_angle;
+
+    change_angle();
+
+    canvas.addEventListener('mousedown', onImageClick, false);
+    addEventListener('keydown', keys, false);
+    canvas.addEventListener('mousewheel', wheel_zoom, false);
+    window.onresize = canvas_resize;
+};
diff --git a/js/pano_array.js b/js/pano_array.js
new file mode 100644
index 0000000..b0e50a2
--- /dev/null
+++ b/js/pano_array.js
@@ -0,0 +1,604 @@
+if (img_prefix == undefined) var img_prefix = './tiles/ttn_mediatheque/mediatheque_70';
+if (to_cap == undefined) var to_cap = 0;
+if (to_ele == undefined) var to_ele = 0;
+if (cap == undefined) var cap = 0;
+if (elevation == undefined) var elevation = 0;
+if (cap_min == undefined) var cap_min = cap;
+if (cap_max == undefined) var cap_max = cap_min+360;
+if (ref_points == undefined) var ref_points = new Array();
+if (image_loop == undefined) var image_loop = true;
+
+var canvas;
+var cntext;
+var point_list = new Array();
+var zoom;
+var to_zoom;
+var zooms = new Array();
+var prev_zm;
+var zm;
+var tile = {width:256, height:256};
+var ntiles = {x:228, y:9};
+var border_width = 5;
+var imageObj = new Array();
+
+var last  = {x:0,y:0};
+var shift = {x:0,y:0};
+var mouse = {x:0,y:0};
+var speed = {x:0,y:0};
+var canvas_pos = {x:0,y:0};
+var tmt;
+
+var test = {x:0, y:0, i:100};
+
+function nmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres naturels N.
+    return Math.floor((val%div+div)%div);   // il y a peut être plus simple, mais en attendant .... 
+}
+
+function fmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres réels positifs.
+    return (val%div+div)%div;               // il y a peut être plus simple, mais en attendant .... 
+}
+
+function distort_canvas(p, x, y) {
+    if (p == 0) distort = 0;
+    else {
+	cntext.save();
+	distort++;
+        cntext.clearRect(0, 0, canvas.width, 2*canvas.height);
+	var ratio = (canvas.width-2*distort)/canvas.width;
+	var shift = canvas.height/2*(1-ratio);
+	cntext.scale(1, ratio);
+	if (p == 1) cntext.translate(0, 0);
+	else if (p == -1) cntext.translate(0, 0);
+	draw_image(x, y);
+	cntext.restore();
+	document.getElementById('res').innerHTML = 'distort : ' + distort + ' shift ' + shift + ' ratio : ' + ratio + '<br/>';
+    }
+}
+
+function draw_image(ox, oy) {
+    ox = nmodulo(ox-canvas.width/2, zm.im.width);        // pour placer l'origine au centre du canvas 
+    oy = Math.floor(oy-canvas.height/2);                             // pas de rebouclage vertical
+
+    cntext.clearRect(0, 0, canvas.width, canvas.height);
+    cntext.fillStyle = "rgba(128,128,128,0.8)";
+    
+    if (canvas.height > zm.im.height) {
+	var fy = Math.floor((oy+canvas.height/2-zm.im.height/2)/(tile.height*zm.ntiles.y))*zm.ntiles.y;
+	if (fy < 0) fy = 0; 
+	var ly = fy + zm.ntiles.y;
+    } else {
+	var fy = Math.floor(oy/tile.height);
+	var ly = Math.floor((oy+canvas.height+tile.height-1)/tile.height+1);
+	if (fy < 0) fy = 0; 
+	if (ly > zm.ntiles.y) ly = zm.ntiles.y; 
+    }
+
+    for (var j=fy; j<ly; j++) {
+	var delta_y = (Math.floor(j/zm.ntiles.y) - Math.floor(fy/zm.ntiles.y)) * (tile.height - zm.last_tile.height);
+	var dy = j*tile.height - oy - delta_y;
+	var ny = j%ntiles.y;
+	var wy = zm.tile.width;
+	if (ny == zm.ntiles.y - 1) wy = zm.last_tile.height;
+
+	var cpx = 0;
+	var i = 0;
+	var Nx = zm.ntiles.x;
+	while (cpx < ox+canvas.width) {
+	    var cur_width = zm.tile.width;
+	    if (i%Nx == zm.ntiles.x-1) cur_width = zm.last_tile.width;
+	    if (cpx >= ox-cur_width) {
+		var nx = i%Nx;
+		load_image(nx, ny, cpx-ox, dy, ox, oy);
+	    }
+	    cpx += cur_width;
+	    i++;
+	}
+    }
+    drawDecorations(ox, oy);
+}
+
+function load_image(nx, ny, ox, oy, x, y) {
+    var idx = nx+'-'+ny+'-'+zoom;
+    if (imageObj[idx] && imageObj[idx].complete) {
+	draw_tile(idx, ox, oy); // l'image est déja en mémoire, on force le dessin sans attendre.
+    } else {
+	var fname = get_file_name(nx, ny, zoom);
+	imageObj[idx] = new Image();
+	imageObj[idx].src = fname;
+	var ts = zm.get_tile_size(nx, ny);
+	cntext.fillRect(ox, oy, ts.width, ts.height);
+	var dx = shift.x;
+	var dy = shift.y;
+	imageObj[idx].addEventListener('load', function() {draw_tile_del(zoom, dx, dy, idx, ox, oy, x, y)}, false);
+    }
+}
+
+function draw_tile_del(ezoom, dx, dy, idx, ox, oy, x, y) {
+    if (ezoom == zoom && dx == shift.x && dy == shift.y) {
+	draw_tile(idx, ox, oy);
+	drawDecorations(x, y);
+    }
+}
+
+function draw_tile(idx, ox, oy) {
+    var img = imageObj[idx];
+    cntext.drawImage(img, ox, oy);
+}
+
+function drawDecorations(ox, oy) {
+    var wgrd = zm.im.width/360;
+    var od = ((ox+canvas.width/2)/wgrd)%360;
+    var el = (zm.im.height/2 - (oy+canvas.height/2))/wgrd;
+//    document.getElementById('angle_ctrl').value = od.toFixed(2);
+//    document.getElementById('elvtn_ctrl').value = el.toFixed(2);
+    cntext.fillStyle = "rgba(0,128,128,0.9)";
+    cntext.strokeStyle = "rgb(255,255,255)";
+    cntext.lineWidth = 1;
+    cntext.fillRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    cntext.strokeRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    document.getElementById('res').innerHTML = '';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	var cx = nmodulo(zm.pt_list[i]['xc'] - ox, zm.im.width);
+	var cy = zm.pt_list[i]['yc'] - oy;
+	if (zm.pt_list[i]['lnk'] != undefined) cntext.fillStyle = "rgba(255,128,128,0.5)";
+	else cntext.fillStyle = "rgba(128,255,128,0.5)";
+	cntext.beginPath();
+	cntext.arc(cx, cy, 20, 0, 2*Math.PI, true);
+	cntext.fill();
+	document.getElementById('res').innerHTML += 'cx : ' + cx + ' cy ' + cy + ' lnk : ' + zm.pt_list[i]['lnk'] + '<br/>';
+    }
+
+    //cntext.font = "20pt Arial";
+    //cntext.fillRect(0, 0, 200, 20);
+    //cntext.fillStyle = "rgb(255,0,0)";
+    //cntext.fillText(od.toFixed(2), 5, 20);
+    //for (i=0; i<canvas.width/wgrd; i++) {
+	//cntext.strokeRect(i*wgrd, 0, wgrd, 20);
+    //}
+}
+
+function get_file_name(x, y, z) { // recherche du fichier correspondant au zoom et à la position
+    var prm = [z, x, y];
+    var fname = img_prefix;
+    for (var i = 0; i < prm.length; i++) {
+	fname += '_';
+	if (prm[i] < 10) fname += '00';
+	else if (prm[i] < 100) fname += '0';
+	fname += prm[i];
+    }
+    fname += '.jpg';
+    return fname;
+}
+
+function keys(key) {
+    event.preventDefault();
+    event.stopPropagation();
+    if (!key) {
+	key = event;
+	key.which = key.keyCode;
+    }
+    evt = key || event;
+//    alert(key);
+//    if (!evt.shiftKey) return;
+    switch (key.which) {
+    case 36: // home
+	test.x=0;
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 66: // b
+	alert(key.pageX);
+	test.x=tile.width*(ntiles.x-3);
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 67: // c
+	test.x=0;
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    case 35: // end
+	test.x=tile.width*(ntiles.x-3);
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    case 39: // left
+	test.x+=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 40: // up
+	test.y+=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 37: // right
+	test.x-=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 38: // down
+	test.y-=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 33: // pageup
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 34: // pagedown
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    default:
+	//alert(key.which)
+	return;
+    }
+}
+
+function onImageClick(e) {
+    shift.x = last.x;
+    shift.y = last.y;
+    speed.x = 0;
+    speed.y = 0;
+    mouse.x = e.pageX;
+    mouse.y = e.pageY;
+    clearTimeout(tmt);  //arrêt de l'éffet eventuel d'amorti en cours.
+    canvas.addEventListener('mousemove', stickImage, false);
+    canvas.addEventListener('mouseup', launchImage, false);
+    //canvas.addEventListener('mouseout', launchImage, false);
+    canvas.style.cursor='move';
+    //document.onmousemove = stickImage;
+    document.onmouseup = launchImage;
+    hide_links();
+}
+
+
+function stickImage(e) {
+    var xs = mouse.x - e.pageX + shift.x;
+    var ys = mouse.y - e.pageY + shift.y;
+    speed.x = xs - last.x;  //mémorisation des vitesses horizontales
+    speed.y = ys - last.y;  //et verticales lors de ce déplacement
+    putImage(xs, ys);
+}
+
+function launchImage(e) {
+    distort_canvas(0);
+    canvas.removeEventListener('mousemove', stickImage, false);
+    show_links();
+    //document.onmousemove = null;
+    shift.x = e.pageX - mouse.x + shift.x;
+    shift.y = e.pageY - mouse.y + shift.y;
+    tmt = setTimeout(inertialImage, 100);
+}
+
+function putImage(x, y) { // est destiné à permettre l'effet d'amortissement par la mémorisation de la position courante.
+    if (!zm.is_updated) return; 
+    if (x >= zm.im.width) {   // rebouclage horizontal
+	shift.x -= zm.im.width;
+	x -= zm.im.width;
+    } else if (x < 0) {
+	shift.x += zm.im.width;
+	x += zm.im.width;
+    }
+    if (y >= zm.im.height) {   // pas de rebouclage vertical mais blocage
+	//distort_canvas(1, x, y);
+	shift.y = zm.im.height-1;
+	y = zm.im.height-1;
+    } else if (y < 0) {
+	//distort_canvas(-1, x, y);
+	shift.y = 0;
+	y = 0;
+    }
+
+    draw_image(x, y);
+    last.x = x;
+    last.y = y;
+}
+
+function inertialImage() {
+    speed.x *= 0.9;
+    speed.y *= 0.9;
+    if (Math.abs(speed.x) > 2 || Math.abs(speed.y) > 2) {
+	putImage(last.x+speed.x, last.y+speed.y);
+	tmt = setTimeout(inertialImage, 100);
+    }
+}
+
+function tri_ref_points(v1, v2) {
+    return v1['x'] - v2['x'];
+}
+
+
+
+function tzoom(zv) {
+    this.ref_pixels = new Array;
+    this.value = zv;
+    this.ntiles = {x:0,y:0};
+    this.tile = {width:0,height:0};
+    this.last_tile = {width:0,height:0};
+    this.max_tile = {width:0,height:0};
+    this.im = {width:0,height:0};
+    this.pt_list = new Array();
+    this.is_updated = false;
+
+    this.refresh = function() {
+	this.im.visible_width = this.tile.width*(this.ntiles.x-1)+this.last_tile.width;
+	this.is_updated = true;
+
+	this.im.width = this.im.visible_width;
+	this.im.height = this.tile.height*(this.ntiles.y-1)+this.last_tile.height;
+	if (this.last_tile.width > this.tile.width) this.max_tile.width = this.im.last_tile.width;
+	else this.max_tile.width = this.tile.width;
+	if (this.last_tile.height > this.tile.height) this.max_tile.height = this.im.last_tile.height;
+	else this.max_tile.height = this.tile.height;
+
+	this.ref_pixels[0] = new Array();    // Attention il faut compter un intervalle de plus !
+	ord_pts = new Array();
+	i=0;
+	for(var label in ref_points) {
+	    ord_pts[i] = ref_points[label]
+	}
+	ord_pts = ord_pts.sort(tri_ref_points);
+
+	for (var i=0; i < ord_pts.length; i++) {
+	    this.ref_pixels[i+1] = new Array();
+	    if (i != ord_pts.length-1) {
+		this.ref_pixels[i+1]['ratio_x'] = (ord_pts[i+1]['x'] - ord_pts[i]['x'])/fmodulo(ord_pts[i+1]['cap'] - ord_pts[i]['cap'], 360)*this.im.width;
+		this.ref_pixels[i+1]['ratio_y'] = (ord_pts[i+1]['y'] - ord_pts[i]['y'])/fmodulo(ord_pts[i+1]['ele'] - ord_pts[i]['ele'], 360);
+	    }
+	    this.ref_pixels[i+1]['x'] = Math.floor(ord_pts[i]['x']*this.im.width);
+	    this.ref_pixels[i+1]['cap'] = ord_pts[i]['cap'];
+	    this.ref_pixels[i+1]['y'] = Math.floor(ord_pts[i]['y']*this.im.height);
+	    this.ref_pixels[i+1]['ele'] = ord_pts[i]['ele'];
+	}
+	if (image_loop == true) {
+	    var dpix = this.im.width;
+	    var dangle = 360;
+	    if (ord_pts.length > 1) {
+		dpix = zm.im.width - this.ref_pixels[this.ref_pixels.length-1]['x'] + this.ref_pixels[1]['x'];
+		dangle = fmodulo(this.ref_pixels[1]['cap'] - this.ref_pixels[this.ref_pixels.length-1]['cap'], 360);
+	    }
+	    this.ref_pixels[0]['ratio_x'] = dpix/dangle;
+	    this.ref_pixels[ord_pts.length]['ratio_x'] = this.ref_pixels[0]['ratio_x'];
+	    this.ref_pixels[ord_pts.length]['ratio_y'] = this.ref_pixels[0]['ratio_y'];
+	    dpix = this.im.width - this.ref_pixels[ord_pts.length]['x'];
+	    this.ref_pixels[0]['cap'] = this.ref_pixels[ord_pts.length]['cap'] + dpix / this.ref_pixels[0]['ratio_x'];
+	} else {
+	    this.ref_pixels[0]['ratio_x'] = this.ref_pixels[1]['ratio_x'];
+	    this.ref_pixels[ord_pts.length]['ratio_x'] = this.ref_pixels[ord_pts.length-1]['ratio_x'];
+	    this.ref_pixels[0]['ratio_y'] = this.ref_pixels[1]['ratio_y'];
+	    this.ref_pixels[ord_pts.length]['ratio_y'] = this.ref_pixels[ord_pts.length-1]['ratio_y'];
+	    this.ref_pixels[0]['cap'] = this.ref_pixels[1]['cap'] - this.ref_pixels[1]['x'] / this.ref_pixels[1]['ratio_x'];
+	}
+	this.ref_pixels[0]['x'] = 0;
+	this.ref_pixels[0]['y'] = 0;
+	this.ref_pixels[0]['ele'] = 0;
+
+
+	for (var i=0; i<point_list.length; i++) {
+	    this.pt_list[i] = new Array();
+	    this.pt_list[i]['angle'] = point_list[i][2];
+	    this.pt_list[i]['label'] = point_list[i][0];
+	    this.pt_list[i]['xc'] = Math.floor(this.get_pxx(point_list[i][2], 360));
+//	    var tmp = fmodulo(point_list[i][2], 360);
+//	    this.pt_list[i]['xc'] = Math.floor(tmp * this.im.width/360);
+	    this.pt_list[i]['yc'] = Math.floor(this.im.height/2 - (point_list[i][3] + elevation) * this.im.width/360);
+	    if (point_list[i][4] != '') this.pt_list[i]['lnk'] = point_list[i][4]+'&to_zoom='+zv;
+	}
+    }
+    
+    this.get_tile_size = function(nx, ny) {
+	var res = {width:0, height:0};
+	if (nx == this.ntiles.x-1) res.width = this.last_tile.width;
+	else res.width = this.tile.width;
+	if (ny == this.ntiles.y-1) res.height = this.last_tile.height;
+	else res.height = this.tile.height;
+	return res;
+    }
+    
+    this.get_cap = function(px) {                         // recherche du cap à partir d'un pixel.
+	for (var i=0; i < this.ref_pixels.length; i++) {
+	    if (i == this.ref_pixels.length - 1 || px < this.ref_pixels[i+1]['x']) {
+		return fmodulo(this.ref_pixels[i]['cap']+(px-this.ref_pixels[i]['x'])/this.ref_pixels[i]['ratio_x'], 360);
+	    }
+	}
+    }
+    
+    this.get_pxx = function(cap) {                        // recherche du pixel à partir d'un cap.
+	var dcap = fmodulo(cap-this.ref_pixels[0]['cap'], 360);
+	for (var i=0; i < this.ref_pixels.length; i++) {
+	    if (i == this.ref_pixels.length - 1 || dcap < fmodulo(this.ref_pixels[i+1]['cap']-this.ref_pixels[0]['cap'], 360)) {
+		return this.ref_pixels[i]['x'] + this.ref_pixels[i]['ratio_x']*fmodulo(cap - this.ref_pixels[i]['cap'], 360);
+	    }
+	}
+    }
+}
+
+function wheel_zoom (event) {
+    var zshift = {x:0, y:0};
+    if (event.pageX != undefined && event.pageX != undefined) {
+	zshift.x = event.pageX-canvas.width/2-canvas_pos.x;
+	zshift.y = event.pageY-canvas.height/2-canvas_pos.y;
+    }
+    event.preventDefault();
+    if (event.wheelDelta < 0 && zoom_control.value < zoom_control.max) {
+	zoom_control.value++;
+	change_zoom(zshift.x, zshift.y);
+    } else if (event.wheelDelta > 0 && zoom_control.value > zoom_control.min) {
+	zoom_control.value--; 
+	change_zoom(zshift.x, zshift.y);
+    }
+}
+
+function change_zoom(shx, shy) {
+    var zoom_control = document.getElementById("zoom_ctrl");
+    var v = zoom_control.value;
+
+    prev_zm = zm;
+
+    if (zooms[v]) {
+	if (!zooms[v].is_updated) zooms[v].refresh();
+    } else {
+	zooms[v] = new tzoom(v);
+    }
+
+    if (zooms[v].is_updated) {
+	if (shx == undefined || shy == undefined) {
+	    shx=0;
+	    shy=0;
+	}
+	zm = zooms[v];
+	var px = (last.x+shx)*zm.im.width/prev_zm.im.width - shx;
+	var py = (last.y+shy)*zm.im.height/prev_zm.im.height - shy;
+	if (py < zm.im.height && py >= 0) {
+	    zoom = zm.value;
+	    tile = zm.tile;
+	    ntiles = zm.ntiles;
+	    putImage(px, py);
+	} else {
+	    zm = prev_zm;
+	    zoom_control.value = zm.value;
+	}
+    }
+}
+
+function change_angle() {
+    var elvtn_control = document.getElementById('elvtn_ctrl');
+    var angle_control = document.getElementById('angle_ctrl');
+    var pos_x = zm.im.width*angle_control.value/360;
+    var pos_y = Math.floor(zm.im.height/2 - zm.im.width*elvtn_control.value/360);
+    putImage(pos_x, pos_y);
+}
+
+function check_links(e) {
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (Math.sqrt((zm.pt_list[i]['xc'] - pos_x) * (zm.pt_list[i]['xc'] - pos_x) + (zm.pt_list[i]['yc'] - pos_y) * (zm.pt_list[i]['yc'] - pos_y)) < 20) {
+	    if (zm.pt_list[i]['lnk'] != undefined) window.location = zm.pt_list[i]['lnk'];
+	    break;
+	}
+    }
+}
+
+function display_links(e) {
+    var info = document.getElementById('info');
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    //var cap = ((pos_x/zm.im.width)*360).toFixed(2);
+    var cap = zm.get_cap(pos_x).toFixed(2);
+    var elev = (((zm.im.height/2 - pos_y)/zm.im.width)*360 - elevation).toFixed(2);
+    info.innerHTML = 'élévation :'+elev+'<br/>cap :'+cap;
+    info.style.top = e.pageY+'px';
+    info.style.left = e.pageX+'px';
+    info.style.backgroundColor = '#FFC';
+    canvas.style.cursor='crosshair';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (Math.sqrt((zm.pt_list[i]['xc'] - pos_x) * (zm.pt_list[i]['xc'] - pos_x) + (zm.pt_list[i]['yc'] - pos_y) * (zm.pt_list[i]['yc'] - pos_y)) < 20) {
+	    info.innerHTML = zm.pt_list[i]['label'];
+	    info.style.backgroundColor = '#FC8';
+	    canvas.style.cursor='auto';
+	    break;
+	}
+    }
+}
+
+function hide_links() {
+    canvas.removeEventListener( "mousemove", display_links, false);
+    var info = document.getElementById('info');
+    info.style.display = 'none';
+}
+
+function show_links() {
+    canvas.addEventListener( "mousemove", display_links, false);
+    var info = document.getElementById('info');
+    info.style.display = 'block';
+}
+
+function show_insert_point(e) {
+    event.preventDefault();
+    event.stopPropagation();
+    var insrt = document.getElementById('insert');
+    document.getElementById('do-insert').onclick = function() {insert_point(insert, e.pageX-canvas_pos.x, e.pageY-canvas_pos.y)};
+    document.getElementById('do-cancel').onclick = function() {insert.style.display = 'none'};
+    insrt.style.left = e.pageX+'px';
+    insrt.style.top = e.pageY+'px';
+    insrt.style.display = 'block';
+}
+
+function insert_point(el, x, y) {
+    el.style.display = 'none';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	var label = zm.pt_list[i]['label'];
+	if(label == document.getElementById('sel_point').value) {
+	    var posx = nmodulo(last.x + x - canvas.width/2, zm.im.width)/zm.im.width;
+	    var posy = 0;
+	    var pval = {x:posx, cap:zm.pt_list[i]['angle'], y:posy, label:label};
+	    ref_points[label] = pval;
+	    document.getElementById('res').innerHTML += zm.pt_list[i]['label'] + '. ' + posx + '=' + zm.pt_list[i]['angle'] + '<br/>';
+ 	    putImage(last.x, last.y);
+
+//	    if (ref_points.length > 1) {
+//		var pval0 = inserted_points[inserted_points.length-2];
+//		var origin = fmodulo(pval0.angle - ((pval.angle - pval0.angle)/(pval.ratio - pval0.ratio))*pval0.ratio, 360);
+//		var end = fmodulo(pval.angle + ((pval.angle - pval0.angle)/(pval.ratio - pval0.ratio))*(1-pval.ratio), 360);
+//		document.getElementById('res').innerHTML += 'cap_min = ' + origin + '<br/>';
+//		document.getElementById('res').innerHTML += 'cap_max = ' + end + '<br/>';
+//	    }
+	    break;
+	}
+    }
+}
+
+function clean_canvas_events(e) {
+    canvas.removeEventListener('mousemove', stickImage, false);
+    document.getElementById('info').style.display = 'none';
+    speed.x = 0;
+    speed.y = 0;
+}
+
+window.onload = function() {
+    canvas = document.getElementById("mon-canvas");
+    canvas.style.border = border_width+"px solid red";
+    cntext = canvas.getContext("2d");
+    canvas.width = window.innerWidth-200;
+    canvas.height = window.innerHeight-20;
+    canvas.addEventListener("click", check_links, false);
+    canvas_pos.x = canvas.offsetLeft+border_width;
+    canvas_pos.y = canvas.offsetTop+border_width;
+    canvas.addEventListener("contextmenu", show_insert_point, false);
+    canvas.addEventListener("mouseout", clean_canvas_events, false);
+
+    show_links();
+
+    var max_zoom = zooms.length - 1;
+    zoom_control = document.getElementById("zoom_ctrl");
+    zoom_control.onchange = change_zoom;
+    zoom_control.max = max_zoom;
+    if (to_zoom == undefined || to_zoom > max_zoom) to_zoom = Math.floor(max_zoom/2);
+    zm = zooms[to_zoom];
+    zoom_control.value = to_zoom;
+    zm.refresh();
+
+    zoom = zm.value;
+    tile = zm.tile;
+    ntiles = zm.ntiles;
+
+    angle_control = document.getElementById("angle_ctrl");
+    angle_control.value = to_cap;
+    angle_control.onchange = change_angle;
+    angle_control.onclick = change_angle;
+    angle_control.onkeyup = change_angle;
+    elvtn_control = document.getElementById("elvtn_ctrl");
+    elvtn_control.value = to_ele+elevation;
+    elvtn_control.onchange = change_angle;
+    elvtn_control.onclick = change_angle;
+    elvtn_control.onkeyup = change_angle;
+
+    change_angle();
+
+    canvas.addEventListener('mousedown', onImageClick, false);
+    addEventListener('keyup', keys, false);
+    canvas.addEventListener('mousewheel', wheel_zoom, false);
+};
diff --git a/js/pano_old.js b/js/pano_old.js
new file mode 100644
index 0000000..6061d58
--- /dev/null
+++ b/js/pano_old.js
@@ -0,0 +1,539 @@
+if (img_prefix == undefined) var img_prefix = './tiles/ttn_mediatheque/mediatheque_70';
+if (to_cap == undefined) var to_cap = 0;
+if (to_ele == undefined) var to_ele = 0;
+if (cap == undefined) var cap = 0;
+if (elevation == undefined) var elevation = 0;
+if (cap_min == undefined) var cap_min = cap;
+if (cap_max == undefined) var cap_max = cap_min+360;
+
+var inserted_points = new Array();
+var canvas;
+var cntext;
+var point_list = new Array();
+var zoom;
+var to_zoom;
+var zooms = new Array();
+var prev_zm;
+var zm;
+var tile = {width:256, height:256};
+var ntiles = {x:228, y:9};
+var border_width = 5;
+var imageObj = new Array();
+
+var last  = {x:0,y:0};
+var shift = {x:0,y:0};
+var mouse = {x:0,y:0};
+var speed = {x:0,y:0};
+var canvas_pos = {x:0,y:0};
+var tmt;
+
+var test = {x:0, y:0, i:100};
+
+function nmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres naturels N.
+    return Math.floor((val%div+div)%div);   // il y a peut être plus simple, mais en attendant .... 
+}
+
+function fmodulo(val, div) {                // pour obtenir un modulo dans l'espace des nombres réels positifs.
+    return (val%div+div)%div;               // il y a peut être plus simple, mais en attendant .... 
+}
+
+function distort_canvas(p, x, y) {
+    if (p == 0) distort = 0;
+    else {
+	cntext.save();
+	distort++;
+        cntext.clearRect(0, 0, canvas.width, 2*canvas.height);
+	var ratio = (canvas.width-2*distort)/canvas.width;
+	var shift = canvas.height/2*(1-ratio);
+	cntext.scale(1, ratio);
+	if (p == 1) cntext.translate(0, 0);
+	else if (p == -1) cntext.translate(0, 0);
+	draw_image(x, y);
+	cntext.restore();
+	document.getElementById('res').innerHTML = 'distort : ' + distort + ' shift ' + shift + ' ratio : ' + ratio + '<br/>';
+    }
+}
+
+function draw_image(ox, oy) {
+    ox = nmodulo(ox-canvas.width/2, zm.im.width);        // pour placer l'origine au centre du canvas 
+    var ocx = nmodulo(ox - zm.phase_pix, zm.im.width);
+    //document.getElementById('res').innerHTML = zm.phase_pix + ' -- ' + zm.im.width + ' shift(x,y) ' + shift.x;
+    oy = Math.floor(oy-canvas.height/2);                             // pas de rebouclage vertical
+
+    cntext.clearRect(0, 0, canvas.width, canvas.height);
+    cntext.fillStyle = "rgba(128,128,128,0.8)";
+    
+    if (canvas.height > zm.im.height) {
+	var fy = Math.floor((oy+canvas.height/2-zm.im.height/2)/(tile.height*zm.ntiles.y))*zm.ntiles.y;
+	if (fy < 0) fy = 0; 
+	var ly = fy + zm.ntiles.y;
+    } else {
+	var fy = Math.floor(oy/tile.height);
+	var ly = Math.floor((oy+canvas.height+tile.height-1)/tile.height+1);
+	if (fy < 0) fy = 0; 
+	if (ly > zm.ntiles.y) ly = zm.ntiles.y; 
+    }
+
+    for (var j=fy; j<ly; j++) {
+	var delta_y = (Math.floor(j/zm.ntiles.y) - Math.floor(fy/zm.ntiles.y)) * (tile.height - zm.last_tile.height);
+	var dy = j*tile.height - oy - delta_y;
+	var ny = j%ntiles.y;
+	var wy = zm.tile.width;
+	if (ny == zm.ntiles.y - 1) wy = zm.last_tile.height;
+
+	var cpx = 0;
+	var i = 0;
+	var Nx = zm.ntiles.x;
+	if (zm.im.unused_width != 0) Nx++;
+	while (cpx < ocx+canvas.width) {
+	    var cur_width = zm.tile.width;
+	    if (i%Nx == zm.ntiles.x-1) cur_width = zm.last_tile.width;
+	    else if (i%Nx == Nx-1) cur_width = zm.im.unused_width;
+	    if (cpx >= ocx-cur_width) {
+		if (i%Nx == Nx-1 && zm.im.unused_width != 0) {
+		    cntext.fillRect(cpx-ocx, dy, zm.im.unused_width, wy);
+		} else {
+		    var nx = i%Nx;
+		    load_image(nx, ny, cpx-ocx, dy, ox, oy);
+		}
+	    }
+	    cpx += cur_width;
+	    i++;
+	}
+    }
+    drawDecorations(ox, oy);
+}
+
+function load_image(nx, ny, ox, oy, x, y) {
+    var idx = nx+'-'+ny+'-'+zoom;
+    if (imageObj[idx] && imageObj[idx].complete) {
+	draw_tile(idx, ox, oy); // l'image est déja en mémoire, on force le dessin sans attendre.
+    } else {
+	var fname = get_file_name(nx, ny, zoom);
+	imageObj[idx] = new Image();
+	imageObj[idx].src = fname;
+	var ts = zm.get_tile_size(nx, ny);
+	cntext.fillRect(ox, oy, ts.width, ts.height);
+	var dx = shift.x;
+	var dy = shift.y;
+	imageObj[idx].addEventListener('load', function() {draw_tile_del(zoom, dx, dy, idx, ox, oy, x, y)}, false);
+    }
+}
+
+function draw_tile_del(ezoom, dx, dy, idx, ox, oy, x, y) {
+    if (ezoom == zoom && dx == shift.x && dy == shift.y) {
+	draw_tile(idx, ox, oy);
+	drawDecorations(x, y);
+    }
+}
+
+function draw_tile(idx, ox, oy) {
+    var img = imageObj[idx];
+    cntext.drawImage(img, ox, oy);
+}
+
+function drawDecorations(ox, oy) {
+    var wgrd = zm.im.width/360;
+    var od = ((ox+canvas.width/2)/wgrd)%360;
+    var el = (zm.im.height/2 - (oy+canvas.height/2))/wgrd;
+//    document.getElementById('angle_ctrl').value = od.toFixed(2);
+//    document.getElementById('elvtn_ctrl').value = el.toFixed(2);
+    cntext.fillStyle = "rgba(0,128,128,0.9)";
+    cntext.strokeStyle = "rgb(255,255,255)";
+    cntext.lineWidth = 1;
+    cntext.fillRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    cntext.strokeRect(canvas.width/2-5, canvas.height/2-5, 10, 10);
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	var cx = zm.pt_list[i]['xc'] - ox;
+	var cy = zm.pt_list[i]['yc'] - oy;
+	if (zm.pt_list[i]['lnk'] != undefined) cntext.fillStyle = "rgba(255,128,128,0.5)";
+	else cntext.fillStyle = "rgba(128,255,128,0.5)";
+	cntext.beginPath();
+	cntext.arc(cx, cy, 20, 0, 2*Math.PI, true);
+	cntext.fill();
+    }
+
+    //cntext.font = "20pt Arial";
+    //cntext.fillRect(0, 0, 200, 20);
+    //cntext.fillStyle = "rgb(255,0,0)";
+    //cntext.fillText(od.toFixed(2), 5, 20);
+    //for (i=0; i<canvas.width/wgrd; i++) {
+	//cntext.strokeRect(i*wgrd, 0, wgrd, 20);
+    //}
+}
+
+function get_file_name(x, y, z) { // recherche du fichier correspondant au zoom et à la position
+    var prm = [z, x, y];
+    var fname = img_prefix;
+    for (var i = 0; i < prm.length; i++) {
+	fname += '_';
+	if (prm[i] < 10) fname += '00';
+	else if (prm[i] < 100) fname += '0';
+	fname += prm[i];
+    }
+    fname += '.jpg';
+    return fname;
+}
+
+function keys(key) {
+    event.preventDefault();
+    event.stopPropagation();
+    if (!key) {
+	key = event;
+	key.which = key.keyCode;
+    }
+    evt = key || event;
+//    alert(key);
+//    if (!evt.shiftKey) return;
+    switch (key.which) {
+    case 36: // home
+	test.x=0;
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 66: // b
+	alert(key.pageX);
+	test.x=tile.width*(ntiles.x-3);
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 67: // c
+	test.x=0;
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    case 35: // end
+	test.x=tile.width*(ntiles.x-3);
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    case 39: // left
+	test.x+=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 40: // up
+	test.y+=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 37: // right
+	test.x-=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 38: // down
+	test.y-=test.i;
+	putImage(test.x, test.y);
+	return;
+    case 33: // pageup
+	test.y=0;
+	putImage(test.x, test.y);
+	return;
+    case 34: // pagedown
+	test.y=tile.height*(ntiles.y-3);
+	putImage(test.x, test.y);
+	return;
+    default:
+	//alert(key.which)
+	return;
+    }
+}
+
+function onImageClick(e) {
+    shift.x = last.x;
+    shift.y = last.y;
+    speed.x = 0;
+    speed.y = 0;
+    mouse.x = e.pageX;
+    mouse.y = e.pageY;
+    clearTimeout(tmt);  //arrêt de l'éffet eventuel d'amorti en cours.
+    canvas.addEventListener('mousemove', stickImage, false);
+    canvas.addEventListener('mouseup', launchImage, false);
+    //canvas.addEventListener('mouseout', launchImage, false);
+    canvas.style.cursor='move';
+    //document.onmousemove = stickImage;
+    document.onmouseup = launchImage;
+    hide_links();
+}
+
+
+function stickImage(e) {
+    var xs = mouse.x - e.pageX + shift.x;
+    var ys = mouse.y - e.pageY + shift.y;
+    speed.x = xs - last.x;  //mémorisation des vitesses horizontales
+    speed.y = ys - last.y;  //et verticales lors de ce déplacement
+    putImage(xs, ys);
+}
+
+function launchImage(e) {
+    distort_canvas(0);
+    canvas.removeEventListener('mousemove', stickImage, false);
+    show_links();
+    //document.onmousemove = null;
+    shift.x = e.pageX - mouse.x + shift.x;
+    shift.y = e.pageY - mouse.y + shift.y;
+    tmt = setTimeout(inertialImage, 100);
+}
+
+function putImage(x, y) { // est destiné à permettre l'effet d'amortissement par la mémorisation de la position courante.
+    if (!zm.is_updated) return; 
+    if (x >= zm.im.width) {   // rebouclage horizontal
+	shift.x -= zm.im.width;
+	x -= zm.im.width;
+    } else if (x < 0) {
+	shift.x += zm.im.width;
+	x += zm.im.width;
+    }
+    if (y >= zm.im.height) {   // pas de rebouclage vertical mais blocage
+	//distort_canvas(1, x, y);
+	shift.y = zm.im.height-1;
+	y = zm.im.height-1;
+    } else if (y < 0) {
+	//distort_canvas(-1, x, y);
+	shift.y = 0;
+	y = 0;
+    }
+
+    draw_image(x, y);
+    last.x = x;
+    last.y = y;
+}
+
+function inertialImage() {
+    speed.x *= 0.9;
+    speed.y *= 0.9;
+    if (Math.abs(speed.x) > 2 || Math.abs(speed.y) > 2) {
+	putImage(last.x+speed.x, last.y+speed.y);
+	tmt = setTimeout(inertialImage, 100);
+    }
+}
+
+function tzoom(zv) {
+    this.value = zv;
+    this.ntiles = {x:0,y:0};
+    this.tile = {width:0,height:0};
+    this.last_tile = {width:0,height:0};
+    this.max_tile = {width:0,height:0};
+    this.im = {width:0,height:0};
+    this.im.unused_width = 0;
+    this.pt_list = new Array();
+    this.is_updated = false;
+    this.refresh = function() {
+	this.im.visible_width = this.tile.width*(this.ntiles.x-1)+this.last_tile.width;
+	this.is_updated = true;
+	var cap_int = (Math.abs(cap_max - cap_min + 0.005)) % 360.0;
+	if (cap_int < 0.01) this.im.unused_width = 0;
+	else this.im.unused_width = Math.floor(fmodulo(cap_min - cap_max, 360)*this.im.visible_width/fmodulo(cap_max - cap_min, 360));
+//	document.getElementById('res').innerHTML += 'unuse_cap ' + fmodulo(cap_min - cap_max) + ' used_cap ' + fmodulo(cap_max - cap_min);
+
+	this.im.width = this.im.visible_width+this.im.unused_width;
+	this.im.height = this.tile.height*(this.ntiles.y-1)+this.last_tile.height;
+	if (this.im.unused_width > this.tile.width) this.max_tile.width = this.im.unused_width;
+	else this.max_tile.width = this.tile.width;
+	this.max_tile.height = this.tile.height;
+	this.max_tile.width = 
+	this.phase_pix = Math.floor(this.im.width*cap_min/360);
+	for (var i=0; i<point_list.length; i++) {
+	    var tmp = fmodulo(point_list[i][2], 360);
+	    this.pt_list[i] = new Array();
+	    this.pt_list[i]['label'] = point_list[i][0];
+	    this.pt_list[i]['xc'] = Math.floor(tmp * this.im.width/360);
+	    this.pt_list[i]['yc'] = Math.floor(this.im.height/2 - (point_list[i][3] + elevation) * this.im.width/360);
+	    this.pt_list[i]['angle'] = point_list[i][2];
+	    if (point_list[i][4] != '') this.pt_list[i]['lnk'] = point_list[i][4]+'&to_zoom='+zv;
+	}
+    }
+    this.get_tile_size = function(nx, ny) {
+	var res = {width:0, height:0};
+	if (nx == this.ntiles.x-1) res.width = this.last_tile.width;
+	else res.width = this.tile.width;
+	if (ny == this.ntiles.y-1) res.height = this.last_tile.height;
+	else res.height = this.tile.height;
+	return res;
+    }
+}
+
+function wheel_zoom (event) {
+    var zshift = {x:0, y:0};
+    if (event.pageX != undefined && event.pageX != undefined) {
+	zshift.x = event.pageX-canvas.width/2-canvas_pos.x;
+	zshift.y = event.pageY-canvas.height/2-canvas_pos.y;
+    }
+    event.preventDefault();
+    if (event.wheelDelta < 0 && zoom_control.value < zoom_control.max) {
+	zoom_control.value++;
+	change_zoom(zshift.x, zshift.y);
+    } else if (event.wheelDelta > 0 && zoom_control.value > zoom_control.min) {
+	zoom_control.value--; 
+	change_zoom(zshift.x, zshift.y);
+    }
+}
+
+function change_zoom(shx, shy) {
+    var zoom_control = document.getElementById("zoom_ctrl");
+    var v = zoom_control.value;
+
+    prev_zm = zm;
+
+    if (zooms[v]) {
+	if (!zooms[v].is_updated) zooms[v].refresh();
+    } else {
+	zooms[v] = new tzoom(v);
+    }
+
+    if (zooms[v].is_updated) {
+	if (shx == undefined || shy == undefined) {
+	    shx=0;
+	    shy=0;
+	}
+	zm = zooms[v];
+	var px = (last.x+shx)*zm.im.width/prev_zm.im.width - shx;
+	var py = (last.y+shy)*zm.im.height/prev_zm.im.height - shy;
+	if (py < zm.im.height && py >= 0) {
+	    zoom = zm.value;
+	    tile = zm.tile;
+	    ntiles = zm.ntiles;
+	    putImage(px, py);
+	} else {
+	    zm = prev_zm;
+	    zoom_control.value = zm.value;
+	}
+    }
+}
+
+function change_angle() {
+    var elvtn_control = document.getElementById('elvtn_ctrl');
+    var angle_control = document.getElementById('angle_ctrl');
+    var pos_x = zm.im.width*angle_control.value/360;
+    var pos_y = Math.floor(zm.im.height/2 - zm.im.width*elvtn_control.value/360);
+    putImage(pos_x, pos_y);
+}
+
+function check_links(e) {
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (Math.sqrt((zm.pt_list[i]['xc'] - pos_x) * (zm.pt_list[i]['xc'] - pos_x) + (zm.pt_list[i]['yc'] - pos_y) * (zm.pt_list[i]['yc'] - pos_y)) < 20) {
+	    if (zm.pt_list[i]['lnk'] != undefined) window.location = zm.pt_list[i]['lnk'];
+	    break;
+	}
+    }
+}
+
+function display_links(e) {
+    var info = document.getElementById('info');
+    var mouse_x = e.pageX-canvas_pos.x;
+    var mouse_y = e.pageY-canvas_pos.y;
+    var pos_x = nmodulo(last.x + mouse_x - canvas.width/2, zm.im.width);
+    var pos_y = last.y + mouse_y - canvas.height/2;
+    var cap = ((pos_x/zm.im.width)*360).toFixed(2);
+    var elev = (((zm.im.height/2 - pos_y)/zm.im.width)*360 - elevation).toFixed(2);
+    info.innerHTML = 'élévation :'+elev+'<br/>cap :'+cap;
+    info.style.top = e.pageY+'px';
+    info.style.left = e.pageX+'px';
+    info.style.backgroundColor = '#FFC';
+    canvas.style.cursor='crosshair';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if (Math.sqrt((zm.pt_list[i]['xc'] - pos_x) * (zm.pt_list[i]['xc'] - pos_x) + (zm.pt_list[i]['yc'] - pos_y) * (zm.pt_list[i]['yc'] - pos_y)) < 20) {
+	    info.innerHTML = zm.pt_list[i]['label'];
+	    info.style.backgroundColor = '#FC8';
+	    canvas.style.cursor='auto';
+	    break;
+	}
+    }
+}
+
+function hide_links() {
+    canvas.removeEventListener( "mousemove", display_links, false);
+    var info = document.getElementById('info');
+    info.style.display = 'none';
+}
+
+function show_links() {
+    canvas.addEventListener( "mousemove", display_links, false);
+    var info = document.getElementById('info');
+    info.style.display = 'block';
+}
+
+function show_insert_point(e) {
+    event.preventDefault();
+    event.stopPropagation();
+    var insrt = document.getElementById('insert');
+    document.getElementById('do-insert').onclick = function() {insert_point(insert, e.pageX-canvas_pos.x, e.pageY-canvas_pos.y)};
+    document.getElementById('do-cancel').onclick = function() {insert.style.display = 'none'};
+    insrt.style.left = e.pageX+'px';
+    insrt.style.top = e.pageY+'px';
+    insrt.style.display = 'block';
+}
+
+function insert_point(el, x, y) {
+    el.style.display = 'none';
+    for(var i = 0; i < zm.pt_list.length; i++) {
+	if(zm.pt_list[i]['label'] == document.getElementById('sel_point').value) {
+	    var posx = nmodulo(last.x + x - canvas.width/2 - zm.phase_pix, zm.im.width)/zm.im.width;
+	    var pval = {ratio:posx, angle:zm.pt_list[i]['angle']};
+	    inserted_points.push(pval);
+	    document.getElementById('res').innerHTML += zm.pt_list[i]['label'] + '. ' + posx + '=' + zm.pt_list[i]['angle'] + '<br/>';
+	    if (inserted_points.length > 1) {
+		var pval0 = inserted_points[inserted_points.length-2];
+		var origin = fmodulo(pval0.angle - ((pval.angle - pval0.angle)/(pval.ratio - pval0.ratio))*pval0.ratio, 360);
+		var end = fmodulo(pval.angle + ((pval.angle - pval0.angle)/(pval.ratio - pval0.ratio))*(1-pval.ratio), 360);
+		document.getElementById('res').innerHTML += 'cap_min = ' + origin + '<br/>';
+		document.getElementById('res').innerHTML += 'cap_max = ' + end + '<br/>';
+	    }
+	    break;
+	}
+    }
+}
+
+function clean_canvas_events(e) {
+    canvas.removeEventListener('mousemove', stickImage, false);
+    document.getElementById('info').style.display = 'none';
+    speed.x = 0;
+    speed.y = 0;
+}
+
+window.onload = function() {
+    canvas = document.getElementById("mon-canvas");
+    canvas.style.border = border_width+"px solid red";
+    cntext = canvas.getContext("2d");
+    canvas.width = window.innerWidth-200;
+    canvas.height = window.innerHeight-20;
+    canvas.addEventListener("click", check_links, false);
+    canvas_pos.x = canvas.offsetLeft+border_width;
+    canvas_pos.y = canvas.offsetTop+border_width;
+    canvas.addEventListener("contextmenu", show_insert_point, false);
+    canvas.addEventListener("mouseout", clean_canvas_events, false);
+
+    show_links();
+
+    var max_zoom = zooms.length - 1;
+    zoom_control = document.getElementById("zoom_ctrl");
+    zoom_control.onchange = change_zoom;
+    zoom_control.max = max_zoom;
+    if (to_zoom == undefined || to_zoom > max_zoom) to_zoom = Math.floor(max_zoom/2);
+    zm = zooms[to_zoom];
+    zoom_control.value = to_zoom;
+    zm.refresh();
+
+    zoom = zm.value;
+    tile = zm.tile;
+    ntiles = zm.ntiles;
+
+    angle_control = document.getElementById("angle_ctrl");
+    angle_control.value = to_cap;
+    angle_control.onchange = change_angle;
+    angle_control.onclick = change_angle;
+    angle_control.onkeyup = change_angle;
+    elvtn_control = document.getElementById("elvtn_ctrl");
+    elvtn_control.value = to_ele+elevation;
+    elvtn_control.onchange = change_angle;
+    elvtn_control.onclick = change_angle;
+    elvtn_control.onkeyup = change_angle;
+
+    change_angle();
+
+    canvas.addEventListener('mousedown', onImageClick, false);
+    addEventListener('keyup', keys, false);
+    canvas.addEventListener('mousewheel', wheel_zoom, false);
+};
diff --git a/panorama.php b/panorama.php
new file mode 100644
index 0000000..b2b37b7
--- /dev/null
+++ b/panorama.php
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
+<head>
+  <?php
+   require 'class/site_point.class.php';
+   require 'class/sites_dir.class.php';
+
+   if (isset($_GET['dir']) && isset($_GET['panorama'])) {
+     $dir   = $_GET['dir'];
+     $name  = $_GET['panorama'];
+   } else {
+     $dir   = 'tiles';
+     $name  = 'ttn_mediatheque';
+   }
+   $opt_vals = array();
+   foreach(array('to_cap', 'to_ele', 'to_zoom') as $val) {
+     if (!empty($_GET[$val])) $opt_vals[$val] = $_GET[$val];
+   }
+
+   $base_dir = $dir.'/'.$name;
+   $pt = new site_point($base_dir);
+   if(!$pt) die("impossible d'accéder à ".$base_dir." !\n");
+   $params = $pt->get_params();
+   $prefix = $pt->get_prefix();
+  ?>
+  <meta http-equiv="Content-type" content="text/html; charset=UTF-8"/>
+   <?php
+     $titre = 'panorama';
+     if ($params && isset($params['titre'])) $titre .= ' ; '.$params['titre'];
+     printf ("<title>%s</title>\n", $params['titre']);
+  ?>
+  <script>
+   <?php
+     printf ("var img_prefix = '%s/%s'\n", $base_dir, $prefix);
+     if (is_array($params)) $opt_vals = array_merge($params, $opt_vals);
+     foreach(array('to_cap', 'to_ele', 'to_zoom', 'image_loop') as $val) {
+       if (isset($opt_vals[$val])) printf ('var '.$val.' = '.$opt_vals[$val].";\n"); // correction du décalage angulaire par rapport au Nord
+     }
+  ?>
+  </script>
+  <script src="js/pano.js"></script>
+  <script>
+  <?php
+     $zoom_array = $pt->get_magnifications();
+     foreach($zoom_array as $zoom => $val) {
+       echo "zooms[$zoom] = new tzoom($zoom);\n";
+       echo "zooms[$zoom].ntiles.x = ".$val['nx'].";\n";
+       echo "zooms[$zoom].ntiles.y = ".$val['ny'].";\n";
+       $size = getimagesize(sprintf($base_dir.'/'.$prefix.'_%03d_%03d_%03d.jpg', $zoom, 0, 0));
+       echo "zooms[$zoom].tile.width = ".$size[0].";\n";
+       echo "zooms[$zoom].tile.height = ".$size[1].";\n";
+       $size = getimagesize(sprintf($base_dir.'/'.$prefix.'_%03d_%03d_%03d.jpg', $zoom, $val['nx']-1, $val['ny']-1));
+       echo "zooms[$zoom].last_tile.width = ".$size[0].";\n";
+       echo "zooms[$zoom].last_tile.height = ".$size[1].";\n";
+     }
+
+   $dir_list = new sites_dir($dir);
+
+   $ipt = 0;
+   $scrname = getenv('SCRIPT_NAME');
+   foreach($dir_list->get_sites() as $opt) {
+     $prm = $opt->get_params();
+     $oname = $opt->get_name();
+     if ($oname != $name && isset($prm['latitude']) && isset($prm['longitude']) && isset($prm['altitude']) && isset($prm['titre'])) {
+       list($dist, $cap, $ele) = $pt->coordsToCap($prm['latitude'], $prm['longitude'], $prm['altitude']);
+       $lnk = sprintf("%s?dir=%s&panorama=%s&to_cap=%.3f&to_ele=%.3f", $scrname, $dir, $oname, $cap + 180, -$ele);
+       printf('point_list[%d] = new Array("%s", %03lf, %03lf, %03lf, "%s");'."\n", $ipt++, $prm['titre'], $dist, $cap, $ele, $lnk);
+     }
+   }
+
+   include 'ref_points.php';
+   $extra_names = array();
+   $ref_names = array();
+   if (is_array($ref_points)) {
+     foreach ($ref_points as $name => $vals) {
+       $extra_names[] = $name;
+       list($dist, $cap, $ele) = $pt->coordsToCap($vals[0], $vals[1], $vals[2]);
+       $ref_names[$name] = array($dist, $cap, $ele);
+       printf('point_list[%d] = new Array("%s", %03lf, %03lf, %03lf, "");'."\n", $ipt++, $name, $dist, $cap, $ele);
+     }
+   }
+
+   if (isset($params['reference'])) {
+     echo "ref_points = new Array();\n";
+     foreach ($params['reference'] as $nm => $val) {
+       if (isset($ref_names[$nm])) {
+	 list($dist, $cap, $ele) = $ref_names[$nm];
+	 list($px, $py) = explode(',', $val);
+	 printf("ref_points[\"%s\"] = {x:%.5f, cap:%.5f, y:%.5f, ele:%.5f};\n", $nm, $px, $cap, $py, $ele);
+       }
+     }
+   }
+  ?>
+  </script>
+  <link type="image/x-icon" rel="shortcut icon" href="images/tsf.png"/>
+  <link rel="stylesheet" media="screen" href="css/all.css" />
+</head>
+<body>
+  <canvas id="mon-canvas">
+    Ce message indique que ce navigateurs est vétuste car il ne supporte pas <samp>canvas</samp> (IE6, IE7, IE8, ...)
+  </canvas>
+  <fieldset id="control"><legend>contrôle</legend>
+      <label>Zoom : <input type="range" min="0" max="2" value="2" id="zoom_ctrl"/></label>
+      <label>Cap : <input type="number" min="0" max="360" step="10" value="0" autofocus="" id="angle_ctrl"/></label>
+      <label>Élévation : <input type="number" min="-90" max="90" step="1" value="0" autofocus="" id="elvtn_ctrl"/></label>
+  </fieldset>
+
+  <?php
+      //phpinfo();exit;
+     if ($params && isset($params['latitude']) && isset($params['longitude'])) {
+       print("<div id=\"params\">\n");
+       printf ("<p>latitude :   <em>%.3f°</em></p>\n", $params['latitude']);
+       printf ("<p>longitude : <em>%.3f°</em></p>\n", $params['longitude']);
+       if (isset($params['altitude'])) printf ("<p>altitude : <em>%d m</em></p>\n", $params['altitude']);
+       print("</div>\n");
+     }
+     echo '<p id="info"></p>'."\n";
+     if (count($extra_names) > 1) {
+       echo "<p id=\"insert\">\n<select id=\"sel_point\" name=\"known_points\">\n";
+       foreach ($extra_names as $nm) {
+	 echo '<option>'.$nm."</option>\n";
+       }
+       echo "</select>\n<br/>";
+       echo "<input type=\"button\" id=\"do-insert\" value=\"insérer\"/>\n";
+       echo "<input type=\"button\" id=\"do-delete\" value=\"suppimer\"/>\n";
+       echo "<input type=\"button\" id=\"do-cancel\" value=\"annuler\"/>\n";
+       echo "</p>\n";
+     }
+  ?> 
+  <p class="validators"><a href="http://validator.w3.org/check?uri=referer">page xHTML validé !</a></p>
+  <p id="res"></p>
+</body>
+</html>
diff --git a/ref_points.php b/ref_points.php
new file mode 100644
index 0000000..8fa683c
--- /dev/null
+++ b/ref_points.php
@@ -0,0 +1,14 @@
+<?php
+$ref_points = array (
+		     'Clocher de Saint-Sernin' => array(43.6085, 1.4424, 190),
+		     'Clocher des Minimes' => array(43.61811, 1.43639, 165),
+		     "Obélisque de l'observatoire" => array(43.6114, 1.4618, 200),
+		     "Pic d'Aneto" => array(42.63055, 0.65695, 3404),
+                     "Pic de Montcalm" => array(42.672118, 1.406422, 3077),
+                     "Pic du Carlit" => array(42.569865, 1.932092, 2921),
+                     "Pic du Canigou" => array(42.518889, 2.456667, 2784),
+		     "Mont Valier" => array(42.797699, 1.085672, 2838),
+		     "Pic du Midi de Bigrorre" => array(42.936389,0.142778, 2877),
+		     "Dôme de la grave" => array(43.60085, 1.43298, 169),
+		     "Clocher des Jacobins" => array(43.60368, 1.44038, 171),
+		     );
