/**
 * editableMap.js
 * @author: Luigi Alibrandi, De Toffol Paolo, Luca Russello
 * @description: Libreria Js per la creazione di mappe google editabili (creazione/modifica di linee, 'lines along roads' e punti)
 */

/**
 * costruttore per l'inizializzazione della mappa (e' la funzione Js da agganciare all'apertura della pagina html che usa la libreria)
 * input:
 * struttura options comprendente:
 * latCenter --> Latitudine cui centrare la mappa (se non viene passato, viene comunque impostata una latitudine di default)
 * longCenter --> Longitudine cui centrare la mappa (se non viene passato, viene comunque impostata una longitudine di default)
 * divMap -> id del div in cui inserire la mappa (obbligatorio, se non viene passato viene lanciata un'eccezione)
 * zoom --> livello di zoom della mappa (facoltativo)
 * lineColor --> colore delle linee (facoltativo)
 * lineSize --> larghezza delle linee (facoltativo, default = 3)
 * divLines --> id del div in cui inserire i dati delle linee (obbligatorio, se non viene passato viene lanciata un'eccezione)
 * divPoints --> id del div in cui inserire i dati dei punti (obbligatorio, se non viene passato viene lanciata un'eccezione)
 * divLinesAlongRoads --> id del div in cui inserire i dati delle linee along roads (obbligatorio, se non viene passato viene lanciata un'eccezione)
 * cssTextClass --> nome classe css da associare al testo
 * cssInputClass --> nome classe css da associare al campo input=text
 * cssAnchorClass --> nome classe css da associare all'anchor
 * nameMap --> nome assegnato all'oggetto editableMap, usato per la costruzione del codice Js inserito nella pagina
 * debug --> booleano, impostato a true stampa info aggiuntive
 * visDati --> booleano, impostato a false inibisce la renderizzazione in pagina dei dati di punti e linee
 * urlIcon --> url dell'icona associata ai punti
 * widthIcon --> larghezza in pixel dell'icona associata ai punti
 * heightIcon --> altezza in pixel dell'icona associata ai punti
 * pointIconStandard --> oggetto GIcon da associare ai punti (se viene passato questo parametro non vengono considerati i parametri urlIcon, widthIcon e heightIcon) 
 * urlKml --> url da cui prelevare il kml da visualizzare nella mappa (facoltativo, se non passato viene visualizzata una mappa vuota) 
 * latBound1 --> latitudine primo bound mappa
 * longBound1 --> longitudine primo bound mappa
 * latBound2 --> latitudine secondo bound mappa
 * longBound2 --> longitudine secondo bound mappa
 * clickAddPoint --> booleano (default true), se false inibisce l'aggiunta del punto sul click della mappa
 * linkEdit --> booleano (default true), se false non visualizza i link di edit
 * hidePoints --> boolean (default false), se true nasconde le funzionalita' di editing dei punti
 * hideLines --> boolean (default false), se true nasconde le funzionalita' di editing delle lines
 * msgLinesAlongRoadsMax25Points --> messaggio per impedire il tracciamento di lines along roads con piu' di 25 punti (non e' permesso dalle api di google!)
 * msgErrDirections --> messaggio di errore restituito in caso di errore generato dall'oggetto GDirections
 * msgErrPointsWithSameName --> messaggio di errore restituito in caso di assegnazione dello stesso nome a punti diversi 
 * labelPoints --> etichetta (facoltativa) anteposta ad ogni riga dei punti
 * imgPlay --> url immagine play per image gallery
 * imgPause --> url immagine pause per image gallery
 * imgStop --> url immagine stop per image gallery
 * urlIconStart --> url dell'icona associata al punto di start
 * urlIconEnd --> url dell'icona associata al punto di end
 * imgChange1 --> url dell'icona associata al punto di change animate generico
 * imgChange2 --> url dell'icona associata al punto di change animate foto
 * marginBottom --> margine toolbar
 * urlIconAnimate --> url dell'icona associata al punto animate
 * 
 * output:
 * non previsto
 */
function editableMap(options) {
	
	//metodo privato che restituisce un boolean che indica se il parametro non contiene dati
	//input:
	//par --> parametro obbligatorio da controllare
	//output:
	//boolean
	var isEmpty = function(par) {
		return (par==undefined || par==null || par=='');
	};
	
	//Costanti
	//zoom usato se non viene passato uno zoom nelle options
	//const ZOOM_DEFAULT=14;
	var ZOOM_DEFAULT=14;
	
	//latitudine centrale della mappa usata se non viene passata nelle options
	//const LAT_CENTER_DEFAULT=45;
	var LAT_CENTER_DEFAULT=45;
	
	//longitudine centrale della mappa usata se non viene passata nelle options
	//const LONG_CENTER_DEFAULT=9;
	var LONG_CENTER_DEFAULT=9;
	
	//colore usato per la creazione delle linee se non viene passato nelle options
	//const LINE_COLOR_DEFAULT="#0000ff";
	var LINE_COLOR_DEFAULT="#0000ff";
	
	//larghezza di default delle linee usato se non viene passato nelle options
	//const LINE_COLOR_DEFAULT="3";
	var LINE_SIZE_DEFAULT="3";	
	
	//prefisso del nome associato automaticamente al punto all'atto della sua creazione nella mappa
	//const POINT_PREFIX_NAME="Point";
	var POINT_PREFIX_NAME="Point";
	
	//prefisso del nome associato automaticamente alla linea all'atto della sua creazione nella mappa
	//const LINE_PREFIX_NAME="Line";
	var LINE_PREFIX_NAME="Line";
	
	//larghezza in pixel di default dell'icona associata ai punti
	var WIDTH_ICON_DEFAULT=20;
	
	//altezza in pixel di default dell'icona associata ai punti
	var HEIGHT_ICON_DEFAULT=34;
	
	//messaggio di default per impedire il tracciamento di lines along roads con piu' di 25 punti (non e' permesso dalle api di google!)
	var MSG_LINES_ALONG_ROADS_MAX_25_POINTS="Non e' possibile tracciare linee con oltre 25 punti!";
	
	//messaggio di default di errore restituito in caso di errore generato dall'oggetto GDirections
	var MSG_ERR_DIRECTIONS="Si e' verificato un errore nella generazione del percorso: probabilmente l'ultimo punto inserito o spostato non consente di completare il percorso, modifica la posizione e riprova";
	
	//messaggio di default di errore restituito in caso di errore generato dall'oggetto GDirections
	var MSG_ERR_POINTS_WITH_SAME_NAME="Non e' possibile assegnare lo stesso a nome a piu' punti";

	//messaggio di default di errore restituito in caso di errore generato dall'oggetto GDirections
	var MSG_ERR_TWO_POINTS_NEED="You must enter two points to create a line";
	
	//messaggio di default di errore restituito in caso di errore generato dall'oggetto GDirections
	var MSG_ERR_STOP_INVALID_PATH="You can't stop an invalid path";
	
	//valore opacita' standard applicata ai poligoni
	var OPACITY_STANDARD = 0.5;
	
	//valore opacita' evidenziata applicata ai poligoni
	var OPACITY_HIGHLIGHT = 1;	
	
	var TITLE_PHOTO_ANIMATION="Switch to Photo Animation";
	
	var TITLE_TRIP_ANIMATION="Switch to Trip Animation";
	
	var urlWsAltitude="http://ws.geonames.org/gtopo30JSON";
	
	//Variabili globali
	var center;
	var map;
	
	//struttura interna che memorizza i punti (markers) che vengono create nella mappa
	//markers[indice]["marker"] --> oggetto marker
	//markers[indice]["name"] --> name visibile (modificabile) associato al punto
	//markers[indice]["id"] --> identificativo interno associato al punto
	//markers[indice]["urlImg"] --> url immagine associata al punto
	//markers[indice]["urlIcon"] --> url icona associata al punto
	//markers[indice]["comment"] --> commento associato al punto
	//markers[indice]["category"] --> categoria associata al punto
	var markers=[];
	
	//struttura interna che memorizza le linee (polylines) che vengono create nella mappa
	//polylines[indice]["polyline"] --> oggetto polyline
	//polylines[indice]["name"] --> name visibile (modificabile) associato alla linea
	//polylines[indice]["id"] --> identificativo interno associato alla linea
	var polylines=[];
	
	var linesAlongRoads=[];
	var indexMarkers=0;
	var indexLine=0;
	var bAddLine=false,bAddLineAlongRoads=false,bEditLineAlongRoads=false;
	var indiceEditLine=-1,indiceEditPoint=-1,indiceEditLineAlongRoads=-1;
	var markersAlongRoads=[];
	var directions;
	var listenerGMapStandard,listenerGMapLinesAlongRoads;
	var currentMarkers=[];
	var currentExtendedData=[];
	var currentHighlight=null;
	
	//gestione gallery
    var indiceImageControl=0;
    var imageControl=null;
    var opacityImageControl=0;
    var versoImageControl=false;
    var bloccoImageControl=false;
    var centerImageControl=null;
    var toolbarControl=null;
    //0 --> stop
    //1 --> in esecuzione
    //2 --> in pausa
    var statoImageGallery=0;
    var timerGallery=null;
    var timerImage=null;
    var endThreadPointCurrent=false;
    var indiceAnimate=0,indiceLineAnimate=0;
    var markerAnimate=null,iconAnimate=null;
    var timerAnimateLinesAlongRoads=null;
    var currentAnimation=1;
    var polylinesTotal=[];    
	var isAbortedWsAltitude=false;
	var timerAltitudeWs=null;
    var addressFound="";
    var geocoder=new GClientGeocoder();
    var altitudeFond="";
		
	this.options=options;
	var latCenter=this.options["latCenter"];
	var longCenter=this.options["longCenter"];
	var divMap=this.options["divMap"];
	var zoom=this.options["zoom"];
	var lineColor=this.options["lineColor"];
	var lineSize=this.options["lineSize"];
	var cssTextClass=this.options["cssTextClass"];
	var cssInputClass=this.options["cssInputClass"];
	var cssAnchorClass=this.options["cssAnchorClass"];
	var divLines=this.options["divLines"];
	var divPoints=this.options["divPoints"];
	var debug=this.options["debug"];
	var nameMap=this.options["nameMap"];
	var visDati=this.options["visDati"];
	var urlIcon=this.options["urlIcon"];
	var widthIcon=this.options["widthIcon"];
	var heightIcon=this.options["heightIcon"];
	var pointIconStandard=this.options["pointIconStandard"];
	var divLinesAlongRoads=this.options["divLinesAlongRoads"];
	var urlKml=this.options["urlKml"];
	var latBound1=this.options["latBound1"];
	var longBound1=this.options["longBound1"];
	var latBound2=this.options["latBound2"];
	var longBound2=this.options["longBound2"];
	var clickAddPoint=this.options["clickAddPoint"];	
	var linkEdit=this.options["linkEdit"];
	var hidePoints=this.options["hidePoints"];
	var hideLines=this.options["hideLines"];
	var msgLinesAlongRoadsMax25Points=this.options["msgLinesAlongRoadsMax25Points"];
	var msgErrDirections=this.options["msgErrDirections"];
	var msgErrPointsWithSameName=this.options["msgErrPointsWithSameName"];
	var msgErrTwoPointsNeed=this.options["msgErrTwoPointsNeed"];
	var msgErrStopInvalidPath=this.options["msgErrStopInvalidPath"];
	var labelPoints=this.options["labelPoints"];
	var imgPlay=this.options["imgPlay"];
	var imgPause=this.options["imgPause"];
	var imgStop=this.options["imgStop"];
	var urlIconStart=this.options["urlIconStart"];
	var urlIconEnd=this.options["urlIconEnd"];
	var marginBottom=this.options["marginBottom"];
	var imgChange1=this.options["imgChange1"];
	var imgChange2=this.options["imgChange2"];
	var urlIconAnimate=this.options["urlIconAnimate"];
	
	if (isEmpty(latCenter)) latCenter=LAT_CENTER_DEFAULT;
	if (isEmpty(longCenter)) longCenter=LONG_CENTER_DEFAULT;
	if (isEmpty(divMap)) throw new Error("Errore: il parametro divMap e' obbligatorio");
	if (isEmpty(lineColor)) lineColor=LINE_COLOR_DEFAULT;
	if (isEmpty(lineSize)) lineSize=LINE_SIZE_DEFAULT;
	if (isEmpty(cssTextClass)) cssTextClass="";
	if (isEmpty(cssInputClass)) cssInputClass="";
	if (isEmpty(cssAnchorClass)) cssAnchorClass="";
	if (isEmpty(divLines)) throw new Error("Errore: il parametro divLines e' obbligatorio");
	if (isEmpty(divPoints)) throw new Error("Errore: il parametro divPoints e' obbligatorio");
	if (debug==null || debug==undefined) debug=false;
	if (isEmpty(nameMap)) throw new Error("Errore: il parametro nameMap e' obbligatorio");
	if (visDati==null || visDati==undefined) visDati=true;
	if (isEmpty(widthIcon)) widthIcon=WIDTH_ICON_DEFAULT;
	if (isEmpty(heightIcon)) widthIcon=HEIGHT_ICON_DEFAULT;
	if (isEmpty(urlIcon)) urlIcon=null;
	if (pointIconStandard==undefined) pointIconStandard=null;
	if (isEmpty(divLinesAlongRoads)) throw new Error("Errore: il parametro divLinesAlongRoads e' obbligatorio");
	if (isEmpty(urlKml)) urlKml=null;
	if (isEmpty(zoom)) zoom=ZOOM_DEFAULT;
	if (isEmpty(latBound1)) latBound1=null;
	if (isEmpty(longBound1)) longBound1=null;
	if (isEmpty(latBound2)) latBound2=null;
	if (isEmpty(longBound2)) longBound2=null;
	if (clickAddPoint==null || clickAddPoint==undefined) clickAddPoint=true;
	if (linkEdit==null || linkEdit==undefined) linkEdit=true;
	if (hidePoints==null || hidePoints==undefined) hidePoints=false;
	if (hideLines==null || hideLines==undefined) hideLines=false;
	if (isEmpty(msgLinesAlongRoadsMax25Points)) msgLinesAlongRoadsMax25Points=MSG_LINES_ALONG_ROADS_MAX_25_POINTS;
	if (isEmpty(msgErrDirections)) msgErrDirections=MSG_ERR_DIRECTIONS;
	if (isEmpty(msgErrPointsWithSameName)) msgErrPointsWithSameName=MSG_ERR_POINTS_WITH_SAME_NAME;
	if (isEmpty(msgErrTwoPointsNeed)) msgErrTwoPointsNeed=MSG_ERR_TWO_POINTS_NEED;
	if (isEmpty(msgErrStopInvalidPath)) msgErrStopInvalidPath=MSG_ERR_STOP_INVALID_PATH;
	if (isEmpty(labelPoints)) labelPoints=null;
	if (isEmpty(imgPlay)) imgPlay=null;
	if (isEmpty(imgPause)) imgPause=null;
	if (isEmpty(imgStop)) imgStop=null;
	if (isEmpty(urlIconStart)) urlIconStart=null;
	if (isEmpty(urlIconEnd)) urlIconEnd=null;
	if (isEmpty(marginBottom)) marginBottom=null;
	if (isEmpty(imgChange1)) imgChange1=null;
	if (isEmpty(imgChange2)) imgChange2=null;
	if (isEmpty(urlIconAnimate)) urlIconAnimate=null;
	
	/**
	 * metodo pubblico per la creazione della mappa
	 * 
	 * questo metodo va usato in alternativa a setMap
	 * da agganciare alla pagina chiamante per la creazione della mappa
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/
	this.createMap = function() {
		if (GBrowserIsCompatible()) {
			center=new GLatLng(latCenter, longCenter);
	    	map=new GMap2(document.getElementById(divMap));
	    	map.addMapType(G_PHYSICAL_MAP);
	    	map.addControl(new GLargeMapControl3D());
	        map.addControl(new GMapTypeControl());
	
	        if (urlKml!=null) {
				var kml = new GGeoXml(urlKml);
				  
				map.addOverlay(kml);	        	
	        }
	        
	    	directions = new GDirections(map);
	    	setListenerGMapStandard();
	    	
	    	if (latBound1!=null && longBound1!=null && latBound2!=null && longBound2!=null) {
	    		var bounds=new GLatLngBounds(new GLatLng(latBound1, longBound1), new GLatLng(latBound2, longBound2));
	    		zoom=map.getBoundsZoomLevel(bounds);
	    	}
	    	else {
	    		zoom=ZOOM_DEFAULT;
	    	}

	    	map.setCenter(center, zoom);
		}
		else {
			//$("#"+divMap).html("Il Browser non e' compatibile con Google Maps");
			document.getElementById(divMap).innerHTML="Il Browser non e' compatibile con Google Maps";
		}
	};
   
	/**
	 * metodo pubblico per il centramento della mappa
	 * input:
	 * non previsto
	 * output:
	 * non previsto 
	 */
	this.setCenter = function(latlng, zoom) {
		map.setCenter(latlng, zoom);
	};
	/** 
	 * 
	 * metodo pubblico per associare il componente ad una mappa gia' precedentemente creata
	 * questo metodo va usato in alternativa a createMap
	 * da agganciare alla pagina chiamante per la creazione della mappa
	 * input:
	 * mapNew --> oggetto GMap2 gia' instanziato e impostato
	 * markers --> array di markers presenti nella mappa (necessario per la funzione di editing di punti esistenti)
	 * extendedDataParser --> array di extendedData impostato in precedenza dal parser
	 * output:
	 * non previsto
	*/ 
	this.setMap = function(mapNew, markers, extendedDataParser) {
		map=mapNew;
		if (markers!=null && markers!=undefined) currentMarkers=markers;
		if (extendedDataParser!=null && extendedDataParser!=undefined) currentExtendedData=extendedDataParser;
		
    	directions = new GDirections(map);
    	setListenerGMapStandard();		
	};
	
	/**
	 * metodo privato per determinare l'ultimo indice utilizzato come name dei markers
	 * utilizzato nella funzionalita' di edit della mappa
	 * input:
	 * non previsto
	 * output:
	 * indice intero dal qual far ripartire la numerazione dei markers
	 */
	var getLastIndexMarkers = function() {
		var nidMax=0;
		
		for (var indice=0;indice<markers.length;indice++) {
			var name=markers[indice]["name"];
			
			if (name.length>POINT_PREFIX_NAME.length) {
				if (name.substring(0, POINT_PREFIX_NAME.length)==POINT_PREFIX_NAME) {
					id=name.substring(POINT_PREFIX_NAME.length);
				
					var nid=parseInt(id, 10);
					
					if (nid>nidMax) {
						nidMax=nid;
					}
				}
			}
		}
		
		return (nidMax>markers.length)?nidMax:markers.length;
	};
	/**
	 * metodo privato per determinare l'ultimo indice utilizzato come name delle lines
	 * utilizzato nella funzionalita' di edit della mappa
	 * input:
	 * non previsto
	 * output:
	 * indice intero dal qual far ripartire la numerazione delle lines
	 */
	var getLastIndexLines = function() {
		var nidMax=0;
		
		for (var indice=0;indice<polylines.length;indice++) {
			var name=polylines[indice]["name"];
			
			if (name.length>LINE_PREFIX_NAME.length) {
				if (name.substring(0, LINE_PREFIX_NAME.length)==LINE_PREFIX_NAME) {
					id=name.substring(LINE_PREFIX_NAME.length);
				
					var nid=parseInt(id, 10);
					
					if (nid>nidMax) {
						nidMax=nid;
					}
				}
			}
		}
		
		return (nidMax>polylines.length)?nidMax:polylines.length;
	};	
	
	/**
	 * metodo privato che restituisce gli extendedData relativi ad un placemark
	 * input:
	 * name --> nome del placemark
	 * extendedData --> array di extendedData relativi al kml (restituito dal parser)
	 * output:
	 * array di extended data relativi al placemark
	 */
	var getExtendedData = function(name, extendedData) {
		var data=[];
		
		for (var indice=0;indice<extendedData.length;indice++) {
			var extendedDataCurr=extendedData[indice];
			
			var namePlacemark=extendedDataCurr["namePlacemark"];
			var nameData=extendedDataCurr["name"];
			var valueData=extendedDataCurr["value"];

			if (namePlacemark==name) {
				data[nameData]=(isEmpty(valueData))?"":valueData;
			}
		}
		
		return data;
	};
	
	/**
	 * metodo pubblico usato per la funzionalita' di editing di una mappa a partire dai dati di marker e polylines
	 * va usato in combinazione con il componente EGeoXml che deve restituire la lista di marker e polyline 
	 * input:
	 * mapNew --> oggetto GMap2 gia' instanziato e impostato
	 * markersParser --> array di GMarkers impostato in precendenza dal parser
	 * polylinesParser --> array di GPolyline impostato in precendenza dal parser
	 * extendedDataParser --> array di extendedData impostato in precedenza dal parser
	 * dataPolylines --> array di dati aggiuntivi (name) relativi ai polylines impostato in precendenza dal parser
	 * output:
	 * non previsto
	 */
	this.setMapForEdit = function(mapNew, markersParser, polylinesParser, extendedDataParser, dataPolylinesParser) {
		map=mapNew;
		
		if (markersParser!=null && markersParser!=undefined) {
			currentMarkers=markersParser;
			if (extendedDataParser!=null && extendedDataParser!=undefined) currentExtendedData=extendedDataParser;
			
			for (var indice=0;indice<markersParser.length;indice++) {	
				var marker=markersParser[indice];
				var name=marker.getTitle();
				var data=getExtendedData(name, extendedDataParser);
				var urlImg=data["urlImage"];
				var comment=data["comment"];
				var category=data["category"];
				var urlIconLocal=(isEmpty(data["urlIcon"]))?"":data["urlIcon"];
				
				++indexMarkers;
				
				markers.push({"marker":marker, "name":name, "id":indexMarkers, "urlImg":urlImg, "urlIcon": urlIconLocal, "comment":comment, 
					"category":category});	
				if (!hidePoints) marker.enableDragging();
				
				GEvent.addListener(marker, "dragend", function() {
					listPoints();
				});
					
				GEvent.clearListeners(marker, "click");
				GEvent.clearListeners(marker, "drag");
				GEvent.clearListeners(marker, "dragend");

				GEvent.addListener(marker, "click", function() {
					getListenerClickPoint(this);
				});
				
				GEvent.addListener(marker, "drag", function(latLngDrag) {
					if (this.getIcon().image.indexOf("image_ON.png")==-1) this.setImage(getIconHighlight(this.getIcon().image));
					
					this.closeInfoWindow();
				});		
				
				GEvent.addListener(marker, "dragend", function(latLngDrag) {
					this.setImage(getIconUnhighlight(this.getIcon().image));
				});					
			}
		}
		
		if (polylinesParser!=null && polylinesParser!=undefined) {
			for (var indice=0;indice<polylinesParser.length;indice++) {
				var polyline=polylinesParser[indice];
				++indexLine;
				var name=dataPolylinesParser[indice];
				
				polylines.push({"polyline":polyline, "name":name, "id":indexLine});
			}
		}
		
		indexMarkers=getLastIndexMarkers()+1;
		indexLines=getLastIndexLines()+1;
		
    	directions = new GDirections(map);
    	setListenerGMapStandard();		
    	
    	listPoints();
    	listLines();
	};
	
	/**
	 * metodo pubblico che restituisce il riferimento all'oggeto GMap2 usato internamente
	 * input:
	 * non previsto
	 * output:
	 * oggetto GMap2
	 */
	this.getMap = function() {
		if (map==null || map==undefined) throw new Error("Errore: la mappa non e' stata settata");
		
		return map;
	};
	
	/**
	 * metodo privato per l'impostazione del listener di default da associare al click della mappa (aggiunta di un nuovo point)
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 	
	var setListenerGMapStandard = function() {	
    	listenerGMapStandard=GEvent.addListener(map, "click", function(overlay, latlng, overlaylatlng) {
    		if (!clickAddPoint) return;
    		if (overlay!=null) return;
    		
			eval(nameMap).addPoint({"latlng":latlng});
		});			    	
    	
    	GEvent.addListener(map, "mouseover", function(latlng) {	
    		// abilita la visualizzazione della toolbar se il puntatore del mouse viene spostato nel rettangolo inferiore della mappa di
    		// altezza pari a 20 pixel
    		if (toolbarControl!=null) {
    			var y=map.fromLatLngToContainerPixel(latlng).y;
        		var height=$(map.getContainer()).css("height");
        		var limitHeight=200;
        		if (!isEmpty(height)) limitHeight=parseInt(height, 10)-20;
        		
    			if (y>limitHeight) {
    				if ($("#bg_player_map").css("display")=="none") $("#bg_player_map").css("display","block");
    			}
    		}
		});	
    	
    	GEvent.addListener(map, "mouseout", function(latlng) {	
    		// disabilita la visualizzazione della toolbar se il puntatore del mouse viene allontanato dal rettangolo inferiore della mappa
    		if (toolbarControl!=null) {
    			var y=map.fromLatLngToContainerPixel(latlng).y;
        		var height=$(map.getContainer()).css("height");
        		var limitHeight=200;
        		if (!isEmpty(height)) limitHeight=parseInt(height, 10)-20;
    			
    			if (statoImageGallery==0) {
    				if (y<=limitHeight) {
    					$("#bg_player_map").css("display","none");
    				}
    			}
    		}
		});	    	
	};
	
	/**
	 * metodo privato che restituisce un'icona da associare ad un punto
	 * se e' stato passata un'icona nel parametro pointIconStandard, viene passata questa icona
	 * altrimenti ne crea una a partire dai parametri url, width e height
	 * input:
	 * url --> url dell'icona
	 * width --> larghezza in pixel dell'icona
	 * height --> altezza in pixel dell'icona
	 * output:
	 * oggetto GIcon
	*/ 	
	var getIcon = function(url, width, height) {
		if (pointIconStandard!=null) return pointIconStandard;
		
		if (url==null) return null;
		var small=url.indexOf("Small")>-1;
		
        var image=new Image();
        image.src=url;
        height=image.height;
        width=image.width;
        
        var urlShadow=(url.indexOf("image.png")>-1)?url.replace("image.png", "shadow.png"):"";
        var widthShadow=0,heightShadow=0;
        
        if (urlShadow!="") {
            var image=new Image();
            image.src=urlShadow;
            heightShadow=image.height;
            widthShadow=image.width;        	
        }
        
		var icon = new GIcon();
		icon.image = url;
		icon.shadow = urlShadow;
		icon.shadowSize = new GSize(widthShadow, heightShadow);
		icon.iconSize = new GSize(width, height);
		icon.iconAnchor = (small)?new GPoint(10,24):new GPoint(20,47);	
		icon.infoWindowAnchor = (small)?new GPoint(10,0):new GPoint(20,0);	 //indispensabile per il corretto funzionamento del metodo openInfoWindowHtml!!
		
		icon.printImage = (url.indexOf("image.png")>-1)?url.replace("image.png", "printImage.gif"):"";
		icon.mozPrintImage = (url.indexOf("image.png")>-1)?url.replace("image.png", "mozPrintImage.gif"):""; 
		icon.printShadow = (url.indexOf("image.png")>-1)?url.replace("image.png", "printShadow.gif"):"";  
		icon.transparent = (url.indexOf("image.png")>-1)?url.replace("image.png", "transparent.png"):""; 	
		
		icon.imageMap = (small)?
					[10,1,13,2,16,3,16,4,17,5,18,6,18,7,19,8,19,9,19,10,19,11,19,12,19,13,18,14,18,15,17,16,17,17,16,18,15,19,15,20,14,21,13,22,12,23,8,23,7,22,6,21,5,20,4,19,3,18,2,17,2,16,1,15,2,14,1,13,1,12,1,11,1,10,1,9,1,8,1,7,1,6,2,5,3,4,4,3,5,2,7,1]:
					[27,3,29,4,31,5,32,6,34,7,35,8,36,9,36,10,37,11,38,12,38,13,38,14,39,15,39,16,39,17,39,18,39,19,39,20,39,21,39,22,39,23,39,24,39,25,39,26,39,27,38,28,38,29,38,30,37,31,37,32,36,33,35,34,35,35,34,36,33,37,32,38,32,39,31,40,30,41,29,42,28,43,27,44,26,45,24,46,17,46,15,45,14,44,13,43,12,42,11,41,10,40,9,39,9,38,7,37,7,36,6,35,6,34,5,33,5,32,4,31,3,30,3,29,2,28,2,27,2,26,1,25,1,24,1,23,1,22,2,21,2,20,2,19,2,18,3,17,3,16,3,15,3,14,4,13,4,12,5,11,6,10,6,9,7,8,8,7,10,6,11,5,13,4,16,3];
		return icon;
	};
	
	/**
	 * metodo privato che restituisce il nome dell'icona highlight a partire dall'icona corrente
	 * input:
	 * url --> url dell'icona
	 * output:
	 * stringa --> nome icona highlight 
	*/ 		
	var getIconHighlight = function(url) {
		if (isEmpty(url)) url=urlIcon;
		var appo=(url.indexOf("image.png")>-1)?url.replace("image.png", "image_ON.png"):url;
		
		return appo;
	};
	
	/**
	 * metodo privato che restituisce il nome dell'icona standard a partire dall'icona corrente highlight
	 * input:
	 * url --> url dell'icona
	 * output:
	 * stringa --> nome icona standard 
	*/ 		
	var getIconUnhighlight = function(url) {
		if (isEmpty(url)) url=urlIcon;
		var appo=(url.indexOf("image_ON.png")>-1)?url.replace("image_ON.png", "image.png"):url;
		
		return appo;
	};
	
	/**
	 * metodo privato che rappresenta il metodo da associare al click di ogni punto creato nella mappa
	 * input:
	 * marker --> marker al quale assegnare il listener di click
	 * output:
	 * non previsto
	*/ 
	var getListenerClickPoint = function(marker) {
		var nameMarkerClick=marker.getTitle();
		var idToEditOrRemove;
		var urlImg="";
		var category="";
		var comment="";
		
		for (var indice=0;indice<markers.length;indice++) {
			var nameMarker=markers[indice]["name"];
			var idMarker=markers[indice]["id"];
			urlImg=markers[indice]["urlImg"];
			category=markers[indice]["category"];
			comment=markers[indice]["comment"];
	
			if (nameMarkerClick==nameMarker) {
				idToEditOrRemove=idMarker;
				break;
			}
		}
		
		var html="";
		
		html+=isEmpty(urlImg)?"<div>":"<div>";
		//title
		
		//html+="<div style='font-family: 	Verdana,Geneva,sans-serif;font-size: 12px;color: #0000FF;font-weight:bold'>"+nameMarkerClick+"</div><br/>";
		html+="<div style=\"height:45px;\">";
		html+="<p class=\"title_box_poi_balloon\">";
		html+=nameMarkerClick;
		html+="</p>";
		html+="</div>";
		
		//category
		if (!isEmpty(category)) {
			//html+="<div style='font-family: Verdana,Geneva,sans-serif;font-size: 12px;color: #0000FF'>"+category+"</div><br/>";
			html+="<p class=\"commento_poi2\">";
			html+=category;
			html+="</p>";
		}
		
		//immagine
		//html+=(!isEmpty(urlImg)?"<div><img height=74 width=111 src='"+urlImg+"'/></div>":"");
		
		if (isEmpty(urlImg)) {
			//comment
			if (!isEmpty(comment)) {
				//html+="<div style='font-family: Verdana,Geneva,sans-serif;font-size: 12px;color: #0000FF'>"+comment+"</div><br/>";
				html+="<p class=\"commento_poi2\">";
				html+=unescape(comment.replace(/\+/g, " "));
				html+="</p>";
			}	
		}
		else {
			html+="<div class=\"img_box_poi\">";
			html+="<img src='"+urlImg+"' style=\"border:#FFF 1px solid\" />";
			
			if (!isEmpty(comment)) {
				html+="<p class=\"commento_poi\">";
				html+=unescape(comment.replace(/\+/g, " "));
				html+="</p>";
			}
		}
		
		if (!hidePoints) {
			//html+="<div>";
			html+="<div class=\"edit_remove\">";
			
			//link edit
			if (linkEdit) {
				//html+="<div style='float:left;font-family: Verdana,Geneva,sans-serif;font-size: 12px'><a href='#' onclick='javascript:"+nameMap+".editPoint("+idToEditOrRemove+")'>Edit</a></div>";
				html+="<span class=\"edit\">";
				html+="<a href=\"javascript:void(0)\" onclick='javascript:"+nameMap+".editPoint("+idToEditOrRemove+")'>Edit</a>";
				html+="</span>";
			}
			
			//link remove
			//html+="<div style='"+((linkEdit)?"float:right;":"")+"font-family: Verdana,Geneva,sans-serif;font-size: 12px'><a href='#' onclick='javascript:"+nameMap+".removePoint("+idToEditOrRemove+")'>Remove</a></div>";
			html+="<span class=\"remove\">";
			html+="<a href=\"javascript:void(0)\" onclick='javascript:"+nameMap+".removePoint("+idToEditOrRemove+")'>Remove</a>";
			html+="</span>";
			
			html+="</div>"; //end edit_remove
		}
		
		html+="</div>";

		marker.openInfoWindowHtml(html);	
	};	
	
	/**
	 * metodo privato che rappresenta il metodo da associare al click di ogni punto con un html personalizzato
	 * input:
	 * marker --> marker al quale assegnare il listener di click
	 * html --> html da inserire nella window associata al click
	 * output:
	 * non previsto
	*/ 	
	var getListenerClickPointHtml = function(marker, html) {
		marker.openInfoWindowHtml(html);	
	};	
	
	/**
	 * metodo pubblico che aggiunge un marker editabile alla mappa google
	 * il punto viene sempre posizionato al centro della mappa corrente
	 * da agganciare alla pagina chiamante per la creazione di un nuovo point
	 * input:
	 * struttura parameters contenente i seguenti parametri:
	 * latlng --> coordinate cui posizionare il punto
	 * name --> nome da associare al punto
	 * urlImg --> url immagine associata al punto
	 * func --> function chiamata con latitudine e longitudine sull'evento di drag
	 * draggable --> booleano, indica se il punto e' editabile oppure no
	 * html --> se viene valorizzato il parametro, l'html viene associato all'info window
	 * category --> parametro facoltativo che indica la categoria di appartenenza del punto
	 * urlIconPoint --> parametro facoltativo che indica l'icona da associare al punto 
	 * output:
	 * non previsto
	*/ 
	this.addPoint = function(parameters) {
		var latlng=parameters["latlng"];
		var name=parameters["name"];
		var urlImg=parameters["urlImg"];
		var func=parameters["func"];
		var draggable=parameters["draggable"];
		var html=parameters["html"];
		var category=parameters["category"];
		var urlIconPoint=parameters["urlIconPoint"];
		
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditLine!=-1 || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1) 
			return;
		
		if (draggable==null || draggable==undefined) draggable=true;
		if (isEmpty(html)) html=null;
		if (isEmpty(category)) category="";
		
		++indexMarkers;
		
		var name=isEmpty(name)?POINT_PREFIX_NAME+indexMarkers:name;
		var icona = getIcon(isEmpty(urlIconPoint)?urlIcon:urlIconPoint, widthIcon, heightIcon);
		var marker = new GMarker((latlng!=null && latlng!=undefined)?latlng:map.getBounds().getCenter(), {draggable: draggable, title: name, icon: icona!=null?icona:G_DEFAULT_ICON});

		markers.push({"marker":marker, "name":name, "id":indexMarkers, "urlImg":urlImg, "urlIcon":isEmpty(urlIconPoint)?"":urlIconPoint, "comment":"", "category": category});
		
		GEvent.addListener(marker, "dragend", function() {
			listPoints();
		});
			
		if (html==null) {
			GEvent.addListener(marker, "click", function() {
				getListenerClickPoint(marker);
			});
			
			GEvent.addListener(marker, "drag", function(latLngDrag) {
				if (this.getIcon().image.indexOf("image_ON.png")==-1) this.setImage(getIconHighlight(this.getIcon().image));
				
				this.closeInfoWindow();
				
				if (func!=null) func(latLngDrag.lat().toString(), latLngDrag.lng().toString());
			});
			
			GEvent.addListener(marker, "dragend", function(latLngDrag) {
				this.setImage(getIconUnhighlight(this.getIcon().image));
			});					
		}
		else {
			GEvent.addListener(marker, "click", function() {
				getListenerClickPointHtml(marker, html);
			});			
		}
		
	    map.addOverlay(marker);	

	    listPoints();
	};
	
	/**
	 * metodo pubblico che rimuove tutti i punti della mappa
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	 */
	this.removeAllPoints = function() {
		for (var indice=0;indice<markers.length;indice++) {
			var marker=markers[indice]["marker"];
			
			map.removeOverlay(marker);
		}
		
		markers=[];
	};
	
	/**
	 * metodo privato per ricavare l'id image a partire dall'url
	 * input: 
	 * url --> url immagine
	 * output:
	 * string --> id immagine
	 */
	var getIdImage = function(url) {
		if (isEmpty(url)) return null;

		var pos1=url.indexOf("?");
		if (pos1<0) return null;
		
		var appo=url.substring(pos1);
		var arr=appo.split("&");
		
		if (arr.length==0) return null;
		
		for (var indice=0;indice<arr.length;indice++) {
			var par=arr[indice].split("=");
			
			if (par.length<2) continue;
			
			if (par[0].toLowerCase()=="id") return par[1];
		}
		
		return null;
	};
	
	/**
	 * metodo pubblico che rende editabile un punto preesistente nella mappa
	 * input:
	 * name --> nome del punto da editare
	 * urlImg --> url immagine associata al punto
	 * func --> function chiamata con latitudine e longitudine sull'evento di drag
	 * output:
	 * non previsto
	 */
	this.editPointWithImage = function(name, urlImg, func) {
		if (currentMarkers.length==0) throw new Error("Nella mappa non sono presenti punti da editare");
		
		//se viene richiamato il metodo su un punto gia' editato, esce immediatamente
		for (var indice=0;indice<markers.length;indice++) {
			var nameMarkerAlredyEdit=markers[indice]["name"];
			var urlImgAlredyEdit=markers[indice]["urlImg"];
			var idImgAlredyEdit=getIdImage(urlImgAlredyEdit);
			
			if (idImgAlredyEdit==name) return;
		}
		
		var markerEdit;
		var trovato=false;
		var nameEdit="";
		var currentUrlIcon="";
		
		for (var indice=0;indice<currentMarkers.length;indice++) {
			var currentMarker=currentMarkers[indice];
			var currentData=getExtendedData(currentMarker.getTitle(), currentExtendedData);
			var currentUrlImg=currentData["urlImage"];
			var currentIdImg=getIdImage(currentUrlImg);
			currentUrlIcon=currentData["urlIcon"];
			
			if (currentIdImg==null) continue;
				
			if (currentIdImg==name) {
				markerEdit=currentMarker;
				nameEdit=currentMarker.getTitle();
				trovato=true;
				
				break;
			}
		}
		
		if (trovato) {
			var icona = getIcon(urlIcon, widthIcon, heightIcon);
			icona.image=getIconHighlight(icona.image);

			var newMarker = new GMarker(markerEdit.getLatLng(), {draggable: true, title: nameEdit, icon: icona!=null?icona:G_DEFAULT_ICON});
			
			GEvent.addListener(newMarker, "click", function() {
				getListenerClickPoint(newMarker);
			});
			
			GEvent.addListener(newMarker, "drag", function(latLngDrag) {
				//if (this.getIcon().image.indexOf("image_ON.png")==-1) this.setImage(getIconHighlight(this.getIcon().image));
				
				this.closeInfoWindow();
				
				if (func!=null) func(latLngDrag.lat().toString(), latLngDrag.lng().toString());
			});
			
			/*GEvent.addListener(newMarker, "dragend", function(latLngDrag) {
				this.setImage(getIconUnhighlight(this.getIcon().image));
			});*/	
						
			map.removeOverlay(markerEdit);
			map.addOverlay(newMarker);
			
			++indexMarkers;
			
			markers.push({"marker":newMarker, "name":nameEdit, "id":indexMarkers, "urlImg":urlImg, "urlIcon":"", "comment":"", "category":""});
			
			map.setCenter(newMarker.getLatLng());
		}
	};
	
	/**
	 * metodo pubblico per la gestione dell'evidenziazione di un punto sulla mappa
	 * input:
	 * idImgHighlight --> id Img del punto da evidenziare
	 * output:
	 * non previsto
	 */
	this.highlightPointWithImage = function(idImgHighlight) {
		if (currentMarkers.length==0) return;
		
		if (currentHighlight!=null) {
			var markerHighlight=currentHighlight["marker"];
			var urlIconHighlight=currentHighlight["urlIcon"];
			
			markerHighlight.setImage(urlIconHighlight);
			currentHighlight=null;
		}
		
		var markerEdit;
		var trovato=false;
		var currentUrlIcon;

		for (var indice=0;indice<currentMarkers.length;indice++) {
			var currentMarker=currentMarkers[indice];
			var currentData=getExtendedData(currentMarker.getTitle(), currentExtendedData);
			var currentUrlImg=currentData["urlImage"];
			var currentIdImg=getIdImage(currentUrlImg);
			currentUrlIcon=currentData["urlIcon"];
			
			if (currentIdImg==null) continue;
				
			if (currentIdImg==idImgHighlight) {
				markerEdit=currentMarker;
				trovato=true;
				currentHighlight={"marker":currentMarker, "urlIcon":currentUrlIcon};
				
				break;
			}
		}
		
		if (trovato) {
			markerEdit.setImage(getIconHighlight(currentUrlIcon));

			//commentato perche' troppo pesante per IE nell'animazione dell'image gallery  
			//map.setCenter(markerEdit.getLatLng());
		}		
	};
	
	/**
	 * metodo pubblico che rimuove un marker dalla mappa google
	 * input:
	 * l'id interno associato al marker
	 * output:
	 * non previsto
	*/ 
	this.removePoint = function(id) {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads|| indiceEditLine!=-1 || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1) 
			return;
		
		var marker;
		var trovato=false;
		var indiceToRemove=0;
		
		for (var indice=0;indice<markers.length;indice++) {
			marker=markers[indice]["marker"];
			var name=markers[indice]["name"];
			var idMarker=markers[indice]["id"];
	
			if (idMarker==id) {
				trovato=true;
				indiceToRemove=indice;
				break;
			}
		}
		
		if (trovato) {
			map.removeOverlay(marker);
	
			markers.splice(indiceToRemove, 1);
		}
		
		listPoints();
	};
	
	/**
	 * metodo pubblico che aggiunge una linea alla mappa google
	 * da agganciare alla pagina chiamante per la creazione di una nuova linea
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 
	this.addLine = function() {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditLine!=-1 || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1) 
			return;
		
		bAddLine=true;		
		var name='Line'+(++indexLine);
		var polylineOptions = {geodesic:true};
		
		var polyline = new GPolyline([], lineColor, lineSize, 1, polylineOptions);
		map.addOverlay(polyline);
		
		polyline.enableDrawing();

		GEvent.addListener(polyline, "endline", function() {
			listLines();
			bAddLine=false;
		});

		polylines.push({"polyline":polyline, "name":name, "id":indexLine});
		
		listLines();
	};
	
	/**
	 * metodo pubblico che rimuove una linea dalla mappa google
	 * input:
	 * l'id interno associato alla linea
	 * output:
	 * non previsto
	*/ 
	this.removeLine = function(id) {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditLine!=-1 || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1) 
			return;
		
		var polyline;
		var trovato=false;
		var indiceToRemove=0;
		
		for (var indice=0;indice<polylines.length;indice++) {
			polyline=polylines[indice]["polyline"];
			var name=polylines[indice]["name"];
			var idLine=polylines[indice]["id"];
	
			if (id==idLine) {
				trovato=true;
				indiceToRemove=indice;
				break;
			}
		}
		
		if (trovato) {
			map.removeOverlay(polyline);
	
			polylines.splice(indiceToRemove, 1);
		}
		
		listLines();
	};	
		
	/**
	 * metodo pubblico che rende editabile una linea dalla mappa google
	 * ha un doppio comportamento a seconda se chiamata la prima volta per rendere editabile la linea ed una seconda volta per concludere l'operazione (Done)
	 * input:
	 * l'id interno associato alla linea
	 * output:
	 * non previsto
	*/ 
	this.editLine = function(id) {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1) return;
		
		var polyline;
		var trovato=false;
		var indiceToEdit=0;
		
		if (indiceEditLine==-1) {
			for (var indice=0;indice<polylines.length;indice++) {
				polyline=polylines[indice]["polyline"];
				var name=polylines[indice]["name"];
				var idLine=polylines[indice]["id"];
		
				if (id==idLine) {
					trovato=true;
	
					indiceEditLine=idLine;
					break;
				}
			}
			
			if (trovato) {
				polyline.enableEditing();
				
				if (polyline.getVertexCount()>0) {
					map.setCenter(polyline.getVertex(0));
				}
			}
		}
		else {
			for (var indice=0;indice<polylines.length;indice++) {
				polyline=polylines[indice]["polyline"];
				var name=polylines[indice]["name"];
				var idLine=polylines[indice]["id"];
		
				if (idLine==indiceEditLine) {
					trovato=true;
					indiceToEdit=indice;
					
					break;
				}
			}
			
			if (trovato) {
				//polylines[indiceToEdit]["name"]=$("#txtLine"+indiceEditLine).val();
				polylines[indiceToEdit]["name"]=document.getElementById("txtLine"+indiceEditLine).value;
				polyline.disableEditing();
				indiceEditLine=-1;
			}		
		}
		 
		listLines();
	};
	
	/**
	 * metodo pubblico che rende editabile un punto dalla mappa google
	 * ha un doppio comportamento a seconda se chiamata la prima volta per rendere editabile il punto ed una seconda volta per concludere l'operazione (Done)
	 * restituisce un errore se tramite l'edit del punto viene assegnato un nome ad un punto gia' assegnato ad altro punto
	 * input:
	 * l'id interno associato al punto
	 * output:
	 * non previsto
	*/ 
	this.editPoint = function(id) {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditLine!=-1 || indiceEditLineAlongRoads!=-1) return;
		
		var marker;
		var trovato=false;
		var indiceToEdit=0;
		var currentUrlIcon="";
		
		if (indiceEditPoint==-1) {
			for (var indice=0;indice<markers.length;indice++) {
				marker=markers[indice]["marker"];
				var idPoint=markers[indice]["id"];
				currentUrlIcon=markers[indice]["urlIcon"];
		
				if (id==idPoint) {
					trovato=true;
	
					indiceEditPoint=idPoint;
					break;
				}
			}
			
			marker.setImage(getIconHighlight(currentUrlIcon));
			map.setCenter(marker.getLatLng());
		}
		else {
			var nameEdit=document.getElementById("txtPoint"+indiceEditPoint).value;
			var markerTrovato;
			var urlImg,urlIconLocal,urlImgTrovato,urlIconLocalTrovato,commentTrovato,categoryTrovata;
			
			for (var indice=0;indice<markers.length;indice++) {
				marker=markers[indice]["marker"];
				var idPoint=markers[indice]["id"];
				var namePoint=markers[indice]["name"];
				urlImg=markers[indice]["urlImg"];
				urlIconLocal=markers[indice]["urlIcon"];
				var comment=markers[indice]["comment"];
				var category=markers[indice]["category"];
		
				if (idPoint==indiceEditPoint) {
					trovato=true;
					indiceToEdit=indice;
					markerTrovato=marker;
					urlImgTrovato=urlImg;
					urlIconLocalTrovato=urlIconLocal;
					commentTrovato=comment;
					categoryTrovata=category;
				}
				else if (namePoint==nameEdit) {
					alert(msgErrPointsWithSameName);
					
					return;
				}
			}
			
			if (trovato) {
				//sostituisce il marker da rinominare con un nuovo marker avente il nuovo nome e gli altri parametri uguali al marker di partenza
				var name=nameEdit;
				var icona = getIcon(urlIconLocalTrovato!=""?urlIconLocalTrovato:urlIcon, widthIcon, heightIcon);
				var newMarker = new GMarker(markerTrovato.getLatLng(), {draggable: true, title: name, icon: icona!=null?icona:G_DEFAULT_ICON});
				
				GEvent.addListener(newMarker, "click", function() {
					getListenerClickPoint(newMarker);
				});
				
				GEvent.addListener(newMarker, "drag", function(latLngDrag) {
					if (this.getIcon().image.indexOf("image_ON.png")==-1) this.setImage(getIconHighlight(this.getIcon().image));
					
					this.closeInfoWindow();
				});
					
				GEvent.addListener(newMarker, "dragend", function(latLngDrag) {
					this.setImage(getIconUnhighlight(this.getIcon().image));
				});	

				markers.splice(indiceToEdit, 1);
				markers.push({"marker":newMarker, "name":name, "id":indiceEditPoint, "urlImg":urlImgTrovato, 
					"urlIcon":urlIconLocalTrovato!=""?urlIconLocalTrovato:urlIcon, "comment":commentTrovato, "category": categoryTrovata});
				
				map.removeOverlay(markerTrovato);
				map.addOverlay(newMarker);
				
				indiceEditPoint=-1;
			}		
		}
		
		marker.closeInfoWindow();
	
		listPoints();
	};
	
	/**
	 * metodo privato che renderizza una lista html dei punti creati nella mappa
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 
	var listPoints = function() {
		if (!visDati || hidePoints) return;
		
		//impedisce cambiamenti nell'ordine della lista
		markers.sort(function(a, b) {return (a["name"].toLowerCase()<b["name"].toLowerCase())?-1:1;});
		
		var html="<div class=\"bottom\">";
		
		for (var indice=0;indice<markers.length;indice++) {
			var marker=markers[indice]["marker"];
			var title=markers[indice]["name"];
			var idPoint=markers[indice]["id"];
			var urlIconLocale=markers[indice]["urlIcon"];
			
			html+="<div class=\"container\">";
			html+="<div class=\"image\" style=\"text-align:center\">";
			html+="<img src='"+(!isEmpty(urlIconLocale)?urlIconLocale:urlIcon).replace("image.png", "image_small.png")+"' />";
			html+="</div>";
			
			if (idPoint==indiceEditPoint) {
				html+="<div class=\"left\">";
				html+="<span id='Point"+idPoint+"'>";
				
				if (labelPoints!=null) {
					html+=labelPoints;
				}
				
				html+="<input type='text' id='txtPoint"+idPoint+"' name='txtPoint"+idPoint+"' style='width:150px;height:16px' value=\""+title+"\"> ";
				if (debug) html+=" "+listCoordinatesPoint(marker);
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+=" <a id='linkEditPoint'"+idPoint+" href=javascript:"+nameMap+".editPoint('"+idPoint+"')>DONE</a>&nbsp;";
				}
				
				html+="</div>";
			}
			else {
				html+="<div class=\"left\">";
				html+="<span id='Point"+idPoint+"'>";
				
				if (labelPoints!=null) {
					html+=labelPoints;
				}
				
				html+=title+" ";
				if (debug) html+=" "+listCoordinatesPoint(marker);
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+=" <a href=javascript:"+nameMap+".editPoint('"+idPoint+"')>EDIT</a>&nbsp;";
				}
				
				html+="</div>";
			}

			html+="<div class=\"right\">";
			html+=" <a href=javascript:"+nameMap+".removePoint('"+idPoint+"')>REMOVE</a>";
			html+="</div>";
			html+="</div>"; //end container
		}	
		
		html+="</div>"; //end bottom
		
		document.getElementById(divPoints).innerHTML=html;
		
		if (indiceEditPoint!=-1) {
			document.getElementById("txtPoint"+indiceEditPoint).focus();
			window.location.hash="linkEditPoint"+indiceEditPoint;
		}
	};
	
	/**
	 * metodo privato che renderizza una lista html delle linee create nella mappa
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 
	var listLines = function() {
		if (!visDati || hideLines) return;
		
		//impedisce cambiamenti nell'ordine della lista
		polylines.sort(function(a, b) {return (a["name"].toLowerCase()<b["name"].toLowerCase())?-1:1;});
		linesAlongRoads.sort(function(a, b) {return (a["name"].toLowerCase()<b["name"].toLowerCase())?-1:1;});
		
		var html="<div class=\"bottom\">";
		
		for (var indice=0;indice<polylines.length;indice++) {
			html+="<div class=\"container\">";
			html+="<div class=\"image\" style=\"text-align:center\">";
			html+="</div>";
			
			var polyline=polylines[indice]["polyline"];
			var coordinates=listCoordinatesLine(polyline);
			
			var title=polylines[indice]["name"];
			var idLine=polylines[indice]["id"];
			//***Modifica Et1detofpa***
			var lengthLine=0;
			try{
				lengthLine=polyline.getLength();
			}catch (e) {
				//lunghezza 0 nel caso si verifichi un errore
			}
			
			if (idLine==indiceEditLine) {
				html+="<div class=\"left\">";
				html+="<span id='Line"+idLine+"'>";
				html+="<input type='text' id='txtLine"+idLine+"' name='txtLine"+idLine+"' style='width:150px;height:16px' value=\""+title+"\"> ";
				//***Modifica Et1detofpa***
				if (debug) html+="Length Line: "+lengthLine+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a id='linkEditLine'"+idLine+" href=javascript:"+nameMap+".editLine('"+idLine+"')>DONE</a>&nbsp;";
				}
				
				html+="</div>";
			}
			else {
				html+="<div class=\"left\">";
				html+="<span id='Line"+idLine+"'>"+title+" ";
				//***Modifica Et1detofpa***
				if (debug) html+="Length Line: "+lengthLine+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a href=javascript:"+nameMap+".editLine('"+idLine+"')>EDIT</a>&nbsp;";
				}
				
				html+="</div>";
			}
			
			html+="<div class=\"right\">";
			html+=" <a href=javascript:"+nameMap+".removeLine('"+idLine+"')>REMOVE</a><br/>";
			html+="</div>";
			html+="</div>"; //end container
		}	
		
		for (var indice=0;indice<linesAlongRoads.length;indice++) {
			html+="<div class=\"container\">";
			html+="<div class=\"image\" style=\"text-align:center\">";
			html+="</div>";
			
			var idLine=linesAlongRoads[indice]["id"];
			var title=linesAlongRoads[indice]["name"];
			var polyline=linesAlongRoads[indice]["polyline"];
			//***Modifica  Et1detofpa***
			var distance=linesAlongRoads[indice]["distance"];
			var coordinates=listCoordinatesLine(polyline);
			
			if (idLine==indiceEditLineAlongRoads) {
				html+="<div class=\"left\">";
				html+="<span id='LineAlongRoads"+idLine+"'>";
				html+="<input type='text' id='txtLineAlongRoads"+idLine+"' name='txtLineAlongRoads"+idLine+"' style='width:150px;height:16px' value=\""+title+"\"> ";
				//***Modifica  Et1detofpa***
				if (debug) html+="Distance: "+distance+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a id='linkEditLineAlongRoads'"+idLine+" href=javascript:"+nameMap+".editLineAlongRoads('"+idLine+"')>DONE</a>&nbsp;";
				}
				
				html+="</div>";
			}
			else {
				html+="<div class=\"left\">";
				html+="<span id='LineAlongRoads"+idLine+"'>"+title+" ";
				//***Modifica  Et1detofpa***
				if (debug) html+="Distance: "+distance+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a href=javascript:"+nameMap+".editLineAlongRoads('"+idLine+"')>EDIT</a>&nbsp;";
				}
				
				html+="</div>";
			}
				
			html+="<div class=\"right\">";
			html+=" <a href=javascript:"+nameMap+".removeLineAlongRoads('"+idLine+"')>REMOVE</a><br/>";
			html+="</div>";
			html+="</div>"; //end container
		}			
		
		html+="</div>"; //end bottom
		
		//$("#"+divLines).html(html);
		document.getElementById(divLines).innerHTML=html;
		
		if (indiceEditLine!=-1) {
			document.getElementById("txtLine"+indiceEditLine).focus();
			window.location.hash="linkEditLine"+indiceEditLine;
		}		
		
		if (indiceEditLineAlongRoads!=-1) {
			document.getElementById("txtLineAlongRoads"+indiceEditLineAlongRoads).focus();
			window.location.hash="linkEditLineAlongRoads"+indiceEditLineAlongRoads;
		}			
	};
	
	/**
	 * metodo privato che restituisce una lista di coordinate associate ad una linea
	 * input:
	 * polyline --> un oggetto di tipo polyline del quale restituire le coordinate
	 * output:
	 * una stringa in formato latitudine1, longitudine1  latitudine2, longitudine2, ...
	*/   
	var listCoordinatesLine = function(polyline) {
		var numVertici=polyline.getVertexCount();
		var coordinates="";
		
		for (var indice=0;indice<numVertici;indice++) {
			var latlng=polyline.getVertex(indice);
			coordinates+=latlng.lat()+","+latlng.lng()+" ";
		}
	
		return coordinates;
	};	
	
	/**
	 * metodo privato che restituisce una lista di coordinate associate ad un punto
	 * input:
	 * marker --> un oggetto di tipo marker del quale restituire le coordinate
	 * output:
	 * una stringa in formato latitudine,longitudine
	*/ 
	var listCoordinatesPoint = function(marker) {
		return marker.getLatLng().lat()+","+marker.getLatLng().lng();
	};
	
	/**
	 * metodo pubblico che restituisce la lista dei polylines (linee) tracciati nella mappa
	 * input:
	 * non previsto
	 * output:
	 * la lista dei polylines
	*/ 
	this.getPolylines = function() {
		return polylines;
	};
	
	/**
	 * metodo pubblico che restituisce la lista dei markers (punti) tracciati nella mappa
	 * input:
	 * non previsto
	 * output:
	 * la lista dei markers
	*/ 	
	this.getMarkers = function() {
		return markers;
	};
	
	/**
	 * metodo pubblico per la creazione di una linea 'along roads'
	 * la linea puo' contenere un massimo di 25 punti
	 * input:
	 * oggetto GLatLng --> coordinata da cui far partire la linea
	 * output:
	 * boolean --> esito dell'operazione
	*/ 	
	this.addLineAlongRoads = function(latlng) {
		if (bAddLine || indiceEditLine!=-1 || indiceEditPoint!=-1) 
			return false;
		
		this.stopAnimateLinesAlongRoads();
		
		if (markersAlongRoads.length>=25) {
			alert(msgLinesAlongRoadsMax25Points);
			
			return false;
		}
		
		bAddLineAlongRoads=true;
		++indexMarkers;
		
		var name=POINT_PREFIX_NAME+indexMarkers;
		var icona = getIcon(urlIcon, widthIcon, heightIcon);
		var marker = new GMarker((latlng!=null && latlng!=undefined)?latlng:map.getBounds().getCenter(), {draggable: true, title: name, icon: icona!=null?icona:G_DEFAULT_ICON});
		
		map.addOverlay(marker);	
		markersAlongRoads.push(marker);
		
		GEvent.addListener(marker, "drag", function() {
			createLineAlongRoads();
		});
		GEvent.addListener(marker, "dragend", function() {
			createLineAlongRoads();
		});		
		/*GEvent.addListener(marker, "click", function() {
			eval(nameMap).endLineAlongRoads();
		});*/			
		
		if (markersAlongRoads.length==1) {
			switchListenerGMapAlongRoads();			
		}
		
		createLineAlongRoads();
		//listLinesAlongRoads();
		listLines();
		
		return true;
	};
	
	/**
	 * metodo privato usato internamente per il ridisegno della linea 'along roads', per es.: durante l'evento di drag
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 		
	var createLineAlongRoads = function() {
	    if (markersAlongRoads.length>1) {
	    	var waypoints=[];
	    	
	    	for (var indice=0;indice<markersAlongRoads.length;indice++) {
	    		var marker=markersAlongRoads[indice];
	    		
	    		/*GEvent.clearListeners(marker, "drag");
	    		GEvent.clearListeners(marker, "dragend");*/
	    		waypoints.push(marker.getLatLng());
	    	}
	    	
	    	directions.clear();
	    	directions.loadFromWaypoints(waypoints, {preserveViewport:true});
	    	
	    	GEvent.clearListeners(directions, "error");
	    	GEvent.clearListeners(directions, "load");
	    	
	    	GEvent.addListener(directions, "error", function() {
	    		alert(msgErrDirections);
	    	});
	    	
	    	GEvent.addListener(directions, "load", function() {
		    	for (var indice=0;indice<markersAlongRoads.length;indice++) {
		    		var marker=markersAlongRoads[indice];
		    		
		    		GEvent.clearListeners(marker, "drag");

		    		GEvent.addListener(marker, "drag", function() {
		    			createLineAlongRoads();
		    		});
		    		
		    		//posiziona i marker 'alfabetici' al polo nord --> unico modo che ho trovato per 'nasconderli'
		    		var dir = directions.getMarker(indice);
		    		if(dir!=null){
		    			dir.setLatLng(new GLatLng(90,0)); 
		    		}
		    		//directions.getMarker(indice).setLatLng(new GLatLng(90,0)); 
		    	}
		    	
		    		
				var polyline=directions.getPolyline();
				if(polyline==null){return;}
				GEvent.clearListeners(polyline, "click");
				GEvent.clearListeners(polyline, "mouseout");
				GEvent.clearListeners(polyline, "mouseover");
				
				GEvent.addListener(polyline, "mouseout", function() {
					polyline.setStrokeStyle({opacity: OPACITY_STANDARD});
				});				
				GEvent.addListener(polyline, "mouseover", function() {
					polyline.setStrokeStyle({opacity: OPACITY_HIGHLIGHT});
				});
				GEvent.addListener(polyline, "click", function(latlngClick) {
					gestAddNewPointEditLineAlongRoads(polyline, latlngClick);
				});						
	    	});   		
	    }	
	};
	
	/**
	 * metodo pubblic usato per la chiusura (consolidamento) della linea 'along roads', per es.:al click di un punto della linea
	 * input:
	 * non previsto
	 * output:
	 * boolean --> esito dell'operazione
	*/ 		
	this.endLineAlongRoads = function() {
		if (markersAlongRoads.length<2) {
			alert(msgErrTwoPointsNeed);
			return false;
		}	
		
		var polyline=directions.getPolyline();
		//***modifica  Et1detofpa***
		var distanceRoads=directions.getDistance().meters;
		
		if(polyline==null){
			alert(msgErrStopInvalidPath);
			return false;
		}
		
    	for (var indice=0;indice<markersAlongRoads.length;indice++) {
    		var marker=markersAlongRoads[indice];
    		
    		marker.disableDragging();
    		
    		if (indice==0) {
    			marker.setImage(urlIconStart);
    		}
    		else if (indice==markersAlongRoads.length-1) {
    			marker.setImage(urlIconEnd);
    		}
    		
    	}
    	   	
    	directions.clear();
    	map.addOverlay(polyline);
    	
    	GEvent.removeListener(listenerGMapLinesAlongRoads);
    	setListenerGMapStandard();

		if (bAddLineAlongRoads && !bEditLineAlongRoads) {
			
			var name=LINE_PREFIX_NAME+(++indexLine);
			//***modifica  Et1detofpa***
			linesAlongRoads.push({"id":indexLine, "name":name, "polyline":polyline, "markers":markersAlongRoads, "distance":distanceRoads});	
	    	
	    	bAddLineAlongRoads=false;
	    	bEditLineAlongRoads=false;
		}
		else {
			//var name=$("#txtLineAlongRoads"+indiceEditLineAlongRoads).val();
			var name=document.getElementById("txtLineAlongRoads"+indiceEditLineAlongRoads).value;
			
			for (var indice=0;indice<linesAlongRoads.length;indice++) {
				var idLineAlongRoads=linesAlongRoads[indice]["id"];

				if (idLineAlongRoads==indiceEditLineAlongRoads) {
					//***modifica  Et1detofpa***
					linesAlongRoads[indice]={"id":idLineAlongRoads, "name":name, "polyline":polyline, "markers":markersAlongRoads, "distance":distanceRoads};
					
					break;
				}
			}
						
	    	bAddLineAlongRoads=false;
	    	bEditLineAlongRoads=false;
			indiceEditLineAlongRoads=-1;
		}
		
		GEvent.clearListeners(polyline, "click");
		GEvent.clearListeners(polyline, "mouseout");
		GEvent.clearListeners(polyline, "mouseover");
		polyline.setStrokeStyle({opacity: OPACITY_STANDARD});
		
		markersAlongRoads=[];
		//listLinesAlongRoads();
		listLines();

		this.animate();
		
		return true;
	};
		
	/**
	 * metodo privato per l'impostazione del listener da associare alla mappa durante l'operazione di disegno di linee 'along roads'
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 	
	var switchListenerGMapAlongRoads = function() {
		GEvent.removeListener(listenerGMapStandard);
		listenerGMapLinesAlongRoads=GEvent.addListener(map, "click", function(overlay, latlng, overlaylatlng) {
			if (overlay!=null) return;
			
			eval(nameMap).addLineAlongRoads(latlng);
		});				
	};
	
	/**
	 * metodo privato per inserire un elemento in una posizione qualsiasi di un array
	 * input:
	 * array --> variabile array in cui inserire un elemento
	 * posizione --> posizione in cui inserire un elemento
	 * elemento --> elemento da inserire
	 * output:
	 * array --> variabile array modificato con l'elemento inserito
	*/ 	
	var insertElementInArray = function(array, posizione, elemento) {
		var temp=[];
		
		for (var indice=0;indice<posizione;indice++) {
			temp.push(array[indice]);
		}
		
		temp.push(elemento);
		
		for (var indice=posizione;indice<array.length;indice++) {
			temp.push(array[indice]);
		}						
		
		return temp;	
	};
	
	/**
	 * metodo privato usato interamente per l'aggiunta di un punto intermedio in una linea 'along roads' 
	 * input:
	 * polyline --> oggetto GPolyline in cui inserire il punto
	 * latlngClick --> oggetto GLatLng contenente la coordinata cui inserire il punto
	 * output:
	 * non previsto
	*/ 
	var gestAddNewPointEditLineAlongRoads = function(polyline, latlngClick) {
		if (markersAlongRoads.length>=25) {
			alert(msgLinesAlongRoadsMax25Points);
			
			return;
		}
		
		var numVertici=polyline.getVertexCount();
		var listaCoordinate=[];
		
		for (var indice=0;indice<numVertici;indice++) {
			var latlngCurrent=polyline.getVertex(indice);
			
			listaCoordinate.push({"latlng":latlngCurrent, "index":0});
		}
		
		for (var indice=0;indice<markersAlongRoads.length;indice++) {
			var latlngCurrent=markersAlongRoads[indice].getLatLng();
	
			var indexVertex=getIndexVertexCoordinata(listaCoordinate, latlngCurrent);
			listaCoordinate[indexVertex]["index"]=1;
		}		
		
		var indexVertexClick=getIndexVertexCoordinata(listaCoordinate, latlngClick);
		var indexToInsertPoint=-1,indiceCont=0;
	
		for (var indice=0;indice<=indexVertexClick;indice++) {
			var indexCorrente=listaCoordinate[indice]["index"];
	
			if (indexCorrente>0) {
				indiceCont++;
			}
		}
		
		indexToInsertPoint=indiceCont;
		++indexMarkers;
		
		var name=POINT_PREFIX_NAME+indexMarkers;
		var icona = getIcon(urlIcon, widthIcon, heightIcon);
		var marker = new GMarker((latlngClick!=null && latlngClick!=undefined)?latlngClick:map.getBounds().getCenter(), {draggable: true, title: name, icon: icona!=null?icona:G_DEFAULT_ICON});
	
		map.addOverlay(marker);	
		
		markersAlongRoads=insertElementInArray(markersAlongRoads, indexToInsertPoint, marker); 
		
		GEvent.addListener(marker, "drag", function() {
			createLineAlongRoads();
		});
		
		/*GEvent.addListener(marker, "click", function() {
			eval(nameMap).endLineAlongRoads();
		});*/				
	};

	/**
	 * metodo pubblico da richiamare per ri-editare una linea 'along roads' precedentemente consolidata 
	 * lancia un evento editLineAlongRoads che puo' essere intercettato dalla pagina chiamante
	 * input:
	 * id --> identificato interno associato alla linea 'along roads'
	 * output:
	 * non previsto
	*/ 
	this.editLineAlongRoads = function(id) {
		if (bAddLine || indiceEditLine!=-1 || indiceEditPoint!=-1) 
			return;

		this.stopAnimateLinesAlongRoads();
		bEditLineAlongRoads=true;

		if (indiceEditLineAlongRoads==-1) {
			for (var indice=0;indice<linesAlongRoads.length;indice++) {
				var idLineAlongRoads=linesAlongRoads[indice]["id"];
				
				if (idLineAlongRoads==id) {
					markersAlongRoads=linesAlongRoads[indice]["markers"];
					var waypoints=[];
					
					for (var indiceMarker=0;indiceMarker<markersAlongRoads.length;indiceMarker++) {
						var markerAlongRoads=markersAlongRoads[indiceMarker];
						
						markerAlongRoads.enableDragging();
						markerAlongRoads.setImage(urlIcon);
				    	waypoints.push(markerAlongRoads.getLatLng());
				    	
				    	GEvent.clearListeners(markerAlongRoads, "drag");
				    	GEvent.clearListeners(markerAlongRoads, "click");
				    	
				    	GEvent.addListener(markerAlongRoads, "drag", function() {
							createLineAlongRoads();
						});
						/*GEvent.addListener(markerAlongRoads, "click", function() {
							eval(nameMap).endLineAlongRoads();
						});*/				    	
				    }
			    	
					var polyline=linesAlongRoads[indice]["polyline"];
					map.removeOverlay(polyline);
					
			    	directions.clear();
			    	directions.loadFromWaypoints(waypoints, {preserveViewport:true});
			    	
			    	if (waypoints.length>0) {
			    		map.setCenter(waypoints[0]);
			    	}
			    	
			    	GEvent.clearListeners(directions, "error");
			    	GEvent.clearListeners(directions, "load");
			    	
			    	GEvent.addListener(directions, "error", function() {
			    		alert(msgErrDirections);
			    	});			    	
			    	
					GEvent.addListener(directions, "load", function() {
				    	for (var indice=0;indice<markersAlongRoads.length;indice++) {
				    		//posiziona i marker 'alfabetici' al polo nord --> unico modo che ho trovato per 'nasconderli'
				    		var dir = directions.getMarker(indice);
				    		if(dir!=null){
				    			dir.setLatLng(new GLatLng(90,0)); 
				    		}
				    	}
				    	
						var polyline=directions.getPolyline();
						if(polyline==null){return;}
						GEvent.clearListeners(polyline, "click");
						GEvent.clearListeners(polyline, "mouseout");
						GEvent.clearListeners(polyline, "mouseover");
						
						GEvent.addListener(polyline, "click", function(latlngClick) {
							gestAddNewPointEditLineAlongRoads(polyline, latlngClick);
						});
						GEvent.addListener(polyline, "mouseout", function() {
							polyline.setStrokeStyle({opacity: OPACITY_STANDARD});
						});				
						GEvent.addListener(polyline, "mouseover", function() {
							polyline.setStrokeStyle({opacity: OPACITY_HIGHLIGHT});
						});
					});	
					
					switchListenerGMapAlongRoads();	
					indiceEditLineAlongRoads=idLineAlongRoads;
				}
			}
			
			//listLinesAlongRoads();
			listLines();
		}
		else {
			this.endLineAlongRoads();
		}
		
		GEvent.trigger(this, "editLineAlongRoads");
	};
	
	/**
	 * metodo pubblico da richiamare per eliminare una linea 'along roads' precedentemente consolidata 
	 * input:
	 * id --> identificato interno associato alla linea 'along roads'
	 * output:
	 * non previsto
	*/ 	
	this.removeLineAlongRoads = function(id) {
		if (bAddLine || bAddLineAlongRoads || bEditLineAlongRoads || indiceEditLine!=-1 || indiceEditPoint!=-1 || indiceEditLineAlongRoads!=-1)
			return;
		
		this.stopAnimateLinesAlongRoads();
		
		for (var indice=0;indice<linesAlongRoads.length;indice++) {
			var idLineAlongRoads=linesAlongRoads[indice]["id"];
			var polyline=linesAlongRoads[indice]["polyline"];
			var markersAlongRoads=linesAlongRoads[indice]["markers"];
			
			if (id==idLineAlongRoads) {
				for (var indice2=0;indice2<markersAlongRoads.length;indice2++) {
					var marker=markersAlongRoads[indice2];
					
					map.removeOverlay(marker);
				}
				
				map.removeOverlay(polyline);
				
				linesAlongRoads.splice(indice, 1);
				
				break;
			}
		}
		
		//listLinesAlongRoads();
		listLines();
	};
		
	/**
	 * metodo privato usato per calcolare la 'distanza' tra 2 coordinate
	 * input:
	 * coordinata1 --> oggetto GLatLng
	 * coordinata2 --> oggetto GLatLng
	 * output:
	 * variabile numerica contenente la distanza
	*/ 	
	var compareCoordinates = function(coordinata1, coordinata2) {
		return Math.abs(coordinata1.lat() - coordinata2.lat()) + Math.abs(coordinata1.lng() - coordinata2.lng());
	};
	
	/**
	 * metodo privato usato per individuare, data una lista di coordinate ed una coordinata X, l'indice relativo alla coordinata della lista
	 * piu' vicina alla coordinata X
	 * input:
	 * listaCoordinate --> array di coordinate con struttura "latlng"->oggetto GLatLng, "index"->variabile numerica
	 * coordinata --> oggetto GLatLng
	 * output:
	 * variabile numerica contenente l'indice
	*/ 	
	var getIndexVertexCoordinata = function(listaCoordinate, coordinata) {
		var compare=999999;
		var indexTrovato=0;
		
		for (var indice=0;indice<listaCoordinate.length;indice++) {
			var coordinataCorrente=listaCoordinate[indice]["latlng"];
			
			var compareCoordinate=compareCoordinates(coordinataCorrente, coordinata);
			
			if (compareCoordinate<compare) {
				compare=compareCoordinate;
				indexTrovato=indice;
			}
		}
		
		return indexTrovato;
	};
	
	/**
	 * metodo privato per renderizzare nella pagina la lista delle linee 'along roads' 
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	*/ 
	var listLinesAlongRoads = function() {
		if (!visDati) return;
		
		//impedisce cambiamenti nell'ordine della lista
		linesAlongRoads.sort(function(a, b) {return (a["name"].toLowerCase()<b["name"].toLowerCase())?-1:1;});
		
		var html="<div class=\"bottom\">";
		
		for (var indice=0;indice<linesAlongRoads.length;indice++) {
			html+="<div class=\"container\">";
			html+="<div class=\"image\" style=\"text-align:center\">";
			html+="</div>";
			
			var idLine=linesAlongRoads[indice]["id"];
			var title=linesAlongRoads[indice]["name"];
			var polyline=linesAlongRoads[indice]["polyline"];
			//***Modifica  Et1detofpa***
			var distance=linesAlongRoads[indice]["distance"];
			var coordinates=listCoordinatesLine(polyline);
			
			if (idLine==indiceEditLineAlongRoads) {
				html+="<div class=\"left\">";
				html+="<span id='LineAlongRoads"+idLine+"'>";
				html+="<input type='text' id='txtLineAlongRoads"+idLine+"' name='txtLineAlongRoads"+idLine+"' style='width:150px;height:16px' value=\""+title+"\"> ";
				//***Modifica  Et1detofpa***
				if (debug) html+="Distance: "+distance+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a id='linkEditLineAlongRoads'"+idLine+" href=javascript:"+nameMap+".editLineAlongRoads('"+idLine+"')>DONE</a>&nbsp;";
				}
				
				html+="</div>";
			}
			else {
				html+="<div class=\"left\">";
				html+="<span id='LineAlongRoads"+idLine+"'>"+title+" ";
				//***Modifica  Et1detofpa***
				if (debug) html+="Distance: "+distance+" - "+coordinates;
				html+="</span>";
				html+="</div>";
				
				html+="<div class=\"mid\">";
				
				if (linkEdit) {
					html+="<a href=javascript:"+nameMap+".editLineAlongRoads('"+idLine+"')>EDIT</a>&nbsp;";
				}
				
				html+="</div>";
			}
				
			html+="<div class=\"right\">";
			html+=" <a href=javascript:"+nameMap+".removeLineAlongRoads('"+idLine+"')>REMOVE</a><br/>";
			html+="</div>";
			html+="</div>"; //end container
		}	
		
		html+="</div>"; //end bottom
		
		document.getElementById(divLinesAlongRoads).innerHTML=html;
		
		if (indiceEditLineAlongRoads!=-1) {
			document.getElementById("txtLineAlongRoads"+indiceEditLineAlongRoads).focus();
			window.location.hash="linkEditLineAlongRoads"+indiceEditLineAlongRoads;
		}			
	};
	
	/**
	 * metodo pubblico per la generazione dinamica dei campi hidden della form per la successiva sottomissione verso il server
	 * da richiamare prima della submit nella pagina
	 * input:
	 * non previsto
	 * output:
	 * booleano --> impostato a true, indica la presenza di oggetti (punti o linee) nella mappa, false in caso contrario
	*/ 
	this.prepareDataToSubmit = function() {
		//consolida eventuali 'lines along roads' in corso di editing
		if (bAddLineAlongRoads || bEditLineAlongRoads) this.endLineAlongRoads();
		
		var html="";
		var dati=false;
		
		for (var indice=0;indice<markers.length;indice++) {
			var marker=markers[indice]["marker"];
			var title=markers[indice]["name"];
			var idPoint=markers[indice]["id"];
			var urlImg=markers[indice]["urlImg"];
			var idImg=!isEmpty(urlImg)?getIdImage(urlImg):"";
			var urlIcon=markers[indice]["urlIcon"];
			var comment=markers[indice]["comment"];
			var category=markers[indice]["category"];
			
			html+="<input type='hidden'	name='pointName("+idPoint+")' value=\""+title+"\" >";
			html+="<input type='hidden'	name='pointValue("+idPoint+")' value=\""+listCoordinatesPoint(marker)+"\" >";
			if (!isEmpty(urlImg)) html+="<input type='hidden' name='pointUrlImg("+idPoint+")' value=\""+urlImg+"\" >";
			if (!isEmpty(idImg)) html+="<input type='hidden' name='pointIdImg("+idPoint+")' value=\""+idImg+"\" >";
			if (!isEmpty(urlIcon)) html+="<input type='hidden'	name='pointUrlIcon("+idPoint+")' value=\""+urlIcon+"\" >";
			if (!isEmpty(comment)) html+="<input type='hidden'	name='pointComment("+idPoint+")' value=\""+comment+"\" >";
			if (!isEmpty(category)) html+="<input type='hidden'	name='pointCategory("+idPoint+")' value=\""+category+"\" >";
			
			dati=true;
		}	
		
		//$("#"+divPoints).html(html);
		document.getElementById(divPoints).innerHTML=html;
		if (debug) alert(html);
		
		html="";
		
		for (var indice=0;indice<polylines.length;indice++) {
			var polyline=polylines[indice]["polyline"];
			var title=polylines[indice]["name"];
			var idLine=polylines[indice]["id"];
			//***Modifica Et1detofpa***
			var lengthLine=0;
			try{
				lengthLine=polyline.getLength();
			}catch (e) {
				//lunghezza 0 nel caso si verifichi un errore
			}
			
			html+="<input type='hidden'	name='lineName("+idLine+")' value=\""+title+"\" >";
			//***Modifica Et1detofpa***
			html+="<input type='hidden'	name='lineLength("+idLine+")' value=\""+lengthLine+"\" >";
			html+="<input type='hidden'	name='lineValue("+idLine+")' value=\""+listCoordinatesLine(polyline)+"\" >";
			
			dati=true;
		}			
		
		//$("#"+divLines).html(html);
		document.getElementById(divLines).innerHTML=html;
		if (debug) alert(html);
		
		html="";
		
		var idPoint=0,idLine=0;
		
		for (var indice=0;indice<linesAlongRoads.length;indice++) {
			var idLineAlongRoads=linesAlongRoads[indice]["id"];
			var markersAlongRoads=linesAlongRoads[indice]["markers"];
			var polyline=linesAlongRoads[indice]["polyline"];
			//***Modifica Et1detofpa***
			var distance=linesAlongRoads[indice]["distance"];
			var name=linesAlongRoads[indice]["name"];
				
			for (var indiceMarker=0;indiceMarker<markersAlongRoads.length;indiceMarker++) {
				var markerAlongRoads=markersAlongRoads[indiceMarker];
				var title=markerAlongRoads.getTitle();
				
				html+="<input type='hidden'	name='pointNameAlongRoads("+idPoint+"AlongRoads)' value=\""+title+"\" >";
				html+="<input type='hidden'	name='pointValueAlongRoads("+idPoint+"AlongRoads)' value=\""+listCoordinatesPoint(markerAlongRoads)+"\" >";
				
				if (indiceMarker==0) html+="<input type='hidden' name='pointUrlIconAlongRoads("+idPoint+"AlongRoads)' value=\""+urlIconStart+"\" >";
				if (indiceMarker==markersAlongRoads.length-1) html+="<input type='hidden' name='pointUrlIconAlongRoads("+idPoint+"AlongRoads)' value=\""+urlIconEnd+"\" >";
		    				  
				idPoint++;
		    }
				
			html+="<input type='hidden'	name='lineNameAlongRoads("+idLineAlongRoads+"AlongRoads)' value=\""+name+"\" >";
			//***Modifica Et1detofpa
			html+="<input type='hidden'	name='lineDistanceAlongRoads("+idLineAlongRoads+"AlongRoads)' value=\""+distance+"\" >";			
			html+="<input type='hidden'	name='lineValueAlongRoads("+idLineAlongRoads+"AlongRoads)' value=\""+listCoordinatesLine(polyline)+"\" >";
			
			dati=true;
		}		
		
		//$("#"+divLinesAlongRoads).html(html);
		document.getElementById(divLinesAlongRoads).innerHTML=html;
		if (debug) alert(html);		
		
		return dati;
	};
	
	/**
	 * metodo privato che indica se la mappa ha almeno un punto con immagini
	 * input:
	 * non previsto
	 * output:
	 * boolean --> true, ha almeno un punto con immagini, false in caso contrario
	 */
	var hasPointsWithImage = function() {
		for (var indice=0;indice<currentMarkers.length;indice++) {
			var currentMarker=currentMarkers[indice];
			var currentData=getExtendedData(currentMarker.getTitle(), currentExtendedData);
			var currentUrlImg=currentData["urlImage"];
			var currentIdImg=getIdImage(currentUrlImg);
		
			if (!isEmpty(currentUrlImg)) return true;
		}
		
		return false;
	};
	/**
	 * costruttore pubblico per l'inizializzazione dell'oggeto Control che contiene l'immagine della gallery
	 * input:
	 * urlImage --> url dell'immagine da visualizzare
	 * output:
	 * non previsto
	 */
	this.ImageControl = function(urlImage) {
		this.urlImage=urlImage;
    };
    
	/**
	 * costruttore pubblico per l'inizializzazione dell'oggeto Control che contiene la toolbar con i pulsanti (pause, play, stop)
	 * per la gestione della gallery
	 * input:
	 * non previsto
	 * output:
	 * non previsto
	 */    
    this.ToolbarControl = function() {};
    
	this.ImageControl.prototype = new GControl();
	this.ToolbarControl.prototype = new GControl();

	/**
	 * metodo initialize per l'inizializzazione dell'oggetto Control che contiene l'immagine della gallery
	 * e' chiamato automaticamente dalle api di google
	 * crea un div con un immagine
	 */
    this.ImageControl.prototype.initialize = function() {
    	var container = document.createElement("div");
    	var height=$(map.getContainer()).css("height");
    	var heightNum=0;
    	if (!isEmpty(height)) heightNum=parseInt(height, 10);

    	//container.innerHTML="<div style='margin-top:40px;margin-right:20px'><img style='opacity:0;filter: alpha(opacity=0);' id='imageMap' src='"+this.urlImage+"' /></div>";
    	var html="";
    	html+="<div id=\"img_poi_onmap\" style='opacity:0;filter: alpha(opacity=0);'>";
    	html+="<p class=\"img_landscape\">";
    	html+="<img style='margin-top:5px;opacity:0;filter: alpha(opacity=0);' id='imageMap' src='"+this.urlImage+"' />";
    	html+="</p>";
    	html+="</div>";
    	container.innerHTML=html;

    	map.getContainer().appendChild(container);
      
    	return container;
    };
    
    /**
     * metodo pubblico per nascondere o visualizzare la toolbar
     * input:
     * val --> 0 -> nasconde la toolbar, 1 -> la visualizza
     * output:
     * non previsto
     */
    this.setOpacityToolbar=function(val) {
    	// se la image gallery e' in esecuzione esce immediatamente, la toolbar rimane visibile comunque
    	if (val==0 && statoImageGallery!=0) return;

    	if (val==1) {
    		if ($("#bg_player_map").css("display")=="none") $("#bg_player_map").css("display","block");
    	}
    	else {
    		$("#bg_player_map").css("display","none");
    	}
    };
    
	/**
	 * metodo initialize per l'inizializzazione dell'oggetto Control che contiene la toolbar della gallery
	 * e' chiamato automaticamente dalle api di google
	 * crea un div con 2 immagini, una per pause/play ed una per lo stop
	 */    
    this.ToolbarControl.prototype.initialize = function() {
    	var container = document.createElement("div");
    	var html="";
    	/*html+="<div style='margin-left:70px;margin-bottom:-5px'>";
    	html+="<img id='imagePausePlay' style='cursor: pointer' src='"+imgPause+"' onclick='"+nameMap+".pausePlayImageGallery()'/>";
    	html+="<img style='margin-left:5px;cursor: pointer' src='"+imgStop+"' onclick='"+nameMap+".stopImageGallery()'/>";
    	html+="</div>";*/
    	html+="<div style='margin-bottom:"+marginBottom+"px' id=\"bg_player_map\" onmouseover='"+nameMap+".setOpacityToolbar(1)' onmouseout='"+nameMap+".setOpacityToolbar(0)'>";
    	html+="<p class=\"icon_player\" style='text-align:right;margin-right:4px'>";
    	html+="<img title='"+((currentAnimation==1)?TITLE_TRIP_ANIMATION:TITLE_PHOTO_ANIMATION)+"' id='imageChangeAnimation' style='cursor: pointer;' border=\"none\" src='"+((currentAnimation==1)?imgChange1:imgChange2)+"' onclick='"+nameMap+".changeAnimation()'\" />";
    	html+="&nbsp;&nbsp;";
    	html+="<img id='imagePausePlay' style='cursor: pointer;' border=\"none\" src='"+imgPause+"' onmouseover='"+nameMap+".setOpacityToolbar(1)' onclick='"+nameMap+".pausePlayImageGallery()'\" />";
    	html+="&nbsp;&nbsp;";
    	html+="<img style='cursor: pointer;' border=\"none\" src='"+imgStop+"' onclick='"+nameMap+".stopImageGallery()'\" />";
    	html+="</p>";  
    	html+="</div>"; //end bg_player_map
    	
    	container.innerHTML=html;
    	map.getContainer().appendChild(container);
      
    	return container;
    };    

	/**
	 * metodo getDefaultPosition per l'impostazione della posizione dell'oggetto Control che contiene l'immagine della gallery
	 * e' chiamato automaticamente dalle api di google
	 * posiziona l'oggetto in alto a destra
	 */  
    this.ImageControl.prototype.getDefaultPosition = function() {
    	return new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(8, 30));
    };
    
	/**
	 * metodo getDefaultPosition per l'impostazione della posizione dell'oggetto Control che contiene la toolbar della gallery
	 * e' chiamato automaticamente dalle api di google
	 * posiziona l'oggetto in basso
	 */     
    this.ToolbarControl.prototype.getDefaultPosition = function() {
    	return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(0, 0));
    };    
    
    
    /**
     * metodo pubblico per la modifica dinamica del livello di opacita' dell'immagine (effetto dissolvenza)
     * agisce in doppia modalita' a seconda della variabile versoImageControl (opacity da 0 a 1 oppure da 1 a 0)
     * imposta il timer timerImage che richiama se stesso per la visualizzazione del successivo frame con il nuovo livello di opacity
	 * input:
	 * non previsto
	 * output:
	 * non previsto
     */
    this.setOpacity=function() {
    	// se l'effetto di dissolvenza e' terminato per il punto corrente, esce immediatamente
    	if (endThreadPointCurrent) return;
    	
    	// a seconda della direzione dell'effetto dissolvenza, incrementa o decrementa il contatore che identifica il livello di opacity
    	if (!versoImageControl) opacityImageControl+=0.05; else opacityImageControl-=0.05;
    	
    	// imposta il livello di opacity corrente nell'immagine corrente della gallery
    	$("#imageMap").css("opacity", opacityImageControl);
    	$("#img_poi_onmap").css("opacity", opacityImageControl);
    	
    	if (!versoImageControl) {
	    	if (opacityImageControl>1) {
	    		// se l'immagine ha raggiunto il massimo livello di opacity (1) imposta dei boolani per invertire l'effetto dissolvenza
	    		versoImageControl=true;
	    		bloccoImageControl=true;
	    	}
    	}
    	else {
	    	if (opacityImageControl<=0) {
	    		// se l'immagine ha raggiunto il minimo livello di opacity (0) imposta delle variabili per terminare l'effetto dissolvenza
	    		opacityImageControl=0;
	    		versoImageControl=false;
	    		
	    		// imposta un booleano che indica la conclusione dell'effetto di dissolvenza per il punto corrente
	    		endThreadPointCurrent=true;
	    		
	    		$("#imageMap").css("opacity", 0);
	    		$("#img_poi_onmap").css("opacity", 0);
	    		
	    		return;
	    	}    	
	    	
	    	bloccoImageControl=false;
    	}
    	
    	// riattiva il timer per proseguire l'effetto dissolvenza con il frame successivo
    	timerImage=setTimeout(nameMap+".setOpacity()", !bloccoImageControl?50:3000);
    };
    
    /**
     * metodo pubblico che gestisce il cambio di stato dell'image gallery a pause oppure play a partire da stop, pause o play
     * gli stati possibili sono i seguenti:
     * 0 --> stop
     * 1 --> in esecuzione
     * 2 --> in pausa
     * currentAnimation:
     * 1 --> animazione gallery
     * 2 --> animazione lines
	 * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.pausePlayImageGallery = function() {
    	// se l'animazione corrente e' relativa alle lines, richiama il metodo corrispondente di pause/play
    	if (currentAnimation==2) {
    		this.pausePlayAnimateLinesAlongRoads();
    		
    		return;
    	}
    	
    	if (statoImageGallery==1) {
    		// se e' in esecuzione, va in pausa
    		statoImageGallery=2;
    		
    		// elimina i timer
    		if (timerImage!=null) clearTimeout(timerImage);
    		if (timerGallery!=null) clearTimeout(timerGallery);
    		
    		// imposta l'immaginetta di play
    		$("#imagePausePlay").attr("src", imgPlay);
    	}
    	else if (statoImageGallery==2) {
    		// se e' in pausa, va in esecuzione
    		statoImageGallery=1;
    		
    		// imposta nuovamente i 2 timer
    		timerImage=setTimeout(nameMap+".setOpacity()", 500);
    		timerGallery=setTimeout(nameMap+".imageGallery()", 6000);
    		
    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    	}
    	else if (statoImageGallery==0) {
    		// se e' in stop, va in esecuzione
    		statoImageGallery=1;
    		
    		// imposta il timer gallery
    		timerGallery=setTimeout(nameMap+".imageGallery()", 2000);
    		
    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    	}
    };
    
    /**
     * metodo pubblico che gestisce il cambio di stato dell'image gallery a stop a partire da qualsiasi stato
     * gli stati possibili sono i seguenti:
     * 0 --> stop
     * 1 --> in esecuzione
     * 2 --> in pausa
	 * input:
	 * non previsto
	 * output:
	 * non previsto 
     */    
    this.stopImageGallery = function() {
    	// se l'animazione corrente e' relativa alle lines, richiama il metodo corrispondente di stop
    	if (currentAnimation==2) {
    		this.stopAnimateLinesAlongRoads();
    		
    		return;
    	}
    	
    	// se i timer sono attivati, vengono disattivati
		if (timerImage!=null) clearTimeout(timerImage);
		if (timerGallery!=null) clearTimeout(timerGallery);    	
		
		// elimina l'immagine della gallery eventualmente visualizzata
		if (imageControl!=null) map.removeControl(imageControl);
		imageControl=null;
		
		// elimina l'eventuale evidenziazione del marker
		this.highlightPointWithImage("");
		
		// riposiziona la mappa al centro
		map.setCenter(centerImageControl);
		
		// imposta l'immaginetta di play
		$("#imagePausePlay").attr("src", imgPlay);
		
		// riazzera le veriabili interne dell'image gallery
		indiceImageControl=0;
		opacityImageControl=0;
	    versoImageControl=false;
	    bloccoImageControl=false;

	    // imposta lo stato a stop
		statoImageGallery=0;
		
		// nasconde la toolbar
		this.setOpacityToolbar(0);
    };
    
    /**
     * metodo pubblico main per la gestione delle animazioni
     * se il trip ha associate immagini, fa partire l'animazione image gallery
     * altrimenti fa partire l'animazione delle lines
     * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.animate = function() {
    	// se non e' presente il controllo che visualizza la toolbar, lo instanzia e lo attiva
    	if (toolbarControl==null) {
    		toolbarControl=new this.ToolbarControl();
    		map.addControl(toolbarControl);		
    	}       	
    	
    	statoImageGallery=1;
    	
    	// se il trip ha associate immagini, attiva l'animazione image gallery
    	if (hasPointsWithImage()) {
    		this.setOpacityToolbar(1);
    		
    		timerGallery=setTimeout(nameMap+".imageGallery()", 6000);
    	}
    	// altrimenti, attiva l'animazione delle lines
    	else {
    		$("#imageChangeAnimation").attr("src", imgChange2);
    		$("#imageChangeAnimation").attr("title", TITLE_PHOTO_ANIMATION);
    		this.setOpacityToolbar(1);
    		
    		// fa partire il timer dell'animazione
    		timerAnimateLinesAlongRoads=setTimeout(nameMap+".startAnimateLinesAlongRoads()", 100);
    	}
    		
    };
    
    /**
     * metodo pubblico main da richiamare per attivare l'image gallery
     * l'image gallery si basa su 2 timer:
     * timerGallery --> a intervalli regolari cambia il posizionamento del marker nella mappa e visualizza l'immagine collegata della gallery
     * timerImage --> a intervalli regolari (piu' frequenti di timerGallery) attiva l'effetto di dissolvenza dell'immagine 
     * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.imageGallery = function() {
    	currentAnimation=1;
    	
    	// se non e' presente il controllo che visualizza l'immagine, lo instanzia e lo attiva
    	if (imageControl==null) {
    		// se la mappa non ha punti con immagini, esce immediatamente
    		if (!hasPointsWithImage()) return;
    		
    		imageControl=new this.ImageControl(currentUrlImg);
    		map.addControl(imageControl);	
    		
    		// l'image gallery parte automaticamente in esecuzione
    		statoImageGallery=1;
    		
        	// riordina i marker in base alla sequenza di inserimento delle immagini (ordina per id immagine)
        	currentMarkers.sort(function(a, b) {
    			var currentDataA=getExtendedData(a.getTitle(), currentExtendedData);
    			var currentUrlImgA=currentDataA["urlImage"];
    			var currentIdImgA=getIdImage(currentUrlImgA);

    			var currentDataB=getExtendedData(b.getTitle(), currentExtendedData);
    			var currentUrlImgB=currentDataB["urlImage"];
    			var currentIdImgB=getIdImage(currentUrlImgB);			

    			return (currentIdImgA<currentIdImgB?-1:1);
        	});    		
    	}
    	
    	// se non e' presente il controllo che visualizza la toolbar, lo instanzia e lo attiva
    	if (toolbarControl==null) {
    		toolbarControl=new this.ToolbarControl();
    		map.addControl(toolbarControl);		
    	}    	

    	// se sono stati elaborati tutti i marker, mette in stop la image gallery ed esce
		if (indiceImageControl>=currentMarkers.length) {
			this.stopImageGallery();
			
			return;
		}    	
		
		// conserva la posizione di centro della mappa per il successivo ripristino allo stop dell'image gallery
    	if (centerImageControl==null) centerImageControl=map.getBounds().getCenter();
    	
    	var trovato=false;
    	
    	// per ogni marker della mappa
		for (var indice=indiceImageControl;indice<currentMarkers.length;indice++) {
			var currentMarker=currentMarkers[indice];
			var currentData=getExtendedData(currentMarker.getTitle(), currentExtendedData);
			var currentUrlImg=currentData["urlImage"];
			var currentIdImg=getIdImage(currentUrlImg);
			
			// se il marker ha associata un'immagine
			if (!isEmpty(currentUrlImg)) {
				trovato=true;
				// evidenzia il punto associato all'immagine
				this.highlightPointWithImage(currentIdImg);
				
				// visualizza l'immagine relativa al punto corrente
				$("#imageMap").attr("src", currentUrlImg);
				
				// attiva il timer per l'effetto di dissolvenza sull'immagine corrente
				timerImage=setTimeout(nameMap+".setOpacity()", 100);

				// tiene traccia del punto corrente, al prossimo riciclo ripartira' da qui
				indiceImageControl=indice+1;
				
				// imposta un booleano per far partire l'effetto di dissolvenza sul nuovo punto trovato 
				endThreadPointCurrent=false;
				
				// esce dal loop
				break;
			}
		}
		
		// se sono stati elaborati tutti i marker, mette in stop la image gallery ed esce
		if (!trovato) {
			this.stopImageGallery();
			
			return;
		}   
		
		// riattiva il timer per la visualizzazione dell'immagine successiva
		timerGallery=setTimeout(nameMap+".imageGallery()", 6000);
    };
    
    /**
     * metodo pubblico per lo switch da un tipo di animazione all'altra
     * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.changeAnimation=function() {
    	// se l'animazione corrente e' l'image gallery
    	if (currentAnimation==1) {
    		// mette in stop l'animazione image gallery
    		this.stopImageGallery();
    		
    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    		
    		// cambia l'icona di switch
    		$("#imageChangeAnimation").attr("src", imgChange2);
    		$("#imageChangeAnimation").attr("title", TITLE_PHOTO_ANIMATION);
    		
    		// attiva la toolbar
    		this.setOpacityToolbar(1);
    		
    		// fa partire l'animazione delle lines
    		this.startAnimateLinesAlongRoads();
    	}
    	// altrimenti, se l'animazione corrente e' quella delle lines
    	else {
    		// se non vi sono punti con immagine, esce senza far partire l'animazione
    		if (!hasPointsWithImage()) return;
    		
    		// mette in stop l'animazione delle lines
    		this.stopAnimateLinesAlongRoads();

    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    		
    		// cambia l'icona di switch
    		$("#imageChangeAnimation").attr("src", imgChange1);
    		$("#imageChangeAnimation").attr("title", TITLE_TRIP_ANIMATION);
    		
    		// attiva la toolbar
    		this.setOpacityToolbar(1);
    		
    		// fa partire l'animazione image gallery
    		this.imageGallery();
    	}
    };
    
    /**
     * metodo per lo stop dell'animazione delle lines
     * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.stopAnimateLinesAlongRoads=function() {
    	// se e' presente il marker animate, lo rimuove
     	if (markerAnimate!=null) map.removeOverlay(markerAnimate);
		
     	// inizializza le variabili di stato dell'animazione
		markerAnimate=null;
		indiceAnimate=0;
		indiceLineAnimate=0;    
		
		// imposta l'immaginetta di play
		$("#imagePausePlay").attr("src", imgPlay);
		
		// se e' attivo il timer dell'animazione, lo rimuove
		if (timerAnimateLinesAlongRoads!=null) clearTimeout(timerAnimateLinesAlongRoads);
		
		// mette l'animazione in stato stop
		statoImageGallery=0;
		
		// inizializza l'elenco dei poligoni su cui attivare l'animazione
		polylinesTotal=[];
		
		// nasconde la toolbar
		this.setOpacityToolbar(0);		
    };
    
    /**
     * metodo pubblico che gestisce il cambio di stato dell'animazione delle lines a pause oppure play a partire da stop, pause o play
     * gli stati possibili sono i seguenti:
     * 0 --> stop
     * 1 --> in esecuzione
     * 2 --> in pausa
     * currentAnimation:
     * 1 --> animazione gallery
     * 2 --> animazione lines
	 * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.pausePlayAnimateLinesAlongRoads=function() {
       	if (statoImageGallery==1) {
    		// se e' in esecuzione, va in pausa
    		statoImageGallery=2;
    		
    		// elimina il timer
    		if (timerAnimateLinesAlongRoads!=null) clearTimeout(timerAnimateLinesAlongRoads);
    		
    		// imposta l'immaginetta di play
    		$("#imagePausePlay").attr("src", imgPlay);
    		
    		// lancia un evento di click sul marker animate
    		GEvent.trigger(markerAnimate, "click", markerAnimate.getLatLng());
    	}
    	else if (statoImageGallery==2) {
    		// se e' in pausa, va in esecuzione
    		statoImageGallery=1;
    		
    		// imposta nuovamente i 2 timer
    		timerAnimateLinesAlongRoads=setTimeout(nameMap+".startAnimateLinesAlongRoads()", 100);
    		
    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    	}
    	else if (statoImageGallery==0) {
    		// se e' in stop, va in esecuzione
    		statoImageGallery=1;
    		
    		// imposta il timer gallery
    		timerAnimateLinesAlongRoads=setTimeout(nameMap+".startAnimateLinesAlongRoads()", 100);
    		
    		// imposta l'immaginetta di pausa
    		$("#imagePausePlay").attr("src", imgPause);
    	}
    };    	
    
    /**
     * metodo pubblico per avviare l'animazione delle lines
	 * input:
	 * non previsto
	 * output:
	 * non previsto 
     */
    this.startAnimateLinesAlongRoads=function() {
    	// imposta l'animazione in stato di esecuzione
    	statoImageGallery=1;
    	
    	// imposta come animazione corrente quella delle lines
    	currentAnimation=2;
    	
    	// se e' la primo run, inserisce tutti i polyline del trip nella struttura polylinesTotal
    	if (polylinesTotal.length==0) {
    		for (var indice=0;indice<linesAlongRoads.length;indice++) {
    			var polyline=linesAlongRoads[indice];
    			polylinesTotal.push(polyline);
    		}
    		for (var indice=0;indice<polylines.length;indice++) {
    			var polyline=polylines[indice];
    			polylinesTotal.push(polyline);
    		}    		
    	}

    	// se il trip non ha linee, l'animazione non puo' essere avviata e va in stop
    	if (polylinesTotal.length==0) {				
    		this.stopAnimateLinesAlongRoads();
				
			return;
    	}
    	
    	// imposta il polyline corrente con il relativo numero di vertici (punti)
    	var polyline=polylinesTotal[indiceLineAnimate]["polyline"];	
		var numVertici=polyline.getVertexCount()-1;
		
		// se non ci sono altri punti da elaborare nel polyline (fine della linea corrente)
		if (indiceAnimate>numVertici) {
			// reinizializza l'indice dei punti
			indiceAnimate=0;
			
			// si posiziona sul polyline successivo
			indiceLineAnimate++;
			
			var numLines=polylinesTotal.length-1;
			
			// se sono terminati i polyline del trip, l'animazione termina e va in stop
			if (indiceLineAnimate>numLines) {
				this.stopAnimateLinesAlongRoads();
				
				return;
			}
			
			// riattiva il timer per l'elaborazione del polyline succesivo
			timerAnimateLinesAlongRoads=setTimeout(nameMap+".startAnimateLinesAlongRoads()", 100);
			return;
		}
		
		// preleva le coordinate del punto corrente
		var latlng=polyline.getVertex(indiceAnimate);
		
		// al primo run, inizia il marker animate usato per l'animazione
		if (markerAnimate==null) {
			// il marker animate ha l'icona definita in urlIconAnimate, non puo' essere spostato dall'utente e non ha titolo
			iconAnimate=getIcon(urlIconAnimate);
			markerAnimate = new GMarker(latlng, {draggable: false, title: "", icon:iconAnimate});
			
			// aggiunge il listener per il click
			GEvent.addListener(markerAnimate, "click", function(latlng) {
				var coordinate=latlng.lat().toString()+","+latlng.lng().toString();
				
				// richiama il metodo di geolocalizzazione per prelevare l'indirizzo associato alla coordinata corrente
				geocoding(coordinate);
				
				// rimane in attesa che il servizio di geolocalizzazione abbia completato l'elaborazione
				GEvent.addListener(this, "geocoder", function() {
					var lat=latlng.lat().toString();
					var lng=latlng.lng().toString();
					
					// richiama il metodo per ricavare il dato di altitudine a partire dalla coordinata corrente
					getAltitudeWs(lat, lng);	
					
					// rimane in attesa che il metodo di gestione dell'altitudine abbia completato l'elaborazione
					GEvent.addListener(this, "getAltitude["+lat+","+lng+"]", function() {
						// rimuove il listener sul metodo di gestione dell'altitudine (necessario per la gestione del timeout)
						GEvent.clearListeners(this, "getAltitude["+lat+","+lng+"]");
						
						// costruisce la porzione di html da visualizzare nel balloon
						var html="";
						html+="<div style=\"width:300px\" class=\"title_box_poi_balloon\">";
						html+="Latitude:<span style=\"font-weight:normal\">"+eval(nameMap+".convCoordinateToDegrees(latlng.lat().toString())")+"</span><br/>";
						html+="Longitude:<span style=\"font-weight:normal\">"+eval(nameMap+".convCoordinateToDegrees(latlng.lng().toString())")+"</span><br/>";
						html+="Altitude:<span style=\"font-weight:normal\">"+((altitudeFond=="")?"n/a":altitudeFond+"m")+"</span><br/>";
						html+="Address:<span style=\"font-weight:normal\">"+addressFound+"</span>";
						html+="</div><br/>";
						
						// apre il balloon
						this.openInfoWindowHtml(html);
					});
				});
			});
			
			// inserisce il marker animate nella mappa
			map.addOverlay(markerAnimate);
		}

		// sposta il marker nella coordinata corrente 
		markerAnimate.setLatLng(latlng);
		
		// chiude l'eventuale balloon aperto
		markerAnimate.closeInfoWindow(); 
		
		// calcola lo step ovvero il numero di punti da saltare nel polyline al run successivo
		var step=parseInt(polyline.getVertexCount()/100, 10);
		
		// se lo step e' pari a zero, lo imposta a 1 per far avanzare comunque l'animazione
		if (step==0) step=1;
		
		// incrementa l'indice dei punti di un valore pari allo step calcolato
		indiceAnimate+=step;		
		
		// riattiva il timer dell'animazione per il run successivo
		timerAnimateLinesAlongRoads=setTimeout(nameMap+".startAnimateLinesAlongRoads()", 100);
    };
    
    /**
     * metodo privato che richiama il servizio di geolocalizzazione di google per individuare l'indirizzo collegato alla coordinata corrente
     * input:
     * address --> coordinata per la quale ricavare l'indirizzo
     * output:
     * impostazione della variabile globale addressFound
     */
    var geocoding=function(address) {
    	// richiama il servizio di geolocalizzazione
		geocoder.getLocations(address, function(response) {
			if (!response || response.Status.code != 200) {
				// se l'esito e' negativo, imposta l'indirizzo a stringa vuota
				addressFound="";
				
				// lancia un evento per indicare la conclusione dell'elaborazione
				GEvent.trigger(markerAnimate, "geocoder");
		    	return;
		    } else {
		    	// se l'esito e' positivo, imposta l'indirizzo con il primo dell'elenco restituito da google
			    var text=response.Placemark[0].address;
			    
			    addressFound=text;
			    
			    // lancia un evento per indicare la conclusione dell'elaborazione
			    GEvent.trigger(markerAnimate, "geocoder");
			    return;		  
		    }
		});
	};  

	/**
	 * metodo pubblico per prelevare l'altitudine dai parametri restitutii dal servizio
	 * input:
	 * parameters --> dati restituiti dal servizio
	 * output:
	 * impostazione della variabile globale altitudeFound
	 */
    this.getAltitude=function(parameters) {
    	altitudeFond=(parameters==null||parameters==undefined)?"":parameters["gtopo30"];
    };
    
    /**
     * metodo pubblico per forzare la conclusione del servizio per prelevare l'altitudine
     * input:
     * lat --> latitudine corrente
     * lng --> longitudine corrente
     * output:
     * impostazione della variabile globale altitudeFound
     */
    this.abortAltitudeWs=function(lat, lng) {
    	// imposta un booleano per indicare l'abort
    	isAbortedWsAltitude=true;
    	
    	// l'altitudine trovata e' impostata a stringa vuota
    	altitudeFond="";
    	
    	// lancia un evento per indicare il completamento dell'elaborazione
    	GEvent.trigger(markerAnimate, "getAltitude["+lat+","+lng+"]");
    };
    
    /**
     * metodo privato che richiama il servizio di individuazione dell'altitudine relativa alla coordinata corrente
     * lat --> latitudine corrente
     * lng --> longitudine corrente
     * output:
     * non previsto 
     */
    var getAltitudeWs=function(lat, lng) {
    	isAbortedWsAltitude=false;
    	
    	// abilita un timer per il metodo di abort: se entro un secondo il servizio non risponde, l'elaborazione verra' forzatamente completata 
    	timerAltitudeWs=setTimeout(nameMap+".abortAltitudeWs("+lat+","+lng+")", 1000);
    	
    	// richiama il servizio per ricavare l'altitudine
    	$.getScript(urlWsAltitude+"?lat="+lat+"&lng="+lng+"&callback="+nameMap+".getAltitude", function(data) {
    		if (!isAbortedWsAltitude) {
    			// se il servizio ha risposto in tempo utile, disattiva il timer per la gestione dell'abort
    			clearTimeout(timerAltitudeWs);
    			
    			// lancia un evento per indicare il completamento dell'elaborazione
    			GEvent.trigger(markerAnimate, "getAltitude["+lat+","+lng+"]");
    		}
    	});
    };
    
    /**
     * metodo pubblico per la conversione di una coordinata (latitudine o longitudine) nel formato grd°pp'ss"
     * input:
     * coordinata --> numero con decimali da convertire
     * output:
     * stringa contenente la coordinata convertita
     */
    this.convCoordinateToDegrees=function(coordinata) {
    	if (isEmpty(coordinata)) return "";
    	
    	var appo=coordinata;
    	
    	try {
	    	var negativo=false;
	    	
	    	if (coordinata<0) {
	    		negativo=true;
	    		coordinata*=-1;
	    	}
	    	
	    	var gradi=Math.floor(coordinata);
	    	var residuo=coordinata-gradi;
	    	var primi=Math.floor(residuo*60);
	    	var residuo2=residuo*60-primi;
	    	var secondi=Math.round(residuo2*60*100)/100;
	    	gradi=(negativo)?gradi*=-1:gradi;
	    	if (primi<10) primiString="0"+primi; else primiString=primi;
	    	if (secondi<10) secondiString="0"+secondi; else secondiString=secondi;
	    	
	    	return gradi+"&deg;"+primiString+"'"+secondiString+"\"";
	    	//return gradi+"&deg;"+primiString+"'"+secondiString+"\""+"("+appo+")";
    	}
    	catch (e) {
    		// se si verifica un errore di conversione, restituisce la coordinata iniziale
    		return appo;
    	}
    };
    
    
}

