/*
  Utility functions 
 */
/** 
 * Function to  warp an object API calls into embed object, without overriding 
 * exisiting function. Example : for a class A that have a method 'setup()', B 
 * has a memeber 'a' that is an instance of 'A'. If you call 
 * objectWrapper(A.prototype, "B.prototype", "a"); all calls of 'setup()' on 
 * a 'B' instance will be evualated just as if called it through B.a.setup(). 
 * So it makes B.setup() behave exactly as B.a.setup().
 * 
 * @param {Prototype} prototypeSource prototype of the embeded object
 * @param {String} prototypeDest string that will return the destination prototype object if evaluate by 'eval()' (just quote the code to access the prototype)
 * @param {String} embeded name of the object emebeded in prototypeDest class
 */
function objectWrapper(prototypeSource, prototypeDest, embeded) {
	for (var i in prototypeSource) {
		try {
			if (typeof(prototypeSource[i]) == "function" && eval(prototypeDest + "." + i) == undefined){
				// 'i' must be greater than 2, because in gmap objects, short properties are hidden ones
				if (i.length > 2) {
					// generating parameters string
					paramsCount = prototypeSource[i].length;
					params = "";
					paramString = "p";
					if (paramsCount > 0) {
						params = paramString;
					}
					for (p = 1; p < paramsCount; p++) {
						paramString += "p";
						params += ", " + paramString;
					}
        
					eval(prototypeDest + "." + i + " = function ("+ params +") { return " + embeded + "." + i +"("+params+");}");
				}
			}
		} catch(e) {
		// fails silently (goosh)
		}
	}
}


/*
 * Cross browser method to get event coordinates, absolute if 2nd argument is
 * undefined, else relative to given element
 *
 * @param {DOMEvent} e an event, more likely a mouse event
 * @param {DOMEvent} dom_elem an optional DOMElement
 **/

function getMousePosFor(e, dom_elem) {
	var eposx = 0;
	var eposy = 0;
    
	while (dom_elem != null) {
		eposx += dom_elem.offsetLeft;
		eposy += dom_elem.offsetTop;
		dom_elem = dom_elem.offsetParent;
	}
     
     
	var posx = 0;
	var posy = 0;
	if (!e) e = window.event;
	if (e.pageX || e.pageY) 	{
		posx = e.pageX;
		posy = e.pageY;
	}
	else if (e.clientX || e.clientY) 	{
		posx = e.clientX + document.body.scrollLeft
		+ document.documentElement.scrollLeft;
		posy = e.clientY + document.body.scrollTop
		+ document.documentElement.scrollTop;
	}
	return {
		x: posx - eposx,
		y: posy - eposy
	};
}

/**
 * Makes copies of all the properties of an object to another, so that the dest
 * object has all the behaviours of source object (beware of overriding) plus its own methods and members
 *
 * @param {Object} objectSource Source Object
 * @param {Object} objectDest Destination Object
 */
function copyProperties(objectSource, objectDest) {
	for (var i in objectSource) {
		objectDest[i] = objectSource[i];
	}
}

/**
 * Generate a sequence of QRST that match the tile requested by the parameters. The parameters are in the google maps tile numbering style, that differs from the geogarage tile numbering.
 * 
 * @param {Number} x tile coordinate (unit is the tile)
 * @param {Number} y tile coordinate (unit is the tile)
 * @param {Number} zoom zoom level
 * @returns The qrst that identifies that tile
 * @type String
 */

function genQrst(x, y, zoom) {
	var inc = 1 << zoom-1;
	var qrstString = "t";
	for (var i = zoom; i > 0; i--) {
		if ((x & inc) && (y & inc)) {
			qrstString += "s";
		} else if  ((x & inc) && ! (y & inc)) {
			qrstString += "r";
		} else if  (!(x & inc) && ! (y & inc)) {
			qrstString += "q";
		} else if  (!(x & inc) && (y & inc)) {
			qrstString += "t";
		}
		inc = inc >> 1;
	}
	return qrstString;
}

/**
 * Generate a path where a file corresponding to the string should located, in GeoGarage quadtree storage structure. 
 * 
 * @param {String} qrstString the qrst string corresponding to a file.
 * @returns The corresponding directory path
 * @type String
 */
function genPathFromQrst(qrstString) {
	var directoryElem = new Array();
	var strLength = qrstString.length;
	for (var i = 0; (i + 1) * 6 <= strLength; i++) {
		directoryElem.push(qrstString.substr(i * 6, 6));
	}
	var result = "";
	for (var i = 0; i < directoryElem.length; i++) {
		result += directoryElem[i] + "/";
	}
	return result + qrstString + ".png";
}


/*
  GLOBAL_CONSTANTS
 */
// IE version dection
/** @ignore */
var arVersion = navigator.appVersion.split("MSIE");
/**
 * Get and sotre the microsoft internet explorer version.
 * @final
 */
var IE_VERSION =  parseFloat(arVersion[1]);
if (! arVersion[1]) {
	IE_VERSION = null;
}

/**
 * True if the microsoft internet explorer needs the image alpha workarounds
 * @final
 */
var IE_NEEDS_ALPHA_CODE = (IE_VERSION > 5) && (IE_VERSION < 7);

/** 
 * Give alpha layer to image fo microsoft IE 5.5 and 6. And transparent .gif file with same path (except extention) should be present on server
 * 
 * @param {Node} img Image DOM Node
 */
function loadImageWithAlphaLayer(img) {
	img.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+ img.src +'", sizingMethod="scale")';
	img.src = img.src.replace(".png", ".gif");
}


/**
 * the document url without parameters
 */
DOCUMENT_URL = document.location.href;
DOCUMENT_PARAMS = "";

if (document.location.href.lastIndexOf('?') > -1) {
	DOCUMENT_URL = document.location.href.substring(0, document.location.href.lastIndexOf('?'));
	var params = document.location.href.substring(document.location.href.lastIndexOf('?') + 1,  document.location.href.length);
	params = params.split("&");
	var paramsHash = {};
	for (var i = 0; i < params.length; i++) {
		var pair = params[i].split("=");
		paramsHash[pair[0]] = pair[1];
	}
	delete paramsHash["lat"];
	delete paramsHash["lon"];
	delete paramsHash["zoom"];
	DOCUMENT_PARAMS = new Array();
	for (var i in paramsHash) {
		DOCUMENT_PARAMS.push(i + "=" + paramsHash[i]);
	}
	DOCUMENT_PARAMS = DOCUMENT_PARAMS.join("&");
}
 

/**
 * Stores the decimal seperator
 * @final
 */
DECIMAL_SEP = (1.1).toString().split("1").join("");

/** For safari workarounds
 * @final
 */
BROWSER_IS_SAFARI = navigator.appVersion.indexOf("AppleWebKit") == -1 ? false : true;

/** Opacity Levels 
 * @final
 */
OPACITY_LEVELS = [0.33, 0.66, 1];

/** grab cursor 
 * @final
 */
CURSOR_GRAB = null;
if (! IE_VERSION) {
	// We assume mozilla, others browser have not cursor support anyway.
	CURSOR_GRAB = "-moz-grab";
}

// using null for 'CURSOR_GRAB' in ie makes it keep current cursor
// that should be the correct behavior most of the time 


/*
  Core architecture : Layers classes and main class
 */

if(typeof geogarage == "undefined") var geogarage = new Object();


/**
  GEvent hack to keep original google maps API behaviour

 */

geogarage.applyGEventHack = function() {
	// we need to use an temporary array because IE7
	// continues to iterate in the for (<prop> in <object>) loop if
	// new properties are added in <object> (damned ie devs).

	if (null == geogarage.oldGEvent) {
		geogarage.oldGEvent = {};
		var propsArray = new Array();

		for (s in GEvent) {
			propsArray.push(s);
		}
		
	}

	for (s in geogarage.oldGEvent) {
		
		arglist = ""
		for (var i = 1; i < geogarage.oldGEvent[s].length; i++) {
			arglist += ", arg" + i;
		}

		eval(" GEvent."+s+" = function (arg0 "+ arglist +") { if (arg0 && arg0.gmap != undefined) { arg0 = arg0.gmap; } geogarage.oldGEvent."+ s +"(arg0 " +arglist+ "); } ")
	}

}

/**
 * Generates a GMap Type composed of the tile layer list and with the given name.
 * @param {String} name Name of the map type
 * @param {Array} tileLayerList Layers to group in the map type
 * @returns The map type ready to be added in GMap2
 * @type GMapType
 */
geogarage.makeComposedMapType = function(name, tileLayerList) {
	var maxZoom = 0;
	for (var i = 0; i < tileLayerList.length; i++) {
		if (tileLayerList[i].maxResolution() + 1 > maxZoom) {
			maxZoom = tileLayerList[i].maxResolution() + 1;
		}
	}
	return new GMapType(tileLayerList, new GMercatorProjection(maxZoom), name);
}

/**
 * @class Abstract layer source, with common methods
 * 
 */
geogarage.LayerSource = function(enabled) {
	this.name = "";
	if (enabled != undefined) {
		this.enabed = enabled;
	} else {
		this.enabled = true;
	}
	this.mOpacity = 1;
}

/**
 * Generate a array of layers with the given object. Depending of the layer type 'onTransparentMapOverlay', 'onMagicOverlay' and 'onSimpleOverlay' are called on handler (with the corresponding LayerSource object) if a specific behaviour is needed for this tile. handler accepts or refuse the layer by returning true or false.
 * @param sourceLayersArray structured object that describe the layers
 * @param handler a handler if a specific behaviour is required with some kind of source
 * @return An array with the created layer list
 * @type Array
 */
generateLayerList = function(sourceLayersArray, handler) {
	if (! handler) handler = {};
	var layerList = new Array();
	for (var i = 0; i < sourceLayersArray.length; i++) {
		var activateMagicPath = false;
		var accept = true;
		switch(sourceLayersArray[i].type) {
			case "GoogleMapsTransparent" : {
				var tmapOverlay = new geogarage.LayerSourceGoogleTransparentMap(sourceLayersArray[i].name, sourceLayersArray[i].id, sourceLayersArray[i].enabled);
				if (handler.onTransparentMapOverlay) {
					accept = handler.onTransparentMapOverlay(tmapOverlay);
				}
				if (accept) {
					layerList.unshift(tmapOverlay);
				}
				break;
			}
			case "Magic" : {
				activateMagicPath = true;
				if (handler.onMagicOverlay) {
					accept = handler.onMagicOverlay(magicOverlay);
				}
			// We don't break here
			}
			case "Simple" : {
				var curLayer = sourceLayersArray[i];
				var simpleOverlay = new geogarage.LayerSourceMagic(curLayer.name, curLayer.id, curLayer.url, curLayer.top_qrst,
					curLayer.max_zoom, curLayer.bounds,curLayer.copyrights, curLayer.enabled, activateMagicPath);
				// we forward accept from previous case (accept = accept && handler.onS...() )
				if (handler.onSimpleOverlay) {
					accept = accept && handler.onSimpleOverlay(simpleOverlay);
				}
				if (accept) {
					layerList.unshift(simpleOverlay);
				}
				break;
			}
		}
	}
	return layerList;
}



geogarage.LayerSource.prototype = {
	/**
     * Set the new opacity for the layer, the visual will be updated when overlay will be (re)loaded 
     * @param {Number} newVal opacity value between 0 (invisible) and 1 (opaque).
     */
	setOpacity: function(newVal) {
		var theName = this.name;
		this.mOpacity = newVal;
		var opacity = this.mOpacity;
		this.gTileLayer.getOpacity = function () {
			return opacity;
		}
	},

	/**
     * Get the GTileLayer corresponding to this tile source.
     * @returns The GTileLayer instance
     * @type GTileLayer
     */ 
	getTileLayer: function() {
		if (! this.gTileLayer) {
			this.generateGTileLayerOverlay();
		}
		return this.gTileLayer;
	},

	/**
     * Get the GTileLayerOverlay corresponding to this tile source.
     * @returns The GTileLayerOverlay instance
     * @type GTileLayerOverlay
     */   
	getTileLayerOverlay: function() {
		if (! this.gTileLayerOverlay) {
			this.generateGTileLayerOverlay();
		}
		return this.gTileLayerOverlay;
	},

	/**
     * Creates a GMapType for this tile source.
     * @param {Array} othersLayers layers 
     * @returns The GMapType instance
     * @type GMapType
     */   
	makeMapType: function(othersLayers) {
		if (!othersLayers) {
			othersLayers = new Array()
		}
		return new GMapType(othersLayers.concat(this.getTileLayer()),
			new GMercatorProjection(this.getTileLayer().maxResolution() + 1),
			this.name);
	},
  
	/**
     * Initialize the instance 
     * @returns The GTileLayerOverlay instance
     * @type GTileLayerOverlay
     */  
	generateGTileLayerOverlay: function(){
		GLog.write("Do call this on LayerSource abstract class");
	}
}

/**
 * @class Concrete implementation of TileSource for Google's transparent maps
 * @constructor
 * @param {String} name name for the tile source
 * @param {String} id an identifier for programatical use
 * @param {boolean} enabled if value is true
 */
geogarage.LayerSourceGoogleTransparentMap = function(name, id, enabled) {
	if (enabled != undefined) {
		this.enabled = enabled;
	} else {
		this.enabled = true;
	}
	this.name = name;
	this.id = id;
}

geogarage.LayerSourceGoogleTransparentMap.prototype = new geogarage.LayerSource;

var layerSourceGoogleTransparentMapPrototype = {
	/**
     * Initialize the instance 
     * @member geogarage.LayerSourceGoogleTransparentMap
     * @returns The GTileLayerOverlay instance
     * @type GTileLayerOverlay
     */
	generateGTileLayerOverlay: function(){
		this.gTileLayer = G_HYBRID_MAP.getTileLayers()[1];
		this.gTileLayerOverlay =  new GTileLayerOverlay(this.gTileLayer);
		return this.gTileLayerOverlay;
	}
}

copyProperties(layerSourceGoogleTransparentMapPrototype, geogarage.LayerSourceGoogleTransparentMap.prototype);

/**
 * @class Concrete implementation of TileSource for SimpleQrst and Magic Qrst source
 *
 * @param {String} name name for the tile source
 * @param {String} id an identifier for programatical use
 * @param {String} url the base url for the tile source
 * @param {String} topQrst the highest qrst that this tile source has
 * @param {String} maxZoom the biggest zoom level this tile source can display (actually qrst tree depth)
 * @param {GLatLngBounds} bounds the area covered by this tile source
 * @param {String} copyrights defines the copyrights holder for this source
 * @param {boolean} enabled true if layer is enabled
 * @param {String} genPath true or false if wether the tile tree is stored in a directory tree or in a flat (single) directory
 */

geogarage.LayerSourceMagic = function(name, id, url, topQrst, maxZoom, bounds, copyrights, enabled, genPath) {
	/** @private */
	this.enabled = undefined;
	/** @private */
	this.name = undefined;
	/** @private */
	this.id = undefined;
	/** @private */
	this.mLeftPart = undefined;
	/** @private */
	this.mRightPart = undefined;
	/** @private */
	this.mMinRand = undefined;
	/** @private */
	this.mMaxRand = undefined;
	/** @private */
	this.mRandomized = undefined;
	/** @private */
	this.mUrl = undefined;
	/** @private */
	this.mBounds = undefined;
	/** @private */
	this.mTopQrst = undefined;
	/** @private */
	this.mMaxZoom = undefined;
	/** @private */
	this.mCopyrights = undefined;
	/** @private */
	this.gTileLayerOverlay = undefined;
	/** @private */
	this.genPath = undefined;
  
	this.name = name;
	this.id = id;
	var testRand = url.split("%");
	if (testRand.length == 3) {
		this.mLeftPart = testRand[0];
		this.mRightPart = testRand[2];
		var numbers = testRand[1].split("-");
		this.mMinRand = parseInt(numbers[0]);
		this.mMaxRand = parseInt(numbers[1]);
		this.mRandomized = true;
	} else {
		this.mRandomized = false;
		this.mUrl = url;
	}
  
	this.mBounds = bounds;
	if (! this.mBounds) {
		this.mBounds = new GLatLngBounds(new GLatLng(-180, -90), new GLatLng(180, 90));
	}
	this.mTopQrst = topQrst;
	this.mMaxZoom = maxZoom;
	this.mCopyrights = copyrights;
	this.gTileLayerOverlay = null;
	if (genPath) {
		this.genPath = true;
	} else {
		this.genPath = false;
	}
  
	if (enabled != undefined) {
		this.enabed = enabled;
	} else {
		this.enabled = true;
	}
  
}

geogarage.LayerSourceMagic.prototype = new geogarage.LayerSource;


var layerSouceMagicPrototype = {
	/**
     * Initialize the instance 
     * @member geogarage.LayerSourceMagic
     * @returns The GTileLayerOverlay instance
     * @type GTileLayerOverlay
     */
	generateGTileLayerOverlay: function() {
		var leftURLPart = this.mLeftPart;
		var rightURLPart = this.mRightPart;
		var minRand = this.mMinRand;
		var maxRand = this.mMaxRand;
		var url = this.mUrl;
		var randomized = this.mRandomized;
   
		var maxZoom = this.mMaxZoom;
		var topQrst = this.mTopQrst;
		var copyrights = new GCopyrightCollection("Image:");
		copyrights.addCopyright(
			new GCopyright(
				Math.floor(Math.random() * 10000),
				this.mBounds,
				topQrst.length,
				this.mCopyrights
				)
			);
		var pathneeded = this.genPath;
		this.gTileLayer = new GTileLayer(copyrights, topQrst.length, maxZoom);
		this.gTileLayer.getTileUrl = function (tile,  zoom) {
			var qrst = genQrst(tile.x, tile.y, zoom);
			// avoid attempts to load something where there is no tiles
			if ( ! BROWSER_IS_SAFARI && qrst.indexOf(topQrst) == -1) {
				return "";
			}
			var localurl = url;
			if (randomized) {
				var selectNumber = 0;
				var startNumber = qrst.length;
				startNumber = startNumber > 4 ? 4 : startNumber;
				for (var i = startNumber; i < qrst.length; i++) {
					selectNumber += qrst.charCodeAt(i) - 113;
				}
				selectNumber %= (maxRand - minRand + 1);
				localurl = leftURLPart + (selectNumber + minRand) + rightURLPart;
			}
			return localurl + (pathneeded ? genPathFromQrst(qrst) : qrst);
		}
    
		this.gTileLayer.isPng = function () {
			return true;
		}
		var opacity = this.mOpacity;
		this.gTileLayer.getOpacity = function () {
			return opacity;
		}
		this.gTileLayerOverlay = new GTileLayerOverlay(this.gTileLayer);
		return this.gTileLayerOverlay;
	}
}

copyProperties(layerSouceMagicPrototype, geogarage.LayerSourceMagic.prototype);

/**
 * @class Global class that enable using easily GMap2 with the GeoGarage. geogarage.Maps warps entirely methods of GMap2, and can be used seamlessly instead of a GMap2 instance. For an extended use of geogarage.Maps, read the <a href="http://www.google.com/apis/maps/documentation/reference.html">Google Maps API reference</a>
 *
 * @param {Node} elem the div elem that should contain the map
 * @param {Object} layersDefs the layers definition, to handle GeoGarage layers easily
 * @param {Object} opts <a href="http://www.google.com/apis/maps/documentation/reference.html#GMapOptions">GMap2 options</a> merged with geogarage.Maps options
 */
geogarage.Maps = function(elem, layersDefs, opts) {
	geogarage.applyGEventHack();

	/** @private */
	this.elem = undefined;
	/** @private */
	this.gmap = undefined;
	/** @private */
	this.layersList = undefined;
  
	if ( ! opts) opts = {};
	this.elem = elem;
	this.layersList = new Array();
	if (! opts.mapTypes ) {
		opts.mapTypes = [G_NORMAL_MAP, G_SATELLITE_MAP];
	}
	if (GBrowserIsCompatible()) {
		this.gmap = new GMap2(elem, opts);
	}
	if(layersDefs) {
		this.loadLayersWithSetup(layersDefs, opts);
	}
}

geogarage.Maps.IMAGES_PATH = "ggimages/"
geogarage.Maps.setImagesPath = function (path) {
	geogarage.Maps.IMAGES_PATH = path;
	updatePositionStyle();
}

geogarage.Maps.prototype = {
	/**
     * Defines the behavior when used with {@link #generateLayerList}
     * @private
     * @param overlay the overlay encountered : shoulf be a transparent map overlay
     * @returns true
     * @type Boolean
     */
	onTransparentMapOverlay: function(overlay) {
		var gmapInst = this.gmap;
		var geogmap = this;
		// We disable the transparent map overlay if type switched to NORMAL_MAP or HYBRID
		GEvent.addListener(this, "maptypechanged", function() {
			var currentType = gmapInst.getCurrentMapType();
			// We hide transparent map if a map will be displayed
			if (currentType == G_NORMAL_MAP || currentType == G_HYBRID_MAP) {
				if (overlay.enabled) {
					overlay.enabled = false;
					overlay.wasEnabled = true;
					geogmap.reloadLayers();
				}
			} else {
				if (overlay.wasEnabled) {
					overlay.enabled = true;
					overlay.wasEnabled = false;
					geogmap.reloadLayers();
				}
			}
		});
		return true;
	},
  
	/**
     * Loads the layers defined in given object
     *
     * @param {Object} sourceLayersDef the object that defines the layer
     */
	loadLayers: function(sourceLayersDef) {
		if (!this.layersList) {
			this.layersList = new Array();
		}
    
		var params = {};
		if (document.location.href.lastIndexOf('?') > -1) {
			var paramsArray = document.location.href.substring(document.location.href.lastIndexOf('?')+1, document.location.href.length);
			paramsArray = paramsArray.split(/&/);
			for (var i = 0; i < paramsArray.length; i++) {
				var pair = paramsArray[i].split(/=/);
					params[pair[0]] = pair[1];
				}
			}
    
			this.center_lat = params.lat || sourceLayersDef.globals.center_lat;
			this.center_lon = params.lon || sourceLayersDef.globals.center_lon;
			this.init_zoom = parseInt(params.zoom) || sourceLayersDef.globals.init_zoom;
    
    
			this.gmap.setCenter(new GLatLng(this.center_lat, this.center_lon), this.init_zoom);
			this.layersList = this.layersList.concat( generateLayerList(sourceLayersDef.layers, this));
		},
		/**
     * Removes all the layers enabled, loaded and shown with {@link geogarage.Maps#displayLayers}
     */ 
		clearLayers: function() {
			for (var i = 0; i < this.layersList.length; i++) {
				this.gmap.removeOverlay(this.layersList[i].gTileLayerOverlay);
				this.gmap.removeControl(this.layersList[i].control);
			}
		},
  
		/**
     * Displays the layers enabled and loaded with {@link geogarage.Maps#loadLayers} 
     */ 
		displayLayers: function() {
			for (var i = 0; i < this.layersList.length; i++) {
				if (! this.layersList[i].gTileLayerOverlay) {
					// we generate the layerOverlay objects and control
					this.layersList[i].generateGTileLayerOverlay();
					this.layersList[i].control = new OverlayController( this.layersList[i].name, this.layersList[i],  this.layersList[i].enabled, this);
				}
				var layer = this.layersList[i];
				this.gmap.addControl(layer.control);
				layer.control.setButtonEnabled(layer.enabled);
				if (layer.enabled) {
					this.gmap.addOverlay(layer.gTileLayerOverlay);
				}
			}
		},
 
		/**
     * Reloads the layers enabled and loaded with {@link geogarage.Maps#loadLayers} and displayed
     */ 
		reloadLayers : function() {
			this.clearLayers();
			this.displayLayers();
		},
  
  
		/**
     * Setup a default layout to display the layers
     *
     * @param {Object} layersObject an object that define the layers in a human readable way
     * @param {Object} opts choose some display parameters with the options
     */ 
		loadLayersWithSetup: function(layersObject, opts) {
			if ( ! opts) opts = {};
			var overlay = opts.method && opts.method == "overlay" ? true : false;
			var underlying = opts.underlying && opts.underlying instanceof Array ? opts.underlying : G_SATELLITE_MAP.getTileLayers();
			var tmaplayer = new geogarage.LayerSourceGoogleTransparentMap("Carte", "TMAP").getTileLayer();
			var over = opts.over && opts.over instanceof Array ?
			opts.over : [tmaplayer];
			var maponly = opts.nonormalmap ? undefined : G_NORMAL_MAP.getTileLayers();
			var maptype = opts.defaultmaptype ? opts.defaultmaptype : G_SATELLITE_MAP;
    
			this.loadLayers(layersObject);
			if (overlay) {
				OverlayController.setInitOffset(35);
				this.displayLayers();
			} else {
				var layers = this.getAllTileLayers();
				for (var i = 0; i < layers.length; i++) {
					if (layers[i] == tmaplayer) {
						layers.splice(i, 1);
					}
				}
				maptype = geogarage.makeComposedMapType("hybride", underlying.concat(layers.concat(over)));

				this.addMapType(maptype);
				this.removeMapType(G_NORMAL_MAP);
				this.removeMapType(G_SATELLITE_MAP);
				if (maponly) {
					this.addMapType(geogarage.makeComposedMapType("carte", maponly.concat(layers) ) );
				}
			}

			this.controls = {
				largeMap: new GLargeMapControl(),
				overviewMap: new GOverviewMapControl(),
				hierarchicalMapType: new GHierarchicalMapTypeControl(),
				scale: new GScaleControl(),
				centerPosition: new geogarage.CenterPosition(),
				reticle: new geogarage.Reticle()
			};

			for (var i in this.controls) {
				this.addControl(this.controls[i]);
			}

			this.enableContinuousZoom();
			this.enableDoubleClickZoom();
			this.enableScrollWheelZoom();
	
			this.setMapType(maptype);
		},

		/**
     * Retrieves a copy of the array containing all the layers loaded
     *
     * @return the array of layers
     * @type Array
     */  
		getAllLayers: function() {
			return new Array().concat(this.layersList);
		},
  
		/**
     * Creates a copy of all the GTileLayer, to create GMapTypes easily
     *
     * @return the array of GTileLayers
     * @type GTileLayer Array
     */    
		getAllTileLayers: function() {
			var layers = new Array();
			for (var i = 0; i < this.layersList.length; i++) {
				layers.push(this.layersList[i].getTileLayer());
			}
			return layers;
		}
	
	}

// We warp the Gmap API calls into embed object, without overriding existing functions
objectWrapper(GMap2.prototype, "geogarage.Maps.prototype", "this.gmap");


	/*
  GUI things : Overlay controllers buttons
 */

	/* Alpha controler : three levels of opacity managed by three buttons (kind of radio buttons) */
	/**
  @private
 */
	function AlphaController(switchButton, overlay, geogaragemap) {
		this.controledOverlay = overlay;
		this.button = switchButton;
		this.map = geogaragemap;
		this.levelSelected = 2;
		this.position = new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(switchButton.offsetX, switchButton.offsetY + 4))
	}


	AlphaController.prototype = new GControl(false, false);

	var alphaControllerPrototype = {
		formatControl: function(elem) {
			var leftImg = document.createElement("img");
			var centerImg = document.createElement("img");
			var rightImg = document.createElement("img");

			leftImg.setAttribute("src", geogarage.Maps.IMAGES_PATH + "ableft.png");
			centerImg.setAttribute("src", geogarage.Maps.IMAGES_PATH + "abcenter.png");
			rightImg.setAttribute("src", geogarage.Maps.IMAGES_PATH + "abright.png");
    
			leftImg.setAttribute("title", "opacity level " + (OPACITY_LEVELS[0] * 100) + "%");
			centerImg.setAttribute("title", "opacity level " + (OPACITY_LEVELS[1] * 100) + "%");
			rightImg.setAttribute("title", "opacity level " + (OPACITY_LEVELS[2] * 100) + "%");
    
			if (IE_NEEDS_ALPHA_CODE) {
				loadImageWithAlphaLayer(leftImg);
				loadImageWithAlphaLayer(centerImg);
				loadImageWithAlphaLayer(rightImg);
			}
    
			var alphaController = this;
			rightImg.alphaLevel = 2;
			centerImg.alphaLevel = 1;
			leftImg.alphaLevel = 0;
    
			rightImg.setSelectedView = centerImg.setSelectedView = leftImg.setSelectedView = function(selection) {
				if (selection) {
					this.src = this.src.replace(".png", "sel.png");
				} else {
					this.src = this.src.replace("sel.png", ".png");
				}
			}
    
			rightImg.onMouseClickView = centerImg.onMouseClickView = leftImg.onMouseClickView = function () {
				this.mSelected = !this.mSelected;
				alphaController.levelSelected = this.alphaLevel;
			}
    
			function onMouseOverEvenHandler() {
				if (!this.mSelected) {
					this.setSelectedView(true);
				}
			}
    
			function onMouseOutEvenHandler() {
				if (!this.mSelected) {
					this.setSelectedView(false);
				}
			}
    
			GEvent.addDomListener(leftImg, "mouseover", onMouseOverEvenHandler);
			GEvent.addDomListener(centerImg, "mouseover", onMouseOverEvenHandler);
			GEvent.addDomListener(rightImg, "mouseover", onMouseOverEvenHandler);
    
			GEvent.addDomListener(leftImg, "mouseout", onMouseOutEvenHandler);
			GEvent.addDomListener(centerImg, "mouseout", onMouseOutEvenHandler);
			GEvent.addDomListener(rightImg, "mouseout", onMouseOutEvenHandler);
   
			if (this.levelSelected == leftImg.alphaLevel) {
				leftImg.mSelected = true;
				centerImg.mSelected = false;
				rightImg.mSelected = false;
				leftImg.setSelectedView(true);
			}
			if (this.levelSelected == centerImg.alphaLevel) {
				leftImg.mSelected = false;
				centerImg.mSelected = true;
				rightImg.mSelected = false;
				centerImg.setSelectedView(true);
			}
			if (this.levelSelected == rightImg.alphaLevel) {
				leftImg.mSelected = false;
				centerImg.mSelected = false;
				rightImg.mSelected = true;
				rightImg.setSelectedView(true);
			}
    
    
			rightImg.style.cursor = centerImg.style.cursor = leftImg.style.cursor = "pointer";
    
			elem.appendChild(leftImg);
			elem.appendChild(centerImg);
			elem.appendChild(rightImg);
    
			this.left = leftImg;
			this.center = centerImg;
			this.right = rightImg;
			return elem;
		},

		createSimpleDiv: function() {
			var button = document.createElement("div");
			return button;
		},

		createAlphaControl: function() {
			var control = this.createSimpleDiv();
			return this.formatControl(control);
		},

		initialize: function(googlemap) {
			var control = this.createSimpleDiv();
			var map = this.map;
     
			googlemap.getContainer().appendChild(control);
			// Our reference is still valid : we can modify button aspect
			this.formatControl(control);
        
			var overlay = this.controledOverlay;
			function onClickHandler() {
				overlay.setOpacity(OPACITY_LEVELS[this.alphaLevel]);
				this.onMouseClickView();
				map.reloadLayers();
			}
        
			GEvent.addDomListener(this.left, "click", onClickHandler);
			GEvent.addDomListener(this.center, "click", onClickHandler);
			GEvent.addDomListener(this.right, "click", onClickHandler);
        
			this.control = control;
			return control;
		},

		getDefaultPosition: function() {
			return this.position;
		}
	}

	copyProperties(alphaControllerPrototype, AlphaController.prototype);

	/* Alpha slider : three levels of opacity managed by three buttons (kind of radio buttons) */
	/**
  @private
 */
	function AlphaSliderController(switchButton, overlay, geogaragemap) {
		this.controledOverlay = overlay;
		this.button = switchButton;
		this.map = geogaragemap;
		this.SIZING = AlphaSliderController.SIZING;
  
		this.position = new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(switchButton.offsetX, switchButton.offsetY));
	}

	AlphaSliderController.SIZING = {
		MIN_LEFT: 15,
		MAX_LEFT: 70
	};

	AlphaSliderController.prototype = new GControl(false, false);

	var alphaSliderControllerPrototype = {
		formatControl: function(elem) {
			this.bar = document.createElement("img");
			this.slider =  document.createElement("img");


			this.bar.setAttribute("src", geogarage.Maps.IMAGES_PATH + "slider-bar.png");
			this.slider.setAttribute("src", geogarage.Maps.IMAGES_PATH + "slider.png");
    
        
			if (IE_NEEDS_ALPHA_CODE) {
				loadImageWithAlphaLayer(this.bar);
				loadImageWithAlphaLayer(this.slider);
			}
    
			copyProperties(
			{
				position: "relative",
				top: "0",
				left: "0px"
			},
			this.bar.style
			);

			copyProperties(
			{
				position: "relative",
				top: "1px",
				zIndex: 1,
				cursor: CURSOR_GRAB
			},
			this.slider.style
			);

			this.setValue(this.controledOverlay.gTileLayer.getOpacity());
    
			elem.appendChild(this.slider);
			elem.appendChild(this.bar);
    
			elem.style.height = "1%";
			return elem;
		},

		createSimpleDiv: function() {
			var button = document.createElement("div");
			return button;
		},

		createAlphaControl: function() {
			var control = this.createSimpleDiv();
			return this.formatControl(control);
		},
  
		getValue: function() {
			var width = (this.SIZING.MAX_LEFT - this.SIZING.MIN_LEFT);
			return (parseInt(this.slider.style.left) - this.SIZING.MIN_LEFT) / width;
		},
  
		setValue: function(value) {
			var width = (this.SIZING.MAX_LEFT - this.SIZING.MIN_LEFT);
			this.slider.style.left = (value * width + this.SIZING.MIN_LEFT) + "px";
		},
  
		onMouseDown: function(ev) {
			this.dragged = true;
			this.mouseXPos = ev.screenX;
			ev.preventDefault();
			return false;
		},
  
		onMouseDrag: function(ev) {
			if (this.dragged) {
				var new_pos = (ev.screenX - this.mouseXPos + parseInt(this.slider.style.left));
				if (new_pos < this.SIZING.MAX_LEFT) {
					if (new_pos > this.SIZING.MIN_LEFT) {
						new_pos = new_pos + "px";
					} else {
						new_pos = this.SIZING.MIN_LEFT + "px";
					}
				} else {
					new_pos = this.SIZING.MAX_LEFT + "px";
				}
				this.slider.style.left = new_pos;
				this.mouseXPos = ev.screenX;
			}
			ev.preventDefault();
		},

		onMouseUp: function(ev) {
			if (this.dragged) {
				this.dragged = false;
				this.controledOverlay.setOpacity(this.getValue());
				this.map.reloadLayers();
			}
		},
  
		onMouseDownOnBar: function(ev) {
			var pos = getMousePosFor(ev, this.bar).x + 4;
        
			if (pos < this.SIZING.MAX_LEFT) {
				if (pos > this.SIZING.MIN_LEFT) {
					pos = pos;
				} else {
					pos = this.SIZING.MIN_LEFT;
				}
			} else {
				pos = this.SIZING.MAX_LEFT;
			}
			this.slider.style.left = pos + "px";
			this.controledOverlay.setOpacity(this.getValue());
			this.map.reloadLayers();
		},
  
		initialize: function(googlemap) {
			var control = this.createSimpleDiv();
     
			googlemap.getContainer().appendChild(control);
			// Our reference is still valid : we can modify button aspect
			this.formatControl(control);
        
			var controller = this;
			GEvent.addDomListener(this.slider, "mousedown", function(ev){
				controller.onMouseDown(ev);
			})
			GEvent.addDomListener(this.slider, "mousemove", function(ev){
				controller.onMouseDrag(ev);
			})
			GEvent.addDomListener(this.bar, "mousemove", function(ev){
				controller.onMouseDrag(ev);
			})
			GEvent.addDomListener(document.body, "mouseup", function(ev){
				controller.onMouseUp(ev);
			})
			GEvent.addDomListener(this.bar, "mousedown", function(ev) {
				controller.onMouseDownOnBar(ev);
			});
        
			this.control = control;
			return control;
		},

		getDefaultPosition: function() {
			return this.position;
		}
	}
	copyProperties(alphaSliderControllerPrototype, AlphaSliderController.prototype);

	/*  General overlay code (enable and disable overlay, add/remove the alpha control)  */

	/**
  @private
 */
	function OverlayController(title, overlayer, was_enabled, geogaragemap) {
		this.controledOverlay = overlayer;
		this.map = geogaragemap;
		if (was_enabled) {
			this.controledOverlay.enabled = true;
		} else {
			this.controledOverlay.enabled = false;
		}
		this.button_title = title;
		this.button = null;
  
		if (! this.map.overlayController_count) this.map.overlayController_count = 0;
		this.map.overlayController_count++;
		this.offsetX = 5;
		this.offsetY = 5 + 26 * (this.map.overlayController_count - 1) + OverlayController.INIT_Y_OFFSET ;
		var alphaControlSpace = IE_NEEDS_ALPHA_CODE ? 0 : 70;
		this.position = new GControlPosition(G_ANCHOR_TOP_RIGHT, new GSize(this.offsetX + alphaControlSpace, this.offsetY));
	}

	OverlayController.setInitOffset = function(val) {
		OverlayController.INIT_Y_OFFSET = val;
	}

	OverlayController.prototype = new GControl(false, false);
	OverlayController.count = 0;
	OverlayController.INIT_Y_OFFSET = 0;

	var overlayControllerPrototype = {
		formatButton: function(elem, text) {
			var leftImg = document.createElement("img");
			var contentSpan = document.createElement("span");
			var rightImg = document.createElement("img");
    
			// we copy attributes from the given elements, to keep properties such as non selectable content

			leftImg.setAttribute("src", geogarage.Maps.IMAGES_PATH + "bgleft.png");
			rightImg.setAttribute("src", geogarage.Maps.IMAGES_PATH + "bgright.png");
			rightImg.style.verticalAlign = leftImg.style.verticalAlign = "middle";
			rightImg.style.cursor = leftImg.style.cursor = "pointer";
   
			if (IE_NEEDS_ALPHA_CODE) {
				loadImageWithAlphaLayer(leftImg);
				loadImageWithAlphaLayer(rightImg);
			}
    
			var contentStyle = {
				background: "transparent url("+geogarage.Maps.IMAGES_PATH +"bgcontent.png) repeat-x scroll center",
				padding: "6px",
				fontSize: "10px",
				fontFamily: "arial, sans-sherif, sans-serif",
				verticalAlign: "middle",
				cursor: "pointer",
				color: "black",
				overlflow: "auto"
			}
    
			copyProperties(contentStyle, contentSpan.style);
			contentSpan.innerHTML = text;
			elem.innerHTML = "";
			elem.appendChild(leftImg);
			elem.appendChild(contentSpan);
			elem.appendChild(rightImg);
			elem.setAttribute("title", "enable/disable layer");
			elem.textNodeNumber = 1; // make sure to correct that if you change 'contentSpan' node position in elem;
			return elem;
		},

		createButton: function(text) {
			var button = document.createElement("div");
			this.formatButton(button, text);
			return button;
		},

		createSimpleDiv: function(text) {
			var button = document.createElement("div");
			button.innerHTML = text;
			return button;
		},

		selectable: function() {
			return false;
		},

		printable: function() {
			return false;
		},

		createAlphaController: function() {
			if (IE_NEEDS_ALPHA_CODE) {
				return;
			}
			if (this.alphacontrol) {
				this.map.gmap.removeControl(this.alphacontrol);
			}
			if (!this.alphacontrol) {
				this.alphacontrol = new AlphaSliderController(this, this.controledOverlay, this.map);
			}
			this.map.gmap.addControl(this.alphacontrol);
		},

		initialize: function(googlemap) {
			var button = this.createSimpleDiv(this.button_title);
			var map = this.map;
			button.innerHTML = this.button_title;
			var overlay = this.controledOverlay;
			if (overlay) {
				GEvent.addDomListener(button, "click", function() {
					if (overlay.enabled) {
						overlay.enabled = false;
					} else {
						overlay.enabled = true;
					}
					map.reloadLayers();
				});
			}
			googlemap.getContainer().appendChild(button);
			// Our reference is still valid : we can modify button aspect
			this.formatButton(button, button.innerHTML);
			this.button = button;
			this.createAlphaController();
			return button;
		},

		setButtonEnabled: function(enabled) {
			var textNode = this.button.childNodes[this.button.textNodeNumber];
			if (enabled) {
				textNode.style.color = "black";
			} else {
				textNode.style.color = "grey";
			}
		},

		getDefaultPosition: function() {
			return this.position;
		}
	}

	copyProperties(overlayControllerPrototype, OverlayController.prototype);

	/* utility classes,  function  && Variables */
	/**
 * Parameters are all optional
 * @class define some ways to format Lat/Lng degrees for displaying latitudes and longitudes.
 *
 * @param {Number} prec precision, in number of decimals
 * @param {Boolean} displayCards true or false wether if the N/S/E/W cadinal points should be shown or use negative values
 *
 */
	function DegreeFormater(prec, displayCards) {
		/** the precision to display */
		this.precision = undefined;
		/** true if cardinal points caracters (N/S/E/W) should be displayed */
		this.displayCards = undefined;
		if (prec) {
			this.precision = prec;
		} else {
			this.precision = 0;
		}
		if (displayCards) {
			this.displayCards = true;
		} else {
			this.displayCards = false;
		}
	}

	DegreeFormater.prototype = {
		/**
     * Returns the cardinal caracter to use, wether if the number is positive/negative and is a latitude/longitude
     * @private
     * @param {Boolean} positive true if the value is positive
     * @param {Boolean} lat true if the value is a latitude
     */
		getCardString: function(positive, lat) {
			if (lat) {
				return positive ? "N" : "S";
			} else{
				return positive ? "E" : "W";
			}
		},

		/**
     * Formats the value in a &lt;deg&gt;&deg; &lt;min&gt;' &lt;sec&gt;" format (with seconds precision defined in constructor)
     * @param {Number} value a degree value (unbounded, but should be limited to -180/+180
     * @param {Boolean} lat true if the value is a latitude
     */  
		formatToDegMinSec : function(value, lat) {
			var prec = this.precision;
			var precMul = Math.pow(10, prec);
			var neg = value < 0 ? true : false;
			if (neg) value = -value;
			var degs = Math.floor(value);
			value -= degs;
			value *= 60;
			degs = degs.toString();
			if (neg && ! this.displayCards) degs = "-"+degs;
			var mins = Math.floor(value);
			value -= mins;
			value *= 60;
			mins = mins.toString();
			var secs = value.toFixed(prec).toString();
			if (prec > 0) prec++;
			while(degs.length < 3) {
				degs = " " + degs;
			}
			while(mins.length < 2) {
				mins = " " + mins;
			}
			while(secs.length < 2 + prec) {
				secs = " " + secs;
			}
			return degs + '&deg;' + mins + "'" + secs + '"' + (this.displayCards ? this.getCardString(!neg, lat) : "");
		},
  
		/**
     * Formats the value in a &lt;deg&gt;&deg; &lt;min&gt;' format (with minutes precision defined in constructor)
     * @param {Number} value a degree value (unbounded, but should be limited to -180/+180
     * @param {Boolean} lat true if the value is a latitude
     */    
		formatToDegMin: function (value, lat) {
			var prec = this.precision;
			var precMul = Math.pow(10, prec);
			var neg = value < 0 ? true : false;
			if (neg) value = -value;
			var degs = Math.floor(value);
			value -= degs;
			value *= 60;
			degs = degs.toString();
			if (neg && ! this.displayCards) degs = "-"+degs;
			var mins = value.toFixed(prec).toString();
			if (prec > 0) prec++;
			while(degs.length < 3) {
				degs = " " + degs;
			}
			while(mins.length < 2 + prec) {
				mins = " " + mins;
			}
			return degs + '&deg;' + mins + "'" + (this.displayCards ? this.getCardString(!neg, lat) : "");
		},

  
		/**
     * Formats the value in a &lt;deg&gt;&deg; format (with degree precision defined in constructor)
     * @param {Number} value a degree value (unbounded, but should be limited to -180/+180
     * @param {Boolean} lat true if the value is a latitude
     */    
		formatToDec: function (value, lat) {
			var prec = this.precision;
			var precMul = Math.pow(10, prec);
			var neg = value < 0 ? true : false;
			if (neg) value = -value;
			var degs = value.toFixed(prec).toString();
			if (neg && ! this.displayCards) degs = "-"+degs;
			if (prec > 0) prec++;
			while(degs.length < 3 + prec) {
				degs = " " + degs;
			}
			return degs + '&deg;' + (this.displayCards ? this.getCardString(!neg, lat) : "");
		},
  
		/**
     * This method is here to define a interface for the use of a default format ({@link DegreeFormater#formatToDegMinSec}  here) <br />
     * It should be overridden by copying other methods reference : 
     * <pre>var myFormat = new DegreeFormater(5, false);<br />myformat.format = myformat.formatToDec; // default format is now a decimal value</pre>
     *
     * @param {Number} value a degree value (unbounded, but should be limited to -180/+180
     * @param {Boolean} lat true if the value is a latitude
     */  
		format: function(value, lat) {
			return this.formatToDegMinSec(value, lat);
		}
  
	}

	/** an instance for &lt;deg&gt;&deg; &lt;min&gt;' &lt;sec&gt;" format, one decimal precision for the seconds, with cardinal points caracters */
	DegreeFormater.DEFAULT_DEG_MIN_SEC = new DegreeFormater(1, true);
	/** an instance for &lt;deg&gt;&deg; &lt;min&gt;' format, four decimal precision for the minutes, with cardinal points caracters */
	DegreeFormater.DEFAULT_DEG_MIN = new DegreeFormater(4, true);
	DegreeFormater.DEFAULT_DEG_MIN.format = DegreeFormater.prototype.formatToDegMin;
	/** an instance for &lt;deg&gt;&deg; &lt;min&gt;' format, seven decimal precision for the degrees, without cardinal points caracters */
	DegreeFormater.DEFAULT_DEG = new DegreeFormater(7, false);
	DegreeFormater.DEFAULT_DEG.format = DegreeFormater.prototype.formatToDec;

	/** an array of defaults format, usefull for cylcing throught different formats */
	DegreeFormater.DEFAULT_FORMATS = [DegreeFormater.DEFAULT_DEG_MIN_SEC, DegreeFormater.DEFAULT_DEG_MIN, DegreeFormater.DEFAULT_DEG];

	/**
 * @private
 * defines where and how CenterPosition and MousePosition instances should be placed
 */

	geogarage.POSITIONS = {
		STYLE: {},
		ANCHOR_BOTTOM_LEFT: 30,
		ANCHOR_BOTTOM_RIGHT: 7,
		ANCHOR_TOP_RIGHT: 7,
		ANCHOR_TOP_LEFT: 7
	};

	function updatePositionStyle() {
		geogarage.POSITIONS.STYLE = {
			color: "white",
			fontFamily:  '"Andale Mono", "Lucida Console", "Courier New", monospace',
			fontSize: "10px",
			whiteSpace: "pre",
			padding: "2px"
		}
		if (IE_NEEDS_ALPHA_CODE) {
			geogarage.POSITIONS.STYLE.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod="scale", src=" '+ geogarage.Maps.IMAGES_PATH  +'black50.png")';
		} else {
			geogarage.POSITIONS.STYLE.backgroundImage = "url("+geogarage.Maps.IMAGES_PATH +"black50.png)";
		}
	}

	updatePositionStyle()


	/**
  @class An instance of this class, when added to a GMap2, will display the latitude and longitude of the center of the map. It supports cycling through different formats.
 */
	geogarage.CenterPosition = function() {
		this.currentFormat = 1;
		this.formater = DegreeFormater.DEFAULT_FORMATS[this.currentFormat];
	}

	geogarage.CenterPosition.prototype = new GControl(true, true);

	var centerPositionPrototype = {
		/**
  Update the displayed value to current center lat/lng
  @private
  @member geogarage.CenterPosition
  @param {GMAP2} googlemap the GMap2 object
  @param {Node} textDiv the text div where the text should be displayed
  @param {Node} linkA the anchor whose link will be updated
  @
     */
		updateValue: function(googlemap, textDiv, linkA) {
			var latlon  = googlemap.getCenter();
			textDiv.innerHTML = this.formater.format(latlon.lat(), true) + "," + this.formater.format(latlon.lng(), false);
			linkA.href = DOCUMENT_URL + "?" + (DOCUMENT_PARAMS ? DOCUMENT_PARAMS + "&" : "") +"lat="+latlon.lat()+"&lon="+latlon.lng()+"&zoom="+googlemap.getZoom();
		},

		/**
  Configures what events should be listened and how handle it
  @private
  @member geogarage.CenterPosition
  @param {GMAP2} googlemap the GMap2 object
  @param {Node} text the text div where the text should be displayed
  @param {Node} link the anchor whose link will be updated
  @
     */
		setUpEventListening: function(googlemap, text, link) {
			var ctl = this;
			function updateFieldValue() {
				ctl.updateValue(googlemap, text, link);
			}
			GEvent.addListener(googlemap, "moveend", updateFieldValue);
			GEvent.addDomListener(window, "resize", updateFieldValue);

		},

		/**
  The GControl initialize function implementation
  @private
  @member geogarage.CenterPosition
  @param {GMAP2} googlemap the GMap2 object
  @
     */
		initialize: function(googlemap) {
			this.mapInstance = googlemap;
			var textField = document.createElement("div");
			copyProperties(geogarage.POSITIONS.STYLE, textField.style);
			googlemap.getContainer().appendChild(textField);
			var switcher = document.createElement("img");
			switcher.title = "Choose between Lat/Lon format";
			switcher.style.verticalAlign = "-15%";
			switcher.style.marginRight = "5px";
			switcher.src = geogarage.Maps.IMAGES_PATH + "loopswitch.png";
			if (IE_NEEDS_ALPHA_CODE) {
				loadImageWithAlphaLayer(switcher)
			}
			switcher.style.cursor = "pointer";
			textField.appendChild(switcher);
			var text = document.createElement("span");
			textField.appendChild(text);
			var link = document.createElement("a");
			link.innerHTML = "(link)";
			textField.appendChild(link);
			link.style.color = "#ddf";
			link.style.textDecoration = "none";
			link.style.padding = "6px";
	
			var ctl = this;
			GEvent.addDomListener(switcher, "click", function() {
				ctl.currentFormat++;
				ctl.currentFormat %= DegreeFormater.DEFAULT_FORMATS.length;
				ctl.formater = DegreeFormater.DEFAULT_FORMATS[ctl.currentFormat];
				ctl.updateValue(googlemap, text);
			});
    
			this.setUpEventListening(googlemap, text, link);
   
			this.updateValue(googlemap, text, link);
			return textField;
		},

		/**
  The GControl getDefaultPosition function implementation
  @private
  @member geogarage.CenterPosition
  @
     */
		getDefaultPosition: function() {
			if (this.mapInstance) {
				if(! this.mapInstance.POSITIONS) this.mapInstance.POSITIONS = {
					ANCHOR_BOTTOM_LEFT: geogarage.POSITIONS.ANCHOR_BOTTOM_LEFT
				};
				this.mapInstance.POSITIONS.ANCHOR_BOTTOM_LEFT += 10;
			}
			return new GControlPosition(G_ANCHOR_BOTTOM_LEFT, new GSize(5, this.mapInstance.POSITIONS.ANCHOR_BOTTOM_LEFT));
		}

	}

	copyProperties(centerPositionPrototype, geogarage.CenterPosition.prototype);


	/**
  @class An instance of this class, when added to a GMap2, will display the latitude and longitude of the mouse pointer. It supports cycling through different formats.
 */
	geogarage.MousePosition = function () {
		this.currentFormat = 1;
		this.formater = DegreeFormater.DEFAULT_FORMATS[1];
	}

	geogarage.MousePosition.prototype = new geogarage.CenterPosition;

	var mousePositionPrototype = {
		/**
  Update the displayed value to current mouse lat/lng (overrides base class implementation)
  @private
  @member geogarage.MousePosition
  @param {GMAP2} googlemap the GMap2 object
  @param {Node} textDiv the text div where the text should be displayed
  @param {GLatLon} latlng the text div where the text should be displayed
  @
     */
		updateValue: function(googlemap, textDiv, latlng) {
			if (!latlng) return;
			textDiv.innerHTML = this.formater.format(latlng.lat(), true) + "," + this.formater.format(latlng.lng(), false);
		},
  
		/**
  Configures what events should be listened and how handle it (overrides base class implementation)
  @private
  @member geogarage.MousePosition
  @param {GMAP2} googlemap the GMap2 object
  @param {Node} text the text div where the text should be displayed
  @
     */
		setUpEventListening: function(googlemap, text) {
			var ctl = this;
			GEvent.addListener(googlemap, "mousemove", function(latlng) {
				ctl.updateValue(googlemap, text, latlng);
			});
		}
	}

	copyProperties(mousePositionPrototype, geogarage.MousePosition.prototype);

	/** @class An instance of this class, when added to a GMap2, will display a reticle (crosschair) at the center of the screen */
	geogarage.Reticle = function () {
		this.position = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(0, 0));
	}

	geogarage.Reticle.prototype = new GControl(false, false);

	var reticlePrototype = {

		placeOnCenter: function(googlemap) {
			var container = googlemap.getContainer();
			var newpoint = {
				x:container.clientWidth/2,
				y:container.clientHeight/2
			};
			var newposition = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(newpoint.x - 8, newpoint.y - 8));
			newposition.apply(this.reticle);
		},

		/**
  The GControl initialize function implementation
  @private
  @member geogarage.Reticle
  @param {GMAP2} googlemap the GMap2 object
  @
     */
		initialize : function(googlemap) {
			var reticle = document.createElement("img");
			reticle.src = geogarage.Maps.IMAGES_PATH + "reticle.png";
			reticle.style.cursor = CURSOR_GRAB;
    
			if (IE_NEEDS_ALPHA_CODE) {
				loadImageWithAlphaLayer(reticle)
			}
			var container = googlemap.getContainer();
			container.appendChild(reticle);

			var point = {
				x:container.clientWidth/2,
				y:container.clientHeight/2
			};
			var position = new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(point.x - 8, point.y - 8));
			position.apply(reticle);

			control = this;
			GEvent.addDomListener(window, "resize", function() {
				control.placeOnCenter( googlemap );
			});


			GEvent.addDomListener(reticle, "dblclick", function() {
				googlemap.zoomIn(googlemap.getCenter(), true, true);
			});
    
			GEvent.addDomListener(reticle, "mousedown", function() {
				reticle.style.cursor = "-moz-grabbing";
			});
			GEvent.addDomListener(reticle, "mouseup", function() {
				reticle.style.cursor = "-moz-grab";
			});
			this.position = position;
			this.reticle = reticle;
			return reticle;
		},
  
		/**
  The GControl printable function implementation
  @private
  @member geogarage.Reticle
  @
     */
		printable: function () {
			return false;
		},
		/**
  The GControl selectable function implementation
  @private
  @member geogarage.Reticle
  @
     */
		selectable: function () {
			return false;
		},

		/**
  The GControl getDefaultPosition function implementation
  @private
  @member geogarage.Reticle
  @
     */
		getDefaultPosition: function() {
			return this.position;
		}

	}

	copyProperties(reticlePrototype, geogarage.Reticle.prototype);

