function viewer(id) {

    this.id = id;
    window[this.id] = this;
    this.legend = null;
    this.main = null;
    this.reference = null;
    this.ms = null;
    this.mode = '';
    this.cache = null;
    this.activeLayer = '';
    //Layer active for queries
    this.selectLayer = '';
    //Layer to elements selection
    this.selectIds = '';
    //var to store the elements selection
    var self = this;

    initialize();

    /************************************************************************** 
     **************************************************************************
     *  INTERFAZ
     *  
     *  Las funciones definidas a continuaci�n definen la interfaz del visor
     *
     ***************************************************************************
     ***************************************************************************/

    /*
    * Retorna el valor del nivel de zoom actual del visor
    */
    this.getZoomSize = function() {
        return self.ms.zoomsize;
    }

    /*
    * Fija el valor del nivel de zoom del visor
    */
    this.setZoomSize = function(zoomValue) {
        self.ms.zoomsize = zoomValue;
    }

    /*
     * Retorna el valor del nivel de pan actual del visor     
     */
    this.getPanSize = function() {
        return self.ms.pansize;
    }

    /*
    * Fija el valor del nivel de pan del visor
    */
    this.setPanSize = function(panValue) {
        self.ms.pansize = panValue;
    }


    /*
    * Prepara el visor para recibir eventos por click del rat�n.
    * Modos:
    * - zoomin: zoom + por click o encuadre
    * - zoomout: zoom - por click
    * - pan: desplazamiento por click o arrastre
    * - measure: medici�n de distancias
    * - area: medici�n de areas
    * - info: consulta de informaci�n en nueva ventana (no afecta
    *         al estado del visor)
    * - select: selecci�n de objetos
    */
    this.changeMode = function(mode) {
        xInnerHtml(MEASURE, '');
        self.mode = mode;
        if (mode == 'zoomin') {
            self.ms.mode = "map";
            self.ms.zoomdir = 1;
            self.main.cursor = "url('" + IMAGES_PATH + "zoomin.cur'),crosshair";
            self.main.boxOn();
        } else if (mode == 'zoomout') {
            self.ms.mode = "map";
            self.ms.zoomdir = -1;
            self.main.cursor = "url('" + IMAGES_PATH + "zoomout.cur'),crosshair";
            self.main.boxOff();
        } else if (mode == 'pan') {
            self.ms.mode = "map";
            self.ms.zoomdir = 0;
            self.main.cursor = "url('" + IMAGES_PATH + "pan.cur'),move";
            self.main.dragOn();
        } else if (mode == 'measure') {
            self.main.lineOn();
            self.main.cursor = 'crosshair';
        } else if (mode == 'area') {
            self.main.polyOn();
            self.main.cursor = 'crosshair';
        } else if (mode == 'info') {
            self.ms.mode = "query";
            self.main.cursor = 'help';
            self.main.boxOff();
        } else if (mode == 'select') {
            self.ms.mode = "query";
            self.main.cursor = 'default';
            self.main.boxOff();
        }
    }

    /*
     * Realiza la operaci�n de desplazamiento en la direcci�n seleccionada
     * La cantidad del desplazamiento efectuado por esta operaci�n viene dado 
     * por la variables self.ms.pansize
     * Direcciones:
     * - n: norte
     * - nw: noroeste
     * - ne: noreste
     * - s: sur
     * - sw: suroeste
     * - se: sureste
     * - e: este
     * - w: oeste
     */
    this.pan = function(direction) {
        self.ms.pan(direction);
    }

    this.textAtributesQuery = function(layer, propertiesValues) {
        //save values
        var queryOld = self.ms.queryoptions;
        var selfModeOld = self.mode;
        var msModeOld = self.ms.mode;

        self.mode = ''
        self.ms.queryoptions = '&qstring=(0=0)';
        for (var i = 0; i < propertiesValues.length; i++) {
            propertiesValues[i];
            self.ms.queryoptions = self.ms.queryoptions + '&p' + i + '=' + propertiesValues[i];
        }
        self.ms.mode = "itemquery";
        self.ms.queryoptions = self.ms.queryoptions + '&qlayer=' + layer;
        self.ms.query();

        //restore values
        self.ms.queryoptions = queryOld;
        self.ms.mode = msModeOld;
        self.mode = selfModeOld;
        return self.ms.url;
    }

    this.mapAtributesQuery = function(layer, propertiesValues, select, center) {
        var url = self.textAtributesQuery(layer, propertiesValues);
        var ajax = new AJAX();
        var text = ajax.getHttpRequest(url);
        var templateElements = ajax.parseValuesXML(new Array("id", "extent", "buffer"), text);
        if (templateElements != null) {
            var layerName = layer.split("_query_")[0];
            setLayer(layerName, true);
            drawQuery(layerName, templateElements[0], select, center, templateElements[1], templateElements[2]);
            if (select) this.selectIds = templateElements[0];
            return true;
        }
        return false;
    }
    
        
    this.drawWithAttributesQuery = function (attributes, values) {
    	for (var i = 0; i<attributes.length; i++) {
    		mapViewer.ms.options = mapViewer.ms.options + '&' + attributes[i] + '=' + values[i];
    	}
    	mapViewer.ms.draw();
    }

    this.clearQuery = function() {
        self.ms.options = '';
        self.selectIds = '';
        self.ms.draw();
    }

    /*
     * Refresca el mapa
     * Los manejadores definidos se ocuparan de realizar las acciones
     * correspondientes (capas de la leyenda, cache, ...)     
     */
    this.refresh = function() {
        self.ms.draw();
    }

    /*
     * Inicializa el visor al estado incial (extensi�n y capas)
     */
    this.init = function() {
        self.ms.layersOff();
        for (var i = 0; i < INIT_LAYERS.length; i++)
            setLayer(INIT_LAYERS[i], true);
        self.ms.mode = 'map';
        self.ms.setExtent(INIT_EXTENT[0], INIT_EXTENT[1], INIT_EXTENT[2], INIT_EXTENT[3]);
        self.ms.draw();
    }

    /*
     * Retorna el visor a la situaci�n anterior (extensi�n y capas)          
     */
    this.back = function() {
        if (self.cache.hasPrevious())
            restore(self.cache.getPrevious());
    }

    /*
     * Retorna el visor a la situaci�n siguiente (extensi�n y capas)          
     * Esta operaci�n s�lo tendr� efecto s� se ha realizado 
     * la operaci�n anterior al menos una vez
     */
    this.forward = function() {
        if (self.cache.hasNext())
            restore(self.cache.getNext());
    }

    this.adjustSize = function(newWidth, newHeight) {
		if (!newWidth){
			newWidth = self.ms.width;
		}
		if (!newHeight){		
			newHeight = self.ms.height;
		}		

        if (window.opener && window.resizeBy)
            window.resizeBy(newWidth - self.ms.width, newHeight - self.ms.height);

        xInnerHtml(MEASURE, '');
        //Para borrar las posibleas areas y mediciones

        //Image size
        window.xGetElementById(MAP).style.width = newWidth + "px";
        window.xGetElementById(MAP).style.height = newHeight + "px";
        if (VIEWER_CHANGE_HANDLER) VIEWER_CHANGE_HANDLER('sizeChange');

        //Legend size
        //window.xGetElementById(LEGEND).style.height = parseInt(window.xGetElementById(LEGEND).style.height)+parseInt(newHeight-self.ms.height) + "px";               
        referenceHeight = self.reference ? (90 - parseInt(window.xGetElementById(REFERENCE).style.height)) : 90;

        window.xGetElementById(LEGEND).style.height = parseInt(newHeight) + parseInt(referenceHeight) + "px";

        self.ms.width = newWidth;
        self.ms.height = newHeight;

        //to adjust Extent and new dimensions
        self.ms.setExtent(self.ms.extent[0], self.ms.extent[1],
                self.ms.extent[2], self.ms.extent[3]);

        if (self.main) self.main.sync();
        if (self.legend) self.legend.sync();
        if (self.reference) self.reference.sync();

        self.ms.setHandler(MAPSERV_POST_DRAW, ms_post_draw_no_cache);
        self.ms.draw();
        self.ms.setHandler(MAPSERV_POST_DRAW, ms_post_draw);

        self.changeMode(self.mode);
    }

    this.getElementsURL = function(width, height) {

        var printUrls = new Array();

        var extent = adjustExtent(self.ms.extent, width, height, "");

        printUrls[0] = escape(SERVER + '?map=' + MAP_FILE +
                              '&mode=map' +
                              '&layers=' + self.ms.getLayers('+') +
                              '&mapext=' + extent.join('+') +
                              '&mapsize=' + width + "+" + height +
                              '&map_scalebar_status=OFF' +
                              '&map_imagecolor=255+255+255' +
                              self.ms.options);
        printUrls[1] = escape(SERVER + '?map=' + MAP_FILE +
                              '&mode=scalebar' +
                              '&mapext=' + extent.join('+') +
                              '&mapsize=' + width + "+" + height);
        printUrls[2] = escape(SERVER + '?map=' + MAP_FILE +
                              '&mode=reference' +
                              '&mapext=' + extent.join('+') +
                              '&mapsize=' + width + '+' + height);
        printUrls[3] = escape(SERVER + '?map=' + MAP_FILE +
                              '&mode=legend' +
                              '&layers=' + self.ms.getLayers('+') +
                              '&mapext=' + extent.join('+') +
                              '&mapsize=' + width + '+' + height +
                              '&map_imagetype=gif' +
                              '&map_legend_template=' + LEGEND_TEMPLATE);
        return printUrls;
    }

	this.refreshLegend = function() {
    	if (self.legend) {
	        var legendURL = SERVER +
	                        '?map=' + MAP_FILE +
	                        '&mode=legend' +
	                        '&layers=' + self.ms.getLayers('+') +
	                        '&mapext=' + self.ms.extent.join('+') +
	                        '&mapsize=' + self.ms.width + "+" + self.ms.height +
	                        '&map_imagetype=gif' +
	                        '&map_legend_template=' + LEGEND_TEMPLATE;
	        //xInnerHtml(MEASURE,legendURL);
	        self.legend.setContent(legendURL);
	        self.activeLayer = checkLegendControls(self.ms.getScale(), self.activeLayer);    	
    	}
    }
    
	this.addLegend = function() {
		initializeLegend();	 	
		self.refreshLegend();
	}

	this.removeLegend = function() {
		self.legend = null;
		var legendElement = document.getElementById(LEGEND);	
		if (legendElement) {
			legendElement.style.width = '0px';
			legendElement.style.height = '0px';			
		}
		var legendElementContainer = document.getElementById(LEGEND + '_container');
		if (legendElementContainer){		
			document.body.removeChild(legendElementContainer);		
		}     						
	}

	this.addReference = function() {
		initializeReference();	   	
		self.reference.setImage(self.ms.referencemap.url);				
	}

	this.removeReference = function() {	
		self.reference = null;
		var referenceElement = document.getElementById(REFERENCE);						
		if (referenceElement) { 
			referenceElement.style.width = '0px';
			referenceElement.style.height = '0px';			
		}		
		var referenceElementContainer = document.getElementById(REFERENCE + '_container');
		if (referenceElementContainer){
			document.body.removeChild(referenceElementContainer);		
		}        
		var referenceElement = document.getElementById(REFERENCE + '_container');				
	}

	
    /************************************************************************** 
     **************************************************************************
     *  IMPLEMENTACI�N
     *  
     *  Las funciones definidas a continuaci�n son funciones internas del visor, 
     *  no son accesibles fuera de este
     *
     ***************************************************************************
     ***************************************************************************/

    /*
    * Inicializa el estado del visor a partir de las variables definidas
    * en el fichero conf.js
    */
    function initialize() {
        self.main = new dBox(MAP);
        self.main.color = "#9e0057";
        self.main.thickness = 2;
        self.main.verbose = true;
        self.main.box = true;
        self.main.useBusyMessage();
        self.main.setHandler(DBOX_SETBOX, main_setbox);
        self.main.setHandler(DBOX_MOUSEMOVE, main_mousemove);
        self.main.setHandler(DBOX_MOUSEEXIT, clear_coords);
        self.main.setHandler(DBOX_MOUSEENTER, clear_coords);
        self.main.setHandler(DBOX_MEASURE, main_measure);
        self.main.setHandler(DBOX_AREA, main_area);
        self.main.initialize();
        self.ms = new Mapserv(SERVER, MAP_FILE,
                INIT_EXTENT[0], INIT_EXTENT[1], INIT_EXTENT[2], INIT_EXTENT[3],
                MAP_WIDTH, MAP_HEIGHT);
        self.ms.queryfile = QUERY_FILE;
        self.ms.minscale = MINSCALE;
        self.ms.maxscale = MAXSCALE;
        self.ms.setHandler(MAPSERV_PRE_DRAW, ms_pre_draw);
        self.ms.setHandler(MAPSERV_POST_DRAW, ms_post_draw);
        self.ms.setHandler(MAPSERV_QUERY, ms_query);
        self.ms.zoomsize = ZOOM_SIZE;
        self.ms.pansize = PAN_SIZE;
        
		if (REFERENCE_INIT_STATUS) {
			initializeReference();
		}

        self.ms.layersOff();
        for (var i = 0; i < INIT_LAYERS.length; i++)
            setLayer(INIT_LAYERS[i], true);

		if (LEGEND_INIT_STATUS) {
			initializeLegend();
		}
				
        self.cache = new cache(CACHE_SIZE);
    }

	function initializeReference() {
        if (REFERENCE != null) {
			var referenceElement = document.getElementById(REFERENCE);						
			if (referenceElement) { 
				referenceElement.style.width = REFERENCE_WIDTH +'px';
				referenceElement.style.height = REFERENCE_HEIGHT + 'px';			
			}	        
            self.reference = new dBox(REFERENCE);
            self.reference.box = false;
            self.ms.referencemap = new Mapserv(SERVER, MAP_FILE,
                    INIT_EXTENT[0], INIT_EXTENT[1], INIT_EXTENT[2], INIT_EXTENT[3],
                    REFERENCE_WIDTH, REFERENCE_HEIGHT);
            self.reference.initialize();
            self.reference.setHandler(DBOX_SETBOX, reference_setbox);
        }		
	}

	function initializeLegend() {
        if (LEGEND != null) {
			var legendElement = document.getElementById(LEGEND);						
			if (legendElement) { 
				legendElement.style.width = LEGEND_WIDTH +'px';
				legendElement.style.height = LEGEND_HEIGHT + 'px';			
			}	        
            self.legend = new dContainer(LEGEND);
    	    self.legend.scroll = true;
	        self.legend.initialize();
        }	
	}
	    
    /* 
     * Fija una capa como visible en el proximo render�zado, independientemente 
     * de si la leyenda ha sido o no renderizada     
     */
    function setLayer(layer, value) {
        var selector = xGetElementById('layerselector-' + layer);
        if ((self.ms.preDrawHandler != null) && (selector))
            selector.checked = value;
        else self.ms.setLayer(layer, value);
    }

    function setTimeOut(f, t) {
        setTimeout("window." + self.id + "." + f, t);
    }

    function drawQuery(layer, id, select, center, extent, buffer) {

        buffer = (buffer != '') ? eval(buffer) : 0;

        if (select) self.ms.options = '&' + layer + '_id=' + id;
        if (center) {
            var auxExtent = extent.split(" ");
            self.ms.setExtent(parseFloat(auxExtent[0]) - buffer, parseFloat(auxExtent[1]) - buffer,
                    parseFloat(auxExtent[2]) + buffer, parseFloat(auxExtent[3]) + buffer);
        }
        self.ms.draw();
    }

    /*
     * Recupera el estado del mapa a partir del registro que se le pasa como par�metro
     * Los registros almacenados en la cache del visor estan formados por dos componentes:
     * - La extensi�n del mapa a recuperar
     * - La lista de capas a seleccionar en la leyenda
     */
    function restore(record) {
        var extent = record[0].split('+');
        self.ms.setHandler(MAPSERV_PRE_DRAW, null);
        self.ms.setHandler(MAPSERV_POST_DRAW, ms_post_draw_no_cache);
        self.ms.mode = 'map';
        self.ms.setExtent(parseFloat(extent[0]), parseFloat(extent[1]),
                parseFloat(extent[2]), parseFloat(extent[3]));
        self.ms.layersOff();
        var layers = record[1].split('+');
        for (var i = 0; i < layers.length; i++)
            setLayer(layers[i], true);
        self.ms.draw();
        self.ms.setHandler(MAPSERV_PRE_DRAW, ms_pre_draw);
        self.ms.setHandler(MAPSERV_POST_DRAW, ms_post_draw);
    }

    function adjustExtent(extent, width, height, buffer) {

        buffer = (buffer != '') ? eval(buffer) : 0;

        for (i = 0; i < 4; i++) extent[i] = parseFloat(extent[i]);

        extent[0] = extent[0] - buffer;
        extent[1] = extent[1] - buffer;
        extent[2] = extent[2] + buffer;
        extent[3] = extent[3] + buffer;

        var cellsize = Math.max((extent[2] - extent[0]) / width, (extent[3] - extent[1]) / height);

        if (cellsize > 0) {
            var ox = Math.max((width - (extent[2] - extent[0]) / cellsize) / 2, 0);
            var oy = Math.max((height - (extent[3] - extent[1]) / cellsize) / 2, 0);

            extent[0] = extent[0] - ox * cellsize;
            extent[1] = extent[1] - oy * cellsize;
            extent[2] = extent[2] + ox * cellsize;
            extent[3] = extent[3] + oy * cellsize;
        }
        return(extent);
    }


    /*     
    * Implementaci�n de los manejadores de la librer�a MapServer
    * Adaptaci�n de la librer�a MapServer al visor
    * Se definen dos manejadores alternativos (ms_post_draw y ms_post_draw_no_cache)
    * Su empleo depender� de si se desea o no trabajar con la cache
    */

    function ms_pre_draw() {
        if (document.layers) {
            if (document.layers.RADIO)
                self.ms.layers = toggleLayers(self.ms.layers, document.layers.RADIO);
            if (document.layers.CHECKBOX)
                self.ms.layers = toggleLayers(self.ms.layers, document.layers.CHECKBOX);
        }
    }

    function ms_post_draw() {
        if (self.reference) self.reference.setImage(self.ms.referencemap.url);
        if (self.main) self.main.setImage(self.ms.url);
        if (self.legend) setTimeOut("refreshLegend()", 100);
        self.cache.set(new Array(self.ms.extent.join('+'), self.ms.getLayers('+')));
        if (VIEWER_CHANGE_HANDLER) VIEWER_CHANGE_HANDLER('cacheChange');
    }

    function ms_post_draw_no_cache() {
        if (self.reference) self.reference.setImage(self.ms.referencemap.url);
        if (self.main) self.main.setImage(self.ms.url);
        if (self.legend) setTimeOut("refreshLegend()", 100);
    }

    function ms_query() {

        if ((self.mode != 'select') && (self.mode != 'info')) return;
        //Para otros valores de mode este manejador no hace nada

        var ajax = new AJAX();
        var text = ajax.getHttpRequest(self.ms.url);
        var templateElements = ajax.parseValuesXML(new Array("id", "extent", "buffer"), text);

        if (self.mode == 'select') {
            if (templateElements == null) {
                alert(message_selectionNotPermitted);
                return
            }
            if (self.ms.layers[self.selectLayer]) {
                if (!MULTISELECT) self.selectIds = templateElements[0];
                else {
                    if (self.selectIds == '') self.selectIds = templateElements[0];
                    else self.selectIds = self.selectIds + '|' + templateElements[0];
                }
                drawQuery(self.selectLayer, self.selectIds, true, false, templateElements[1], templateElements[2]);
                
                fillPlotDiv(text);
                
            } else alert(message_selectionNotPermitted);
        } else if (self.mode == 'info') {
            if (templateElements == null) {
                alert(message_noInformation);
                return
            }
            var extentQuery = adjustExtent(templateElements[1].split(" "), 300, 300, templateElements[2]);
            var imgQueryResult = SERVER + '?mode=map' + '&map=' + self.ms.mapfile +
                                 '&mapext=' + extentQuery.join('+') + '&mapsize=300+300' +
                                 '&layers=' + self.ms.getLayers('+') +
                                 '&' + self.activeLayer + '_id=' + templateElements[0] +
                                 '&map_scalebar_status=OFF';
            var legendInfo = getLegendInfo(self.activeLayer);
            var urlQuery = QUERY_RESULT_URL + '?url=' + escape(self.ms.url) + '&urlImg=' + escape(imgQueryResult) +
                           '&title=' + legendInfo[0] + '&description=' + legendInfo[1];
            FUNCTION_POPUP(550, 420, 'querywin', urlQuery);
        }
    }

    /*
     * Implementaci�n de los manejadores de la librer�a DBox
     * Adaptaci�n de la librer�a DBox al visor
     */

    function reference_setbox(minx, miny, maxx, maxy) {
        self.ms.applyReference(minx, miny);
        self.ms.draw();
    }

    function main_setbox(minx, miny, maxx, maxy) {

        if (self.ms.mode == 'map') {
            if (minx != maxx && miny != maxy) {
                self.ms.applyBox(minx, miny, maxx, maxy);
            } else {
                self.ms.applyZoom(minx, miny);
            }
            self.ms.draw();
            return;
        }

        //ms.mode != 'map'
        self.ms.applyPointQuery(minx, miny);
        var queryTemp = self.ms.queryoptions;

        if (self.mode == 'info') {
            if (self.activeLayer != '') {
                self.ms.queryoptions = '&qlayer=' + self.activeLayer;
            }
            else {
                alert(message_notActiveLayerChoosed);
                return;
            }
        } else {
            if (self.selectLayer != '') self.ms.queryoptions = '&qlayer=' + self.selectLayer;
            else {
                alert(message_notSelectLayerChoosed);
                return;
            }
        }
        self.ms.queryoptions = self.ms.queryoptions +
                               '&x=' + (self.ms.extent[0] + self.ms.cellsize * minx) +
                               '&y=' + (self.ms.extent[3] - self.ms.cellsize * miny);
        self.ms.query();
        self.ms.queryoptions = queryTemp;
    }

    function main_mousemove(x, y) {
        var text = '';
        //var utm = new Point(Number(self.ms.extent[0] + x * self.ms.cellsize), Number(self.ms.extent[3] - y * self.ms.cellsize));
        //var latlon = UTMToGeographic(15, utm);
        //text = text + "\n"+ ", latitude = " + latlon.y.toFixed(8) + " and longitude = " + latlon.x.toFixed(8);  
        
        var utmx = Number(self.ms.extent[0] + x * self.ms.cellsize);
        var utmy = Number(self.ms.extent[3] - y * self.ms.cellsize); 

        text = message_coords + ": x =" + Math.round(utmx) + ", y = " + Math.round(utmy);
        xInnerHtml(COORDS, text);
    }

    function clear_coords() {
        var e = document.getElementById(COORDS);
        if (e) e.innerHTML = '&nbsp;';
    }

    function main_measure(s, t, n, a) {
        var text = message_distance + ': ' + Math.round(t * self.ms.cellsize) + ' ' + message_meters + ' (' + n + ' ' + message_points + ')';
        xInnerHtml(MEASURE, text);
    }

    function main_area(a, d, l, n) {
        var text = message_area + ': ' + Math.round(a * self.ms.cellsize) + ' ' + message_meters_sq + "     |     ";
        text = text + message_distance + ": " + Math.round(l * self.ms.cellsize) + ' ' + message_meters + ' (' + n + ' ' + message_points + ')';
        xInnerHtml(MEASURE, text);
    }
}
