// JavaScript Document

window.includes = (window.includes == undefined) ? [] : window.includes;
if(window.includes["wmap.js"]==undefined){
	window.includes["wmap.js"]=true;

	Number.prototype.toRad = function() {  // convert degrees to radians
	  return this * Math.PI / 180;
	}
	
	Number.prototype.toDeg = function() {  // convert radians to degrees (signed)
	  return this * 180 / Math.PI;
	}
	
	
	
	function WMarker(location, icon, label, color, width, height){
		// Represents a maker, this is old (for flash map api)
		Marker.prototype = Object.call(this);
		this.location = location;
		this.label = label;
		this.icon = icon;
		this.color = color;
		this.labelWidth = width;
		this.labelHeight = height;
		this.toString = function(){
			var marker = "Marker{";
			for(var i in this){
				marker += i + ":" + this[i];
			}
			marker += "}";
			return marker;
		}
	}
	
	function Marker(id, location, options){
		// Represents a marker object, that has an id, a location, and some options like content, icon
		this.id = id;
		this.location = location;
		for(var i in options){
			this[i] = options[i];
		}
	}
	Marker.EVENT_CLICK = "Marker clicked"
	Marker.EVENT_ROLLOVER = "Marker rolled over";
	Marker.prototype.toString = function(){
		return "Marker["+this.id+"](" + this.location.lat + "&deg; lat " + this.location.lon + "&deg; lon)";
	}
	
	function MarkerCollection(){
		// Represents a collection of markers
		this.markers = [];
	}
	MarkerCollection.prototype.push = function(mk){
		// add a marker to the collection if ID does not exist
		if(!this.exists(mk)){
			this.markers.push(mk);
			return true;
		}else{
			return false;
		}
	}
	MarkerCollection.prototype.pop = function(id){
		// remove marker from collection and return it
		result = [];
		toreturn = null
		while(el = this.markers.pop())
			if(el.id != id){
				result.push(el);
			}else{
				toreturn = el;
			}
		this.markers = result;
		return toreturn;
	}
	MarkerCollection.prototype.exists = function(mk){
		// Check for existence of the marker
		for(var i=0; i<this.markers.length; i++){
			if(mk.id == this.markers[i].id)
				return this.markers[i];
		}
		return false;
	}
	MarkerCollection.prototype.toString = function(){
		var toreturn = "";
		for(var i=0; i<this.markers.length; i++){
			toreturn += this.markers[i] + "<br>";
		}
		return toreturn;
	}
	MarkerCollection.prototype.bounds = function(){
		// Get latitude/longitude bounds of all markers in the collection
		var minLat, minLon, maxLat, maxLon, l;
		for(var i=0; i<this.markers.length; i++){
			l = this.markers[i].location
			minLat = Math.min(minLat || l.lat, l.lat);
			maxLat = Math.max(maxLat || l.lat, l.lat);
			minLon = Math.min(minLon || l.lon, l.lon);
			maxLon = Math.max(maxLon || l.lon, l.lon);
		}
		return {tl:new Location(minLat, minLon), br:new Location(maxLat, maxLon)};
	}
	MarkerCollection.prototype.overlaps = function(mk){
		// Checks whether a marker overlaps another in the collection
		for(var i=0; i<this.markers.length; i++){
			//alert(this.markers[i].icon.src + ", indexof = " + this.markers[i].icon.src.indexOf("target"));
			if(mk.markerObject.id !=this.markers[i].id && mk.markerObject.location.lat == this.markers[i].location.lat && mk.markerObject.location.lon == this.markers[i].location.lon && this.markers[i].icon.src.indexOf("target")==-1)
				return true;
		}
		return false;
	}
	MarkerCollection.prototype.getOverlapping = function(mk){
		var overlapping = [];
		for(var i=0; i<this.markers.length; i++){
			if(this.markers[i].icon.src.indexOf("target.png") == -1 &&  mk.markerObject.id !=this.markers[i].id && mk.markerObject.location.lat == this.markers[i].location.lat && mk.markerObject.location.lon == this.markers[i].location.lon)
				overlapping.push(this.markers[i]);
		}
		return overlapping;
	}
	function ContentMarker(id, location, options){
		// A marker that has an icon that when is rolled over pops up the actual content of the marker
		EventDispatcher.call(this);
		this.id = id;
		this.location = location;
		options.skin = options.skin || "wmap";
		options.position = options.position || "t";
		for(var i in options){
			this[i] = options[i];
		}
		this.hidden = true;
		this.container = WUtility.createElement("DIV", {}, {position:"absolute"}); // create a container
		this.icon = this.container.appendChild(WUtility.createElement("IMG", {src:options.icon}, {cursor:"pointer",zIndex:1, position:"absolute", bottom:(-options.center.y)+"px", left:(-options.center.x)+"px"})); // create a visible pin
		this.icon.markerObject = this.container.markerObject = this; // add a reference for future use
		if(options.content!=undefined)
			this.tip = new WTip(this.icon, {skin:options.skin, position:options.position, content:options.content});
			//this.tip = new WTip(this.icon, {skin:options.skin, position:options.position, content:options.content});
		this.icon.onclick = function(e){
			this.markerObject.Dispatch(Marker.EVENT_CLICK, {marker:this.markerObject})
		}
		return this.container; // return the container that can be attached to map
	}
	ContentMarker.prototype = new EventDispatcher;
	ContentMarker.prototype.fadeIn = function(){
		// Fade in the marker's content
		Show(this.content);
		var t = new Tween(this.content, Tween.alphaHandler, Tween.strongEaseInOut, 0, 100, .5);
		t.addEventObserver(this, Tween.EVENT_FINISHED, "show");
		t.start();
	}
	ContentMarker.prototype.fadeOut = function(){
		// Fade out the markers content
		var t = new Tween(this.content, Tween.alphaHandler, Tween.strongEaseInOut, 100, 0, .5);
		t.addEventObserver(this, Tween.EVENT_FINISHED, "hide");
		t.start();
	}
	ContentMarker.prototype.show = function(){
		// show the content
		this.hidden = false;
	}
	ContentMarker.prototype.hide = function(){
		// hide the content
		Hide(this.content)
		this.hidden = true;
	}
	
	
	
	
	function mapProvider(){
		// Base class for map providers
		this.url="";
		this.url404="";
		this.projection = null;
		this.minZoom = this.maxZoom = this.tileSize = this.tilePad = 0;
		this.assembleUrl = function(xIndex, yIndex, zoom) {
			//Debug("x = " + xIndex + ", y = " + yIndex + ", z = " + zoom);
			var th = new templateHandler(this.url);
			return th.evaluate({X:xIndex, Y:yIndex, Z:zoom});
		}
		this.assembleUrl404 = function(xIndex, yIndex, zoom) {
			//Debug("x = " + xIndex + ", y = " + yIndex + ", z = " + zoom);
			var th = new templateHandler(this.url404);
			return th.evaluate({X:xIndex, Y:yIndex, Z:zoom});
		}
	}
	function westendRoadMapProvider(){
		// Westend roadmap provider
		mapProvider.call(this);
		this.url = "/wmap/tile-{X}-{Y}-{Z}.png";
		this.url404 = "http://us.maps1.yimg.com/us.tile.maps.yimg.com/tl?v=4.1&md=2&x={X}&y={Y}&z={Z}&r=1";
		this.projection = new MercatorProjection(26, new Transformation(1.068070779e7, 0, 3.355443185e7, 0, -1.068070890e7, 3.355443057e7));
		this.minZoom = 1;
		this.maxZoom = 17;
		this.tileSize = 256;
		this.locationCoordinate = function(location){
			return this.projection.locationCoordinate(location);
		}
		this.assembleUrl404 = function(xIndex, yIndex, zoom){
			var tileY_w = ( Math.pow( 2, zoom ) /2 ) - yIndex - 1;
			var th = new templateHandler(this.url404);
			return th.evaluate({X:xIndex, Y:tileY_w, Z:zoom+1});
		}
	}
	westendRoadMapProvider.prototype = new mapProvider;
	
	function googleRoadMapProvider(){
		// Yahoo roadmap provider
		mapProvider.call(this);
		this.url = "http://mt" + Math.floor(Math.random() * 3) + ".google.com/vt/x={X}&y={Y}&z={Z}";
		this.projection = new MercatorProjection(26, new Transformation(1.068070779e7, 0, 3.355443185e7, 0, -1.068070890e7, 3.355443057e7));
		this.minZoom = 1;
		this.maxZoom = 18;
		this.tileSize = 256;
		this.locationCoordinate = function(location){
			return this.projection.locationCoordinate(location);
		}
		this.assembleUrl = function(xIndex, yIndex, zoom){
			return googleRoadMapProvider.prototype.assembleUrl.call(this, xIndex, yIndex, zoom);
		}
	}
	googleRoadMapProvider.prototype = new mapProvider;
	
	function yahooRoadMapProvider(){
		// Yahoo roadmap provider
		mapProvider.call(this);
		this.url = "http://us.maps1.yimg.com/us.tile.maps.yimg.com/tl?v=4.1&md=2&x={X}&y={Y}&z={Z}&r=1";
		this.projection = new MercatorProjection(26, new Transformation(1.068070779e7, 0, 3.355443185e7, 0, -1.068070890e7, 3.355443057e7));
		this.minZoom = 1;
		this.maxZoom = 16;
		this.tileSize = 256;
		this.locationCoordinate = function(location){
			return this.projection.locationCoordinate(location);
		}
		this.assembleUrl = function(xIndex, yIndex, zoom){
			return westendRoadMapProvider.prototype.assembleUrl.call(this, xIndex, (Math.pow(2, zoom) /2) - yIndex - 1, zoom+1);
		}
	}
	yahooRoadMapProvider.prototype = new mapProvider;
	
	function Location(lat, lon){
		// Class representing a LAT/LON pair
		this.lat = lat;
		this.lon = lon;
		
		this.toString = function(){
			return "(" + this.lat + "," + this.lon + ")";
		}
		this.distanceFrom = function(location){
			// Calculate this' distance from another Location object
			var lat1 = this.lat, lon1 = this.lon, lat2 = location.lat, lon2 = location.lon;
			var R = 6371; // km
			var dLat = (lat2-lat1).toRad();
			var dLon = (lon2-lon1).toRad(); 
			var a = Math.sin(dLat/2) * Math.sin(dLat/2) +
					Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * 
					Math.sin(dLon/2) * Math.sin(dLon/2); 
			var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
			return R * c;
		}
	}
	Location.toLocation = function(object){
		// Create a Location type object from an Object
		return new Location(object.lat, object.lon);
	}
	
	function Coordinate(row, column, zoom){
		// Class representing a row/column pair and a zoom value
		this.row = row;
		this.column = column;
		this.zoom = zoom;
	
		this.toString = function(){
			return '(' + this.row + ',' + this.column + ' @' + this.zoom + ')';
		}
	
		this.copy = function(){
			return new Coordinate(this.row, this.column, this.zoom);
		}
	
	
		this.container = function(){
			/* Return a new coordinate that corresponds to that of the tile containing this one */
			return new Coordinate(Math.floor(this.row), Math.floor(this.column), this.zoom);
		}
	
		this.zoomTo = function(destination){
			return new Coordinate(this.row * Math.pow(2, destination - this.zoom), this.column * Math.pow(2, destination - this.zoom), destination);
		}
		
		this.zoomBy = function(distance){
			return new Coordinate(this.row * Math.pow(2, distance), this.column * Math.pow(2, distance), this.zoom + distance);
		}
		
		this.isRowEdge = function(){
			return Math.round(this.row) == this.row;
		}
		
		this.isColumnEdge = function(){
			return Math.round(this.column) == this.column;
		}
		
		this.isEdge = function(){
			return isRowEdge() && isColumnEdge();
		}
		
		this.up = function(distance){
			return new Coordinate(this.row - (distance ? distance : 1), this.column, this.zoom);
		}
		
		this.right = function(distance){
			return new Coordinate(this.row, this.column + (distance ? distance : 1), this.zoom);
		}
		
		this.down = function(distance){
			return new Coordinate(this.row + (distance ? distance : 1), this.column, this.zoom);
		}
		
		this.left = function(distance){
			return new Coordinate(this.row, this.column - (distance ? distance : 1), this.zoom);
		}
		
		this.equalTo = function( coord){
			/* Returns true if the the two coordinates refer to the same Tile location. */
			return coord.row == this.row && coord.column == this.column && coord.zoom == this.zoom;
		}
	}
	
	function Transformation(ax, bx, cx, ay, by, cy){
		this.ax = ax;
		this.bx = bx;
		this.cx = cx;
		this.ay = ay;
		this.by = by;
		this.cy = cy;
		
		this.toString = function(){
			/* String signature of the current transformation. */
			return 'T(['+ax+','+bx+','+cx+']['+ay+','+by+','+cy+'])';
		}
		  
		this.transform = function(point){
			/* Transform a point. */
			return new Point(ax*point.x + bx*point.y + cx, ay*point.x + by*point.y + cy);
		}
		
		this.untransform = function(point){
			/* Inverse of transform; p = untransform(transform(p)) */
			return new Point((point.x*by - point.y*bx - cx*by + cy*bx) / (ax*by - ay*bx), (point.x*ay - point.y*ax - cx*ay + cy*ax) / (bx*ay - by*ax));
		}
	}
	
	function AbstractProjection(zoom, T){
		if(T)
			this.T = T;
		this.zoom = zoom; // required native zoom for which transformation above is valid.
	
		this.toString = function(){
			/* String signature of the current projection. */
			throw("Abstract method not implemented by subclass.");
			return null;
		}
		this.rawProject = function(point){
			/* Return raw projected point. */ 
			throw("Abstract method not implemented by subclass.");
			return null;
		}
		this.rawUnproject = function(point){
			/* Return raw unprojected point. */ 
			throw("Abstract method not implemented by subclass.");
			return null;
		}
		this.project = function(point){
			/* Return projected and transformed point. */ 
			point = this.rawProject(point);
			if(this.T)
				point = this.T.transform(point);
			return point;
		}
		this.unproject = function(point){
			/* Return unprojected and untransformed point. */ 
			if(this.T)
				point = this.T.untransform(point);
			point = this.rawUnproject(point);
			return point;
		}
		this.locationCoordinate = function(location){
			/* Return projected and transformed coordinate for a location. */ 
			var point = new Point(Math.PI*location.lon/180, Math.PI*location.lat/180);
			point = this.project(point);
			return new Coordinate(Math.round(point.y), Math.round(point.x), zoom);
		}
		this.coordinateLocation = function(coordinate){
			/* Return untransformed and unprojected location for a coordinate. */
			//alert(coordinate); 
			coordinate = coordinate.zoomTo(this.zoom);
			var point = new Point(coordinate.column, coordinate.row);
			point = this.unproject(point);
			return new Location(180*point.y/Math.PI, 180*point.x/Math.PI);
		}
	} 
	
	function MercatorProjection(zoom, T){
		MercatorProjection.prototype = AbstractProjection.call(this);
		AbstractProjection.apply(this, [zoom, T]); // apply this projection's parameters to this projection's prototype
		this.toString = function(){
			/* String signature of the current projection. */ 
			return 'Mercator('+zoom+', '+T.toString()+')';
		}
		this.rawProject = function(point){
			/* Return raw projected point. See http://mathworld.wolfram.com/MercatorProjection.html*/ 
			return new Point(point.x, Math.log(Math.tan(0.25 * Math.PI + 0.5 * point.y)));
		}
		this.rawUnproject = function(point){
			/* Return raw unprojected point. See http://mathworld.wolfram.com/MercatorProjection.html*/ 
			return new Point(point.x, 2 * Math.atan(Math.pow(Math.E, point.y)) - 0.5 * Math.PI);
		}
	}
	
	function TileViewer(viewer, options) {
		if(viewer){
			EventDispatcher.call(this);
			TileViewer.INSTANCES[TileViewer.INSTANCES.length] = this;
			this.viewer = $(viewer);
			//this.name = viewer;
			this.initialized = false;
			//alert("TileViewer: viewer.offsetWidth = " + this.viewer.offsetWidth + ", viewer.offsetHeight = " + this.viewer.offsetHeight);
			//alert("Tileviewer dimensions: " + this.viewer.style.width + ", " + this.viewer.style.height);
			this.width = parseInt(Element.getCurrentStyleAttribute(this.viewer, "width"));	// set the control's dimensions to it's container's dimensions
			this.height = parseInt(Element.getCurrentStyleAttribute(this.viewer, "height"));
			/*if(this.width==0 || this.height==0){
				alert("Check map dimensions");
			}*/
			//alert("Tileviewer dimensions: " + this.width + ", " + this.height);
			this.border = -1;
			this.lastClick = 0;
			if (document.attachEvent) {
				document.body.ondragstart = function() { return false; }
			}
			WUtility.applyAttributes(this.viewer, {"class":TileViewer.CLASS_VIEWER});
			this.well = this.viewer.appendChild(WUtility.createElement("DIV", {"class":TileViewer.CLASS_WELL})); // create well
			if(options.crosshair){
				this.crosshair = this.viewer.appendChild(WUtility.createElement("IMG", {"class":TileViewer.CLASS_CROSSHAIR, src:TileViewer.CROSSHAIR}, {top:((this.height/2-3)+"px"), left:((this.width/2-3)+"px")}));
			}
			this.surface = this.viewer.appendChild(WUtility.createElement("DIV", {"class":TileViewer.CLASS_SURFACE}, {opacity:0, filter:"alpha(opacity=0)", backgroundColor:"#FF0000"})); // create a surface to drag and click
			this.viewer.backingBean = this.well.backingBean = this.surface.backingBean = this; // add a backing to these newly created elemenets
			if(options.mapProvider){ // set the map provider
				this.mapProvider = options.mapProvider;
			}else{
				alert("No provider.");
				return;
			}
			this.zoomLevel = Math.min(this.mapProvider.maxZoom, Math.max(this.mapProvider.minZoom, options.zoom)); // set initial zoomlevel
			var fullSize = this.mapProvider.tileSize; // set initial zoom, or, if not provided, calculate a best fit
			if (this.zoomLevel >= this.mapProvider.minZoom && this.zoomLevel <= this.mapProvider.maxZoom) {
				fullSize = this.mapProvider.tileSize * Math.pow(2, this.zoomLevel);
			}else{ // calculate best fit zoom level
				this.zoomLevel = -1;
				fullSize = this.mapProvider.tileSize / 2;
				do {
					this.zoomLevel += 1;
					fullSize *= 2;
				} while (fullSize < Math.max(this.width, this.height));
			}
			this.x = -Math.floor((fullSize - this.width) / 2); // center the big picture
			this.y = -Math.floor((fullSize - this.height) / 2);
			offset = Element.getOffset(this.viewer); // calc viewer offset in window
			this.top = offset.top; this.left = offset.left;
			this.mark = { 'x' : 0, 'y' : 0 }; // used for movement reference
			this.tiles = []; // will hold tile references
			//this.markers = []; // holds all the markers;
			this.pressed = false; // mouse press monitor
			//this.surface.style.cursor = TileViewer.GRAB_MOUSE_CURSOR;
			this.cache = {};
			this.cache['blank'] = new Image();
			this.cache['blank'].src = this.cache['blank'].rsrc = TileViewer.TILE_BLANK;
			this.prepareTiles(); // create the necessary tiles
			this.initialized = true; // mark this initialized
	
			//this.center = options.location || new Location(0, 0); // set the initial location
			//this.centerTo(this.center, this.zoomLevel); // set a center
			//this.Dispatch(TileViewer.EVENT_INIT, {bean: this}); // holler that it's inited
		}
	}
	TileViewer.INSTANCES = []; // all viewers
	TileViewer.CLASS_SURFACE = 'tileViewerSurface'; // css classes
	TileViewer.CLASS_WELL = 'tileViewerWell';
	TileViewer.CLASS_TILE = 'tileViewerTile';
	TileViewer.CLASS_VIEWER = "tileViewerBean";
	TileViewer.CLASS_CROSSHAIR = "tileViewerCrosshair";
	
	TileViewer.MSG_BEYOND_MIN_ZOOM = 'Ai ajuns la nivelul minim de marire.'; // language settings
	TileViewer.MSG_BEYOND_MAX_ZOOM = 'Ai ajuns la nivelul maxim de marire.';
	//TileViewer.USE_SLIDE = false; // defaults if not provided as constructor options
	TileViewer.USE_KEYBOARD = true;
	TileViewer.CLICK_SPEED = 100;
	TileViewer.TILE_BLANK = "/imagini/mapdata/blank.gif"; // blank tile
	TileViewer.CROSSHAIR = "/imagini/map/crosshair.gif"; // crosshair
	TileViewer.MOVE_THROTTLE = 3;// performance tuning variables
	//TileViewer.DOM_ONLOAD = (navigator.userAgent.indexOf('KHTML') >= 0 ? false : true); 
	TileViewer.GRAB_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'pointer' : (document.attachEvent ? 'url(grab.cur)' : '-moz-grab')); // some calculated settings
	TileViewer.GRABBING_MOUSE_CURSOR = (navigator.userAgent.search(/KHTML|Opera/i) >= 0 ? 'move' : (document.attachEvent ? 'url(grabbing.cur)' : '-moz-grabbing'));
	//TileViewer.EVENT_INIT = "Viewer initialized";
	TileViewer.EVENT_ZOOM = "Viewer zoomed";
	TileViewer.EVENT_DRAG = "Viewer dragged";
	TileViewer.EVENT_CLICK = "Viewer clicked";
	TileViewer.EVENT_DCLICK = "Viewer doubleclicked";
	TileViewer.EVENT_DRAGGING = "Viewer dragging";
	TileViewer.prototype = new EventDispatcher;
	TileViewer.prototype.fitToWindow = function(border) {
		//Resize the viewer to fit snug inside the browser window (or frame),
		//spacing it from the edges by the specified border.
		//This method should be called prior to init()
	
		if (typeof border != 'number' || border < 0) {
			border = 0;
		}
	
		this.border = border;
		var calcWidth = 0;
		var calcHeight = 0;
		if (window.innerWidth) {
			calcWidth = window.innerWidth;
			calcHeight = window.innerHeight;
		}
		else {
			calcWidth = (document.compatMode == 'CSS1Compat' ? document.documentElement.clientWidth : document.body.clientWidth);
			calcHeight = (document.compatMode == 'CSS1Compat' ? document.documentElement.clientHeight : document.body.clientHeight);
		}
		
		calcWidth = Math.max(calcWidth - 2 * border, 0);
		calcHeight = Math.max(calcHeight - 2 * border, 0);
		if (calcWidth % 2) {
			calcWidth--;
		}
	
		if (calcHeight % 2) {
			calcHeight--;
		}
	
		this.width = calcWidth;
		this.height = calcHeight;
		this.viewer.style.width = this.width + 'px';
		this.viewer.style.height = this.height + 'px';
		this.viewer.style.top = border + 'px';
		this.viewer.style.left = border + 'px';
	}
	TileViewer.prototype.prepareTiles = function() {
		// create the well and tiles
		var rows = Math.ceil(this.height / this.mapProvider.tileSize) + 1;
		var cols = Math.ceil(this.width / this.mapProvider.tileSize) + 1;
	
		for (var c = 0; c < cols; c++) {
			var tileCol = [];
			for (var r = 0; r < rows; r++) {
				//var tile = this.createTile(new Coordinate(r, c, this.zoomLevel));
				//tileCol.push(this.well.appendChild(tile));
				var tile = {
					'element' : null,
					'posx' : 0,
					'posy' : 0,
					'coord' : new Coordinate(r, c, this.zoomLevel)
				};
				tileCol.push(tile);
			}
			this.tiles.push(tileCol);
		}
	
		this.surface.onmousedown = TileViewer.mousePressedHandler;
		this.surface.onmouseup = this.surface.onmouseout = TileViewer.mouseReleasedHandler;
		this.surface.ondblclick = TileViewer.doubleClickHandler;
		if (TileViewer.USE_KEYBOARD) {
			window.onkeypress = TileViewer.keyboardMoveHandler;
			window.onkeydown = TileViewer.keyboardZoomHandler;
		}
		this.positionTiles();
	}
	TileViewer.prototype.positionTiles = function(motion, reset) {
		//reposition the tiles, taking into consideration the motion the well is under
		if (!motion){
			motion = { 'x' : 0, 'y' : 0 };
		}
		for (var c = 0; c < this.tiles.length; c++) {
			for (var r = 0; r < this.tiles[c].length; r++) {
				var tile = this.tiles[c][r];
				tile.posX = (tile.coord.column * this.mapProvider.tileSize) + this.x + motion.x;
				tile.posY = (tile.coord.row * this.mapProvider.tileSize) + this.y + motion.y;
				var visible = true;
				if (tile.posX > this.width) { // if tile moved out on right, coming in from left
					tile.coord.column -= Math.ceil((tile.posX - this.width) / (this.mapProvider.tileSize * this.tiles.length)) * this.tiles.length;
					tile.posX = (tile.coord.column * this.mapProvider.tileSize) + this.x + motion.x;
					if (tile.posX + this.mapProvider.tileSize < 0) {
						visible = false;
					}
				} else { // if moved out on left, coming in on right
					tile.coord.column += Math.ceil((tile.posX + this.mapProvider.tileSize)  / (-this.mapProvider.tileSize * this.tiles.length)) * this.tiles.length;
					tile.posX = (tile.coord.column * this.mapProvider.tileSize) + this.x + motion.x;
					if (tile.posX > this.width) {
						visible = false;
					}
				}
				if (tile.posY > this.height) { // if tile moved out on bottom, coming in on top
					tile.coord.row -= Math.ceil((tile.posY - this.height) / (this.mapProvider.tileSize * this.tiles[c].length)) * this.tiles[c].length;
					tile.posY = (tile.coord.row * this.mapProvider.tileSize) + this.y + motion.y;
					if (tile.posY + this.mapProvider.tileSize < 0) {
						visible = false;
					}
				} else { // if moved out on top, coming in on the bottom
					tile.coord.row += Math.ceil((tile.posY + this.mapProvider.tileSize) / (-this.mapProvider.tileSize * this.tiles[c].length)) * this.tiles[c].length;
					tile.posY = (tile.coord.row * this.mapProvider.tileSize) + this.y + motion.y;
					if (tile.posY > this.height) {
						visible = false;
					}
				}
				tile.coord.zoom = this.zoomLevel;
				// initialize the image object for this quadrant
				/*if(!this.initialized){
					Debug("not initialized");
					this.assignTileImage(tile, true);
					tile.element.style.top = (tile.posY) + 'px';
					tile.element.style.left = (tile.posX) + 'px';
				}*/
				if (visible || !this.initialized) {
					this.assignTileImage(tile, visible);
				}
				// moving the tile
				tile.element.style.top = (tile.posY) + 'px';
				tile.element.style.left = (tile.posX) + 'px';
			}
		}
	
		// reset the x, y coordinates of the viewer according to motion
		if (reset) {
			this.x += motion.x;
			this.y += motion.y;
		}
		this.Dispatch(TileViewer.EVENT_DRAGGING, {motion:new Point(motion.x, motion.y)});
	}
	TileViewer.prototype.assignTileImage = function(tile, visible) {
			var tileImgId, src, oldtile;
			// check if image has been scrolled too far in any particular direction
			// and if so, use the null tile image
			if (!visible || this.coordExceedsBounds(tile.coord)) {
				tileImgId = 'blank:' + tile.coord.column + ':' + tile.coord.row;
				src = this.cache['blank'].rsrc;
				//Debug(src);
			}else{
				tileImgId = src = this.mapProvider.assembleUrl(tile.coord.column, tile.coord.row, this.zoomLevel);
			}
			// only remove tile if identity is changing
			//Debug(tile.element.rsrc + ", " + src);
			if (tile.element != null &&
				tile.element.parentNode != null &&
				tile.element.rsrc != src) {
				//Debug(tile.element.rsrc + ", " + src);
				//Hide(tile.element);
				this.well.removeChild(tile.element);
				
			}
	
			var tileImg = this.cache[tileImgId];
			// create cache if not exist
			if (tileImg == null) {
				tileImg = this.cache[tileImgId] = this.createTile(tile.coord.column, tile.coord.row, this.zoomLevel);
			}
	
			//tileImg.onload = function() {};
			/*if (tileImg.image) {
				tileImg.image.onload = function() {};
			}*/
	
			if (tileImg.parentNode == null) {
				tile.element = this.well.appendChild(tileImg);
			}
			
		},
	
		TileViewer.prototype.createTile = function(col, row, zoom) {
			var src = this.mapProvider.assembleUrl(col, row, zoom);
			var src404 = this.mapProvider.assembleUrl404(col, row, zoom);
			var img = WUtility.createElement('img', {"class":TileViewer.CLASS_TILE}, {width:this.mapProvider.tileSize + 'px', height:this.mapProvider.tileSize + 'px', display:"none"});
			//img.oldOnload = img.onload;
			img.onload = TileViewer.tileFadeIn;
			img.src = img.rsrc = src;
			img.src404 = src404;
			Scheduler.callLater(this, 0, TileViewer.tileSetSrc,	img, src);
			img.onerror = function(){
				this.src = this.src404;
			}
			return img;
		}
		TileViewer.tileSetSrc = function(img, src){
			img.src = img.rsrc = src;
		}
		TileViewer.tileFadeIn = function(){
			Show(this);
		}
	TileViewer.prototype.zoom = function(direction) {
		// ensure we are not zooming out of range
		if (this.zoomLevel + direction < this.mapProvider.minZoom) {
			//alert(TileViewer.MSG_BEYOND_MIN_ZOOM);
			//return;
			direction = -(this.zoomLevel - this.mapProvider.minZoom);
		}
		else if (this.zoomLevel + direction > this.mapProvider.maxZoom) {
			//alert(TileViewer.MSG_BEYOND_MAX_ZOOM);
			//return;
			direction = this.mapProvider.maxZoom - this.zoomLevel;
		}
		if(direction==0)
			return;
		var coords = { 'x' : this.width / 2, 'y' : this.height / 2 };
		//Debug("Center: x = " + coords.x + ", y = " + coords.y);
		var before = {
			'x' : (coords.x - this.x),
			'y' : (coords.y - this.y)
		};
		//Debug("Before: x = " + before.x + ", y = " + before.y);
		var after = {
			'x' : before.x * Math.pow(2, direction),
			'y' : before.y * Math.pow(2, direction)
		};
		//Debug("Center: x = " + after.x + ", y = " + after.y);
		this.x = Math.floor(coords.x - after.x);
		this.y = Math.floor(coords.y - after.y);
		var oldZoom = this.zoomLevel;
		this.zoomLevel += direction;
		this.positionTiles();
		this.Dispatch(TileViewer.EVENT_ZOOM, {bean: this, zoomFrom: oldZoom, zoomTo: this.zoomLevel});
	}
	TileViewer.prototype.clear = function() {
		/** 
		 * Clear all the tiles from the well for a complete reinitialization of the
		 * viewer. At this point the viewer is not considered to be initialized.
		 */	
		this.blank();
		this.initialized = false;
		this.tiles = [];
	}
	TileViewer.prototype.blank = function() {
		for (var c = 0; c < this.tiles.length; c++)
			for (var r = 0; r < this.tiles[c].length; r++){
				var tile = this.tiles[c][r];
				//this.well.removeChild(tile);
			}
	}
	TileViewer.prototype.moveViewer = function(coords) {
		// Method specifically for handling a mouse move event.  A direct
		//movement of the viewer can be achieved by calling positionTiles() directly.
		//Debug("MoveViewer: " + coords.x + ", " + coords.y + ", mark = " + this.mark.x + ", " + this.mark.y);
		this.positionTiles({ 'x' : (coords.x - this.mark.x), 'y' : (coords.y - this.mark.y) });
		//this.Dispatch(TileViewer.EVENT_DRAGGING, {motion:new Point(this.mark.x, this.mark.y)});
	}
	TileViewer.prototype.recenter = function(coords, absolute) {
		// Recenter well on `coords`. The calculation considers the distance between the center
		// of the viewable area and the specified (viewer-relative) coordinates.
		// If absolute is specified, treat the point as relative to the entire
		// image, rather than only the viewable portion.
		if (absolute) {
			coords.x += this.x;
			coords.y += this.y;
		}
	
		var motion = {
			'x' : Math.floor((this.width / 2) - coords.x),
			'y' : Math.floor((this.height / 2) - coords.y)
		};
		this.positionTiles(motion, true);
		return;
	}
	TileViewer.prototype.resize = function() {
		// IE fires a premature resize event
		if (!this.initialized) {
			return;
		}
	
		this.viewer.style.display = 'none';
		this.clear();
	
		var before = {
			'x' : Math.floor(this.width / 2),
			'y' : Math.floor(this.height / 2)
		};
	
		if (this.border >= 0) {
			this.fitToWindow(this.border);
		}
	
		this.prepareTiles();
	
		var after = {
			'x' : Math.floor(this.width / 2),
			'y' : Math.floor(this.height / 2)
		};
	
		this.x += (after.x - before.x);
		this.y += (after.y - before.y);
		this.positionTiles();
		this.viewer.style.display = '';
		this.initialized = true;
		//this.notifyViewerMoved();
	}
	TileViewer.prototype.eventToGlobal = function(e){
		var offset = Element.getOffset(this.viewer, true); // calc viewer offset in window, true stands for borders (firefox doesn't calculate borders)
		var eoffset = Event.mouse(e);
		var p =  new Point(
			eoffset.x - offset.left,
			eoffset.y - offset.top
		);
		return p;
	}
	
	TileViewer.prototype.globalToLocal = function(point){
		return new Point(point.x + this.x, point.y + this.y);
	}
	TileViewer.prototype.localToGlobal = function(point){
		return new point(point.x - this.x, point.y-this.y);
	}
	TileViewer.prototype.getCenterZoom = function(){
		return {center: new Point(Math.round(this.width / 2), Math.round(this.height / 2)), zoom: this.zoomLevel};
	}
	TileViewer.prototype.getBounds = function(){
		return new Rectangle(new Point(0, 0), new Point(this.width-1, this.height-1));
	}
	TileViewer.prototype.dclick = function(point){
		if (!this.pointExceedsBoundaries(point)) {
			this.recenter(point);
			this.Dispatch(TileViewer.EVENT_DCLICK, {bean:this, point:new Point(this.width / 2, this.height/2)});
			//Debug("Doubleclick2: " + point);
		}
	}
	TileViewer.prototype.coordExceedsBounds = function(coord){
		if(coord.column >=0 && coord.column<Math.pow(2, coord.zoom) && coord.row >=0 && coord.row<Math.pow(2, coord.zoom))
			return false;
		else
			return true;
	}
	
	TileViewer.prototype.press = function(coords) {
		this.pressed = true;
		Scheduler.callLater(this, TileViewer.CLICK_SPEED, this.drag, true);
		this.mark = coords;
	}
	TileViewer.prototype.release = function(point) {
		this.pressed = false;
		if(Scheduler.isPending(this.drag)){
			Scheduler.cancel(this.drag);
			this.Dispatch(TileViewer.EVENT_CLICK, {bean:this, point:point})
		}else{
			//Debug("Drag");
			var motion = {
				'x' : (point.x - this.mark.x),
				'y' : (point.y - this.mark.y)
			};
			this.x += motion.x;
			this.y += motion.y;
			this.drag(false);
			this.mark = { 'x' : 0, 'y' : 0 };
			this.Dispatch(TileViewer.EVENT_DRAG, {bean:this, point:new Point(this.width / 2, this.height / 2)})
		}
	}
	TileViewer.prototype.drag = function(pressed) {
		// Move the viewer if mouse is pressed, otherwise restore cursor and remove move handler
		this.surface.style.cursor = (pressed ? TileViewer.GRABBING_MOUSE_CURSOR : "");
		this.surface.onmousemove = (pressed ? TileViewer.mouseMovedHandler : function() {});
	}
	TileViewer.prototype.pointExceedsBoundaries = function(coords) {
		// Check whether the specified point exceeds the boundaries of the viewer's primary image.
		return (coords.x < this.x ||
			coords.y < this.y ||
			coords.x > (this.mapProvider.tileSize * Math.pow(2, this.zoomLevel) + this.x) ||
			coords.y > (this.mapProvider.tileSize * Math.pow(2, this.zoomLevel) + this.y));
	}
	TileViewer.mousePressedHandler = function(e) {
		e = e ? e : window.event;
		// only grab on left-click
		if (e.button < 2) {
			var self = this.backingBean;
			var coords = self.eventToGlobal(e);
			if (self.pointExceedsBoundaries(coords)) {
				e.cancelBubble = true;
			}
			else {
				self.press(coords);
			}
		}
	
		// NOTE: MANDATORY! must return false so event does not propagate to well!
		return false;
	}
	TileViewer.mouseReleasedHandler = function(e) {
		e = e ? e : window.event;
		var self = this.backingBean;
		if (self.pressed) {
			// OPTION: could decide to move viewer only on release, right here
			//Debug("Click1: " + self.eventToGlobal(e));
			self.release(self.eventToGlobal(e));
		}
	}
	TileViewer.doubleClickHandler = function(e) {
		e = e ? e : window.event;
		var self = this.backingBean;
		//Debug("Doubleclick1: " + self.eventToGlobal(e));
		self.dclick(self.eventToGlobal(e));
	}
	TileViewer.mouseMovedHandler = function(e) {
		e = e ? e : window.event;
		var self = this.backingBean;
		self.moveViewer(self.eventToGlobal(e));
	}
	TileViewer.zoomInHandler = function(e) {
		e = e ? e : window.event;
		var self = this.parentNode.parentNode.backingBean;
		self.zoom(1);
		return false;
	}
	TileViewer.zoomOutHandler = function(e) {
		e = e ? e : window.event;
		var self = this.parentNode.parentNode.backingBean;
		self.zoom(-1);
		return false;
	}
	TileViewer.keyboardMoveHandler = function(e) {
		e = e ? e : window.event;
		for (var i = 0; i < TileViewer.INSTANCES.length; i++) {
			var viewer = TileViewer.INSTANCES[i];
			if (e.keyCode == 38)
					viewer.positionTiles({'x': 0,'y': -TileViewer.MOVE_THROTTLE}, true);
			if (e.keyCode == 39)
					viewer.positionTiles({'x': -TileViewer.MOVE_THROTTLE,'y': 0}, true);
			if (e.keyCode == 40)
					viewer.positionTiles({'x': 0,'y': TileViewer.MOVE_THROTTLE}, true);
			if (e.keyCode == 37)
					viewer.positionTiles({'x': TileViewer.MOVE_THROTTLE,'y': 0}, true);
		}
	}
	TileViewer.keyboardZoomHandler = function(e) {
		e = e ? e : window.event;
		for (var i = 0; i < TileViewer.INSTANCES.length; i++) {
			var viewer = TileViewer.INSTANCES[i];
			if (e.keyCode == 109)
					viewer.zoom(-1);
			if (e.keyCode == 107)
					viewer.zoom(1);
		}
	}
	
	function Map(container, options){
		this.name = container;
		TileViewer.call(this, container, options);
		this.addEventObserver(this, TileViewer.EVENT_CLICK, "mapClick");
		this.addEventObserver(this, TileViewer.EVENT_DRAG, "mapDrag");
		this.addEventObserver(this, TileViewer.EVENT_ZOOM, "mapZoom");
		this.addEventObserver(this, TileViewer.EVENT_DCLICK, "mapDClick");
		this.addEventObserver(this, TileViewer.EVENT_DRAGGING, "mapDragging");
		Map.INSTANCES[Map.INSTANCES.length] = this;
	}
	Map.prototype = new TileViewer;
	Map.INSTANCES = [];
	Map.prototype.mapDClick = function(e){
		//Debug("Map " + e.bean.name + " doubleclicked on " + e.point + " which translates to " + this.pointLocation(e.point));
		this.Dispatch(Map.EVENT_DCLICK, {bean:this, location:this.pointLocation(e.point), point:e.point}); // calculate location and redispatch
	}
	Map.prototype.mapClick = function(e){
		//Debug("Map " + e.bean.name + " clicked on " + e.point + " which translates to " + this.pointLocation(e.point));
		this.Dispatch(Map.EVENT_CLICK, {bean:this, location:this.pointLocation(e.point), point:e.point}); // calculate location and redispatch
	}
	Map.prototype.mapDrag = function(e){
		//alert("Map.mapDrag()");
		this.Dispatch(Map.EVENT_DRAG, {bean:this, location:this.pointLocation(e.point), point:e.point}); // calculate location and redispatch
	}
	Map.prototype.mapDragging = function(e){
		//alert("Map.mapDrag()");
		//Debug("Map being dragged, motion: " + e.motion);
		this.Dispatch(Map.EVENT_DRAGGING, {bean:this, motion:e.motion}); // calculate location and redispatch
	}
	
	Map.prototype.mapZoom = function(e){
		this.Dispatch(Map.EVENT_ZOOM, {bean:this, zoomFrom:e.zoomFrom, zoomTo:e.zoomTo}); // calculate location and redispatch
	}
	Map.prototype.pointCoordinate = function(point){
		var tile = this.tiles[0][0];
		var tileCoord = tile.coord.zoomTo(this.mapProvider.maxZoom);
		
		var xTiles = (point.x - tile.posX) / this.mapProvider.tileSize;
		var yTiles = (point.y - tile.posY) / this.mapProvider.tileSize;
	
		// distance in rows & columns at maximum zoom
		var xDistance = xTiles * Math.pow(2, (this.mapProvider.maxZoom - tile.coord.zoom));
		var yDistance = yTiles * Math.pow(2, (this.mapProvider.maxZoom - tile.coord.zoom));
		var pointCoord = new Coordinate(tileCoord.row + yDistance,
									tileCoord.column + xDistance,
									tileCoord.zoom);
		return pointCoord.zoomTo(tile.coord.zoom);
	}
	Map.prototype.coordinatePoint = function(coord){
		// pick a reference tile, an arbitrary choice
		// but known to exist regardless of grid size.
		var tile = this.tiles[0][0];
		// get the position of the reference tile.
		var point = new Point(tile.posX, tile.posY);
		// make sure coord is using the same zoom level
		coord = coord.zoomTo(tile.coord.zoom);
		point.x += this.mapProvider.tileSize * (coord.column - tile.coord.column);
		point.y += this.mapProvider.tileSize * (coord.row - tile.coord.row);
		return point;
	}
	Map.prototype.pointLocation = function(point){
		return this.mapProvider.projection.coordinateLocation(this.pointCoordinate(point));
	}
	Map.prototype.locationPoint = function(location){
		return this.coordinatePoint(this.mapProvider.projection.locationCoordinate(location));
	}
	Map.prototype.centerTo = function(location, zoom){
		zoom = zoom || this.zoomLevel;
		//Debug("setCenterZoom: " + location + " @ " + zoom);
		this.zoom(zoom - this.zoomLevel);
		var coord = this.mapProvider.projection.locationCoordinate(location).zoomTo(this.zoomLevel);
		this.recenter(new Point(coord.column * this.mapProvider.tileSize, coord.row * this.mapProvider.tileSize), true);
	}
	Map.prototype.getCenterZoom = function(coord){
		var c = TileViewer.prototype.getCenterZoom.call(this);
		return {center: this.pointLocation(c.center), zoom:c.zoom};
	}
	Map.prototype.getBounds = function(){
		var bounds = TileViewer.prototype.getBounds.call(this);
		return {tl:this.pointLocation(bounds.tl), br:this.pointLocation(bounds.br)};
	}
	Map.prototype.boundsToRectangle = function(bounds){
		return {tl: this.locationPoint(bounds.tl), br: this.locationPoint(bounds.br)};
	}
	Map.EVENT_INIT = "Map initialized";
	Map.EVENT_CLICK = "Map clicked";
	Map.EVENT_DRAG = "Map dragged";
	Map.EVENT_ZOOM = "Map zoomed";
	Map.EVENT_DCLICK = "Map doubleclicked";
	Map.EVENT_DRAGGING = "Map dragging";
	Map.EVENT_CLEAR = "Map clearing"
	
	function WMap(container, options){
		EventDispatcher.call(this);
		this.container = $(container);
		this.container.backingBean = this;
		options.zoom = options.zoom || 16;
		options.location = options.location || new Location(46.781185, 23.587196);
		this.zoomLevels = [[], [1,"1",[0.1,0.0473484848]],[2,"strazi",[0.125,0.1]],[3,"3",[0.25,0.2]],[4,"oras",[0.5,0.5]],[5,"5",[1,1]],[6,"6",[2,2]],[7,"7",[5,3]],[8,"tara",[10,7]],[9,"9",[20,15]],[10,"10",[30,25]],[11,"11",[75,50]],[12,"12",[150,100]],[13,"13",[300,200]],[14,"14",[600,400]],[15,"15",[1000,750]],[16,"continent",[2000,1500]], [17,"17",[5000,3000]]]
		this.options = options;
		WMap.INSTANCES[WMap.INSTANCES.length] = this;
	}
	WMap.prototype = new EventDispatcher;
	WMap.prototype.init = function(){
		WUtility.applyStyle(this.container, {"position":"relative", "overflow":"hidden"});
		this.clearer = this.container.appendChild(WUtility.createElement("INPUT", {"type":"text", "name":this.container.getAttribute("ID")+"_clear"}, {display:"none"}));
		this.clearer.onchange = Delegate.create(this, function(){ this.Dispatch(Map.EVENT_CLEAR, {bean:this}); });
		this.markerWell = this.container.appendChild(WUtility.createElement("DIV", {"class":WMap.CLASS_MARKERWELL}, {}));
		var w = this.width = parseInt(Element.getCurrentStyleAttribute(this.container, "width"));
		var h = this.height = parseInt(Element.getCurrentStyleAttribute(this.container, "height"));
		//alert("WMap.init(): dimensions = [" + w + " x " + h + "]" );
		var mainmc = this.container.appendChild(WUtility.createElement("DIV", {"class":WMap.CLASS_MAINMAP}, {position:"absolute", width:w+"px", height:h+"px"}));
		this.mainMap = mainmc.appendChild(WUtility.createElement("DIV", {}, {width:w+"px", height:h+"px"}));
		new Map(this.mainMap, {mapProvider:this.options.mapProvider, zoom:this.options.zoom, location:this.options.location, crosshair:this.options.crosshair});
		if(Validator.isArray(this.options.controls)){
			if(this.options.controls.seqSearch(WMap.CONTROL_MINIMAP)>-1){
				var minimc = this.container.appendChild(WUtility.createElement("DIV", {"class":WMap.CLASS_MINIMAP}, {width:"100px", height:"76px", bottom:"2px", left:"2px"})); // note that we must add twice the border size to the dimensions, in this case the border is 1 on all sides, so we add 2
				this.miniMap = minimc.appendChild(WUtility.createElement("DIV", {}, {width:"100px", height:"76px"}));
				new Map(this.miniMap, {mapProvider:this.options.mapProvider, zoom:this.options.zoom - WMap.MINIMAP_DIFF, crosshair:false, location:this.options.location});
				var bounds = this.mainMap.backingBean.getBounds();
				bounds = this.miniMap.backingBean.boundsToRectangle(bounds);
				this.miniMap.mapBounds = this.miniMap.appendChild(WUtility.createElement("DIV", {"class":WMap.CLASS_BOUNDS}, {top:(bounds.tl.y)+"px", left:(bounds.tl.x)+"px", width:(bounds.br.x-bounds.tl.x)+"px", height:(bounds.br.y-bounds.tl.y)+"px"}))
				this.mainMap.backingBean.addEventObserver(this, Map.EVENT_DRAG, "updateMinimap");
				this.mainMap.backingBean.addEventObserver(this, Map.EVENT_DCLICK, "updateMinimap");
				this.miniMap.backingBean.addEventObserver(this, Map.EVENT_DRAG, "updateMainmap");
				this.miniMap.backingBean.addEventObserver(this, Map.EVENT_DCLICK, "updateMainmap");
				this.mainMap.backingBean.addEventObserver(this, Map.EVENT_ZOOM, "updateMinimap");
			}
			if(this.options.controls.seqSearch(WMap.CONTROL_ZOOM)>-1){
				this.controls = this.container.appendChild(WUtility.createElement("DIV", {"class":"controls"}, {bottom:"2px", right:"2px"}));
				this.controls.zoomIn = this.controls.appendChild(WUtility.createElement("IMG", {"src":WMap.ICON_ZOOM_IN, title:"Mareste", width:20, height:20}, {position:"absolute", cursor:"pointer", bottom:"20px", right:"0px"}));
				this.controls.reCenter = this.controls.appendChild(WUtility.createElement("IMG", {"src":WMap.ICON_RECENTER, title:"Locatia initiala", width:20, height:20}, {position:"absolute", cursor:"pointer", "float":"right", bottom:"0px", right:"0px"}));
				this.controls.zoomOut = this.controls.appendChild(WUtility.createElement("IMG", {"src":WMap.ICON_ZOOM_OUT, title:"Micsoreaza", width:20, height:20}, {position:"absolute", cursor:"pointer", bottom:"0px", right:"20px"}));
				this.controls.zoomIn.onclick = Delegate.create(this, this.zoomIn);
				this.controls.zoomOut.onclick = Delegate.create(this, this.zoomOut);
				this.controls.reCenter.onclick = Delegate.create(this, this.reCenter);
			}
			if(this.options.controls.seqSearch(WMap.CONTROL_SCALE)>-1){
				var bottom = (this.options.controls.seqSearch(WMap.CONTROL_MINIMAP)>-1) ? 88 : 2;
				this.scale = this.container.appendChild(WUtility.createElement("DIV", {}, {zIndex:30 , position:"absolute", bottom:bottom+"px", left:"2px"}));
				this.scale.left = this.scale.appendChild(WUtility.createElement("IMG", {src:"/imagini/map/scaleLeft.gif"}, {position:"absolute", bottom:"0px", left:"0px"}));
				this.scale.center = this.scale.appendChild(WUtility.createElement("IMG", {src:"/imagini/map/scaleCenter.gif"}, {position:"absolute", bottom:"0px", left:"3px"}));
				this.scale.right = this.scale.appendChild(WUtility.createElement("IMG", {src:"/imagini/map/scaleRight.gif"}, {position:"absolute", bottom:"0px", left:"13px"}));
				this.scale.label = this.scale.appendChild(WUtility.createElement("DIV", {"class":WMap.CLASS_LABEL}, {position:"absolute", bottom:"2px", left:"2px", whiteSpace:"nowrap", textAlign:"center"}));
				this.updateScaleBar();
				this.mainMap.backingBean.addEventObserver(this, Map.EVENT_ZOOM, "updateScaleBar");
			}
		}
		this.logo = this.container.appendChild(WUtility.createElement("IMG", {src:"/imagini/map/logo.png", "class":WMap.CLASS_LOGO}));
		this.mainMap.backingBean.addEventObserver(this, Map.EVENT_CLICK, "mapClick");
		this.mainMap.backingBean.addEventObserver(this, Map.EVENT_DRAGGING, "mapDragging");
		/*if(typeof(this.options.observers) == "object"){
			for(var i in this.options.observers){
				this.mainMap.backingBean.addEventObserver(this.options.observers[i].scope, i, this.options.observers[i].func);
			}
		}*/
		this.markers = new MarkerCollection();
		if(Validator.isArray(this.options.markers)){
			//alert(this.options.markers.length);
			for(var i = 0; i<this.options.markers.length; i++){
				this.putMarker(this.options.markers[i]);
			}
		}
		this.centerTo(this.options.location || new Location(0, 0), this.options.zoom); // set a center
	
		this.Dispatch(Map.EVENT_INIT, {bean:this});
	}
	WMap.prototype.updateScaleBar = function(){
		//trace("Updating scalebar.");
		var center = this.mainMap.backingBean.getCenterZoom();
		var zoom = (18 - center.zoom);
		var clat = center.center.lat;
		var M_PER_DEG = 111111;
		var RAD_PER_DEG = Math.PI / 180;
		var EARTH_CIRCUM_M = M_PER_DEG * 360;
		var CIRCUM_PX = 1<<(26 - (18 - center.zoom));
		var METERS_PER_PX = EARTH_CIRCUM_M / CIRCUM_PX;
		var mpp_m = METERS_PER_PX * Math.cos(clat * RAD_PER_DEG);
		var units = {km:mpp_m / 1000, lonppx:360 / CIRCUM_PX}
		var km = this.zoomLevels[zoom][2][0];
		var pix_per_km = Math.round(1 / units.km * km - 6);
		this.scale.label.innerHTML = (km<0.5) ? Math.ceil(km*1000)+' m': km + ' km';
		this.scale.center.style.width = pix_per_km+"px";
		this.scale.center.style.height = "5px";
		this.scale.right.style.left = (3 + pix_per_km) + "px";
		this.scale.label.style.width = (6 + pix_per_km) + "px";
	}
	WMap.prototype.updateMinimap = function(e){
		if(this.miniMap){
			var mim = this.miniMap.backingBean;
			var mam = this.mainMap.backingBean;
			if(e.zoomTo && e.zoomFrom){
				var z = e.zoomTo - WMap.MINIMAP_DIFF - mim.zoomLevel;
				mim.zoom(z);
			}
			if(e.location){
				//Debug("updateMinimap: location = " + e.location)
				mim.centerTo(e.location);
			}else{
				var l = mam.getCenterZoom();
				mim.centerTo(l.center);
			}
			var bounds = mam.getBounds();
			bounds = mim.boundsToRectangle(bounds);
			WUtility.applyStyle(this.miniMap.mapBounds, {top:(bounds.tl.y)+"px", left:(bounds.tl.x)+"px", width:(bounds.br.x-bounds.tl.x)+"px", height:(bounds.br.y-bounds.tl.y)+"px"});
			//this.updateScaleBar();
		}
	}
	WMap.prototype.centerTo = function(location, zoom){
		this.mainMap.backingBean.centerTo(location, zoom);
		this.updateMinimap({bean:this, location:location});
		//this.updateMarkers();
	}
	WMap.prototype.updateMarkers = function(){
		for (var mk = this.markerWell.firstChild; mk; mk = mk.nextSibling) {
			//Debug(mk.backingBean);
			var coords = this.mainMap.backingBean.locationPoint(mk.markerObject.location);
			WUtility.applyStyle(mk, {top:coords.y+"px", left:coords.x+"px"});
		}
	}
	WMap.prototype.mapDragging = function(e){
		//Debug(e.motion);
		this.updateMarkers();
	}
	WMap.prototype.updateMainmap = function(e){
		this.mainMap.backingBean.centerTo(e.location);
	}
	WMap.prototype.mapClick = function(e){
		this.Dispatch(Map.EVENT_CLICK, {bean:this, location:e.location, point:e.point});
	}
	WMap.prototype.zoomIn = function(){
		this.mainMap.backingBean.zoom(1);
	}
	WMap.prototype.zoomOut = function(){
		this.mainMap.backingBean.zoom(-1);
	}
	WMap.prototype.zoom = function(direction){
		this.mainMap.backingBean.zoom(direction);
	}
	WMap.prototype.zoomMarkers = function(){
		var b = this.markers.bounds();
		this.zoomBounds(b.tl, b.br, true);
	}
	WMap.prototype.zoomBounds = function(tl, br, setDefault){
		// Make given bounds visible on map.
		// setDefault: set the new zoom level and position to be the map's default starting position
		var latAvg = (tl.lat + br.lat) / 2;
		var lonAvg = (tl.lon + br.lon) / 2;
		var tw = this.width / this.options.mapProvider.tileSize - 1; // leave a margin of error of a quarter tile
		var th = this.height / this.options.mapProvider.tileSize - 1; // so all markers keep a reasonable distance from map margin
		var p = this.options.mapProvider.projection;
		var tlc = p.locationCoordinate(tl);
		var brc = p.locationCoordinate(br);
		while(Math.abs(brc.column - tlc.column) > tw || Math.abs(brc.row - tlc.row) > th){
			brc = brc.zoomBy(-1);
			tlc = tlc.zoomBy(-1);
			//Debug(tlc + " " + brc);
		}
		var newLocation = new Location(latAvg, lonAvg);
		var newZoom	= Math.min(this.options.mapProvider.maxZoom, Math.max(brc.zoom, this.options.mapProvider.minZoom));
		if(setDefault){
			this.options.location = newLocation;
			this.options.zoom = newZoom;
		}
		this.centerTo(newLocation, newZoom);
	}
	WMap.prototype.reCenter = function(){
		this.centerTo(this.options.location, this.options.zoom);
	}
	WMap.prototype.putMarker = function(mk){
		var coords = this.mainMap.backingBean.locationPoint(mk.markerObject.location);
		if(this.markers.push(mk.markerObject)){
			Hide(mk);
			//var overlaps = this.markers.overlaps(mk);
			WUtility.applyStyle(mk, {top:coords.y+"px", left:coords.x+"px", zIndex:Element.getNextHighestDepth(this.markerWell)});
			var overlapping = this.markers.getOverlapping(mk)
			if(overlapping.length > 0){
				//var overlapping = this.markers.getOverlapping(mk);
				overlapping = overlapping[0];
				overlapping.tip.tabs.addTab({"content":mk.markerObject.content}); // add current marker's contents to the contents of the overlapped one
			}else{
				this.markerWell.appendChild(mk);
				Show(mk);
				mk.markerObject.addEventObserver(this, Marker.EVENT_CLICK, "markerClick");
				mk.markerObject.addEventObserver(this, Marker.EVENT_ROLLOVER, "markerRollover");
				if(mk.markerObject.tip)
					mk.markerObject.tip.init();
			}
			return true;
		}else{
			return false;
		}
	}
	WMap.prototype.markerSendToBack = function(id){
		var mk = this.markers.exists(id).container;
		var lowest = parseInt(Element.getCurrentStyleAttribute(Element.getBottomElement(this.markerWell), "zIndex"));
		for(var node = this.markerWell.firstChild; node; node = node.nextSibling){
			var currentDepth = parseInt(Element.getCurrentStyleAttribute(node, "zIndex"));
			WUtility.applyStyle(node, {zIndex:currentDepth+1});
		}
		//Element.swapDepths(mk, lowest);
		WUtility.applyStyle(mk, {zIndex:lowest});
		//mk.markerObject.fadeOut();
		mk.markerObject.tip.hide();
	}
	WMap.prototype.removeMarker = function(id){
		var mk = this.markers.pop(id);
		if(mk!=null){
			if(mk.tip!=undefined)
				WTip.destroy(mk.tip);
			Element.remove(mk.container);
		}
	}
	WMap.prototype.removeAllMarkers = function(){
		for (var mk = this.markerWell.firstChild; mk; mk = mk.nextSibling) {
			this.removeMarker(mk.markerObject.id);
		}
	}
	WMap.prototype.markerClick = function(e){
		this.Dispatch(Marker.EVENT_CLICK, {bean:this, marker:e.marker});
	}
	WMap.prototype.markerRollover = function(e){
		Element.bringToFront(e.marker.container);
	}
	WMap.prototype.getCenterZoom = function(){
		return this.mainMap.backingBean.getCenterZoom();
	}
	WMap.initAll = function(){
		for(var i=0; i<WMap.INSTANCES.length; i++){
			WMap.INSTANCES[i].init();
		}
	}
	
	WMap.INSTANCES			= [];
	WMap.CONTROL_MINIMAP 	= "minimap";
	WMap.CONTROL_ZOOM		= "zoom";
	WMap.CONTROL_SCALE		= "scale";
	WMap.ICON_ZOOM_IN		= "/imagini/map/zoom_in.png";
	WMap.ICON_ZOOM_OUT		= "/imagini/map/zoom_out.png";
	WMap.ICON_RECENTER		= "/imagini/map/pan_center.png";
	WMap.MINIMAP_DIFF		= 4;
	WMap.CLASS_BOUNDS		= "tileViewerBounds";
	WMap.CLASS_MARKERWELL	= "WMapMarkerWell";
	WMap.CLASS_MAINMAP		= "WMapMainMap";
	WMap.CLASS_MINIMAP		= "WMapMiniMap";
	WMap.CLASS_MARKER		= "WMapMarker";
	WMap.CLASS_LABEL		= "WMapLabel";
	WMap.CLASS_LOGO			= "WMapLogo";
	
	window.addOnLoadListener(this, WMap.initAll); // init all maps
}
