commit 7096d2f9afa60e9a356634713de276bc4de37adf
Author: Jocelyn Delande <jocelyn@crapouillou.net>
Date:   Mon Dec 22 23:17:15 2014 +0100

    Refacto of Tiles handling and simplified a bit file naming for tiles/params.
    By the way, fixes #297
    
    THIS COMMIT BREAKS FILE LAYOUT AND REQUIRES MIGRATION (SEE BELLOW).
    
    To migrate data :
    
    cd tiles
    find . '*.jpg' -exec rename 's/[^\/]*_(([0-9]{3}_){2}([0-9]{3}))/$1/' {} \;
    rename 's/(.*\/).*\.params/$1site.params/' */*.params

diff --git a/ajax/all_points.php b/ajax/all_points.php
index 5d3bd0e..45016de 100644
--- a/ajax/all_points.php
+++ b/ajax/all_points.php
@@ -1,4 +1,6 @@
 <?php
+header("Content-type: application/json");
+
 require_once('../class/site_point.class.php');
 
 function get_ref_points() {
diff --git a/class/Tile.class.php b/class/Tile.class.php
new file mode 100644
index 0000000..c632f39
--- /dev/null
+++ b/class/Tile.class.php
@@ -0,0 +1,38 @@
+<?php
+
+class Tile {
+  /** Defines a Tile from a panorama
+   */
+
+    private $point;
+    private $zoom;
+    public $x;
+    public $y;
+
+    public function __construct($site_point, $zoom, $x, $y) {
+        $this->point = $site_point;
+        $this->zoom = $zoom;
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    public function path() {
+        return sprintf('%s/%03d_%03d_%03d.jpg', $this->point->tiles_path(),
+                       $this->zoom, $this->x, $this->y);
+    }
+
+    /** Returns tile size (x,y)
+     */
+    public function dimensions() {
+        return getimagesize($this->path());
+    }
+
+    public static function from_file($file_path, $site_point) {
+        preg_match('/([0-9]+)_([0-9]+)_([0-9]+)\.jpg$/', $file_path, $groups);
+        $zoom = (int)($groups[1]);
+		$x = (int)($groups[2]);
+		$y = (int)($groups[3]);
+        return new Tile($site_point, $zoom, $x, $y);
+    }
+  };
+?>
diff --git a/class/TilesGenerator.php b/class/TilesGenerator.php
index 1e4bfe1..2961aa4 100644
--- a/class/TilesGenerator.php
+++ b/class/TilesGenerator.php
@@ -40,15 +40,14 @@ class TilesGenerator {
 		if ($err) {
 			throw (new TilesGeneratorRightsException($err));
 		}
-
 	}
 
 	public function mk_command() {
 		/** Returns the command to execute
 		 */
-		$c = sprintf('%s/%s -p "%s" "%s"',
+		$c = sprintf('%s/%s -p "%s/" "%s"',
 		             CELUTZ_PATH, $this::SCRIPT_RELATIVE_PATH,
-		             $this->panorama->tiles_prefix(), $this->img_path);
+		             $this->panorama->tiles_path(), $this->img_path);
 		return escapeshellcmd($c);
 	}
 
diff --git a/class/site_point.class.php b/class/site_point.class.php
index 15416cd..49676fe 100644
--- a/class/site_point.class.php
+++ b/class/site_point.class.php
@@ -2,6 +2,8 @@
 require_once(dirname(__FILE__).'/../constants.inc.php');
 require_once(dirname(__FILE__).'/utils.class.php');
 require_once(dirname(__FILE__).'/GeoPoint.class.php');
+require_once(dirname(__FILE__).'/Tile.class.php');
+
 //
 class PanoramaFormatException extends Exception {
 	/** If the files organization is not correct for a panorama, we can't let it go...
@@ -25,24 +27,43 @@ class site_point extends GeoPoint {
   }
 
   public function params_path() {
-	  return $this->base_dir.'/'.$this->prefix.'.params';
+	  return $this->base_dir.'/site.params';
+  }
+
+  /** Look for a *.params file in the base_dir
+   *
+   *  Tries first the site.params, then a globbing *.params.
+   *
+   *  May be deprecated at a certain time (if we consider all files should be
+   *  named site.params)
+   *
+   *  @returns false if not found, an abs path else.
+   */
+  private function look_for_params() {
+	if (is_file($this->params_path())) {
+	  return $this->params_path();
+	}
+	$matches = glob($this->base_dir.'/*.params');
+	if ($matches and count($matches) > 0) {
+	  return $matches[0];
+	} else {
+	  return false;
+	}
   }
 
   public function tiles_url_prefix() {
-	  return PANORAMA_FOLDER.'/'.$this->prefix;
+	  return PANORAMA_FOLDER;
   }
 
   public function tiles_path() {
 	  return $this->base_dir;
   }
 
-  public function tiles_prefix() {
-	  return $this->base_dir.'/'.$this->get_prefix();
-  }
-
   private function parse_and_cache_params() {
-    if (is_file($this->params_path())) {
-	    $params = parse_ini_file($this->params_path());
+	$params_path = $this->look_for_params();
+
+    if ($params_path) {
+	    $params = parse_ini_file($params_path);
 	    if ($params) {
 		    $this->params = $params;
 		    if (isset($params[self::$REF_KEY])) {
@@ -133,21 +154,26 @@ class site_point extends GeoPoint {
   }
 
   public function get_magnifications() {
-    $dir_fd = opendir($this->base_dir);
+    //$dir_fd = opendir($this->base_dir);
     $zoom_array = array();
-    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;
-	 }
-       }
-    }
+
+	// extraction des paramètres de grossissement par le serveur
+	$stop = false;
+	$zoom_level = 0;
+
+	while (! $stop) {
+	  $files = glob(sprintf('%s/%03d_*.jpg', $this->base_dir, $zoom_level));
+	  sort($files);
+	  $last_file = end($files);
+	  if ($last_file) {
+		$last_tile = Tile::from_file($last_file, $this);
+		$zoom_array[$zoom_level] = array('nx' => $last_tile->x + 1,
+										 'ny' => $last_tile->y + 1);
+		$zoom_level++;
+	  } else {
+		$stop = true;
+	  }
+	}
     $this->zooms = $zoom_array;
     return $this->zooms;
   }
diff --git a/js/pano.js b/js/pano.js
index cbae9f2..14a3eeb 100644
--- a/js/pano.js
+++ b/js/pano.js
@@ -264,17 +264,19 @@ function erase_point() {
     putImage(last.x, last.y);
 }
 
+/** Returns a 3-width zero-padded version of an int
+ * ex: 3 -> "003"
+ */
+function zero_pad(number) {
+	var temp = number.toString(10);
+	while (temp.length < 3) {
+		temp = '0' + temp;
+	}
+	return temp;
+}
+
 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;
+	return img_prefix+'/'+zero_pad(z)+'_'+zero_pad(x)+'_'+zero_pad(y)+'.jpg';
 }
 
 function get_base_name() {
@@ -483,142 +485,148 @@ function tzoom(zv) {
     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;
+	    this.im.visible_width = this.tile.width*(this.ntiles.x-1)+this.last_tile.width;
+	    this.is_updated = true;
 
-	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);
+	    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.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.max_tile.width = this.tile.width;
 	    }
-	    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 (this.last_tile.height > this.tile.height) {
+		    this.max_tile.height = this.im.last_tile.height;
+	    } else {
+		    this.max_tile.height = this.tile.height;
 	    }
 
-	    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[0].dshft_y = 0;
-		this.ref_pixels[ord_pts.length].dshft_y = 0;
+	    var ord_pts = new Array();
+	    for(var label in ref_points) {
+		    ord_pts.push(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;
 
-	    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>';
+		    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 (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;
+
+		    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 {
-			var tx0 = this.im.width-1;
-			var ty0 = 0;
+			    this.ref_pixels[0].shift_y = this.ref_pixels[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+', ele:'+tst.ele+'</p>';
+				    var tst2 = this.get_pos_xy(tst.cap, tst.ele);
+				    res.innerHTML += '</ul><p>x:'+tst2.x+', y:'+tst2.y+'</p>';
+			    }
 		    }
-		    res.innerHTML += '</ul><p>test sur : '+tx0+','+ty0+'</p>';
-		    var tst = this.get_cap_ele(tx0, ty0);
-		    res.innerHTML += '<p>cap:'+tst.cap+', ele:'+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 && lbl != 'point temporaire') {
-		typ = 'loc_point';
-	    }else if(is_visible && lbl =='point temporaire') {
-	    typ = 'temporary';
-
-	    } else if(is_visible) {
-		typ = 'pano_point';
-		lnk += '&to_zoom='+this.value;
+	    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 && lbl != 'point temporaire') {
+			    typ = 'loc_point';
+		    }else if(is_visible && lbl =='point temporaire') {
+			    typ = 'temporary';
+
+		    } 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.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};
diff --git a/panorama.php b/panorama.php
index 2e35cb6..ca9f5b8 100644
--- a/panorama.php
+++ b/panorama.php
@@ -3,6 +3,8 @@
 <head>
   <?php
    require 'class/utils.class.php';
+   require_once('class/Tile.class.php');
+
    require_once 'constants.inc.php';
 
   $fields_spec = array(
@@ -67,19 +69,21 @@
   <script>
   <?php
      $zoom_array = $pt->get_magnifications();
-
      foreach($zoom_array as $zoom => $val) {
+       $first_tile = new Tile($pt, $zoom, 0, 0);
+       $last_tile = new Tile($pt, $zoom, $val['nx']-1, $val['ny']-1);
+
        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));
+       $size = $first_tile->dimensions();
        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";
+       // Last tile usually have a "remainder" size
+       $last_size = $last_tile->dimensions();
+       echo "zooms[$zoom].last_tile.width = ".$last_size[0].";\n";
+       echo "zooms[$zoom].last_tile.height = ".$last_size[1].";\n";
      }
-
    $dir_list = new sites_dir($dir);
 
    $ipt = 0;
diff --git a/to_tiles/gen_tiles.sh b/to_tiles/gen_tiles.sh
index 9095c93..53b8ab0 100755
--- a/to_tiles/gen_tiles.sh
+++ b/to_tiles/gen_tiles.sh
@@ -71,7 +71,7 @@ tmp_file=$(mktemp)
 
 for ((z=$min_scale; z <= $max_scale; z++))
 do
-    fprefix=${prefix}_00$z
+    fprefix=${prefix}00$z
     printf -v ratio %1.4lf $(echo "1 / (2^$z)" | bc -l)
     echo génération du ratio $ratio
     zwfname=$tmp_file
@@ -112,8 +112,8 @@ done
 if ! $test_mode; then
 ## les lignes ci dessous sont destinnées à mettre des 0 en debut des numéros de ligne et de colonnes
 ## Il y a certainement plus simple mais là c'est du rapide et efficace.
-    rename 's/_(\d\d)_(\d+\.jpg)$/_0$1_$2/' ${prefix}_*
-    rename 's/_(\d)_(\d+\.jpg)$/_00$1_$2/' ${prefix}_*
-    rename 's/_(\d+)_(\d\d)(\.jpg)$/_$1_0$2$3/' ${prefix}_*
-    rename 's/_(\d+)_(\d)(\.jpg)$/_$1_00$2$3/' ${prefix}_*
+    rename 's/_(\d\d)_(\d+\.jpg)$/_0$1_$2/' ${prefix}*
+    rename 's/_(\d)_(\d+\.jpg)$/_00$1_$2/' ${prefix}*
+    rename 's/_(\d+)_(\d\d)(\.jpg)$/_$1_0$2$3/' ${prefix}*
+    rename 's/_(\d+)_(\d)(\.jpg)$/_$1_00$2$3/' ${prefix}*
 fi
