function initLogging() {
		StarMap.logger = document.createElement("img");
	 	StarMap.logger.src = StarMap.baseUrl+"/Logger?init";
		StarMap.addZoomListener(
			function() {
				StarMap.logger.src = StarMap.baseUrl+"/Logger?zoom";
			}
		);
		StarMap.addDragEndListener(
			function() {
				StarMap.logger.src = StarMap.baseUrl+"/Logger?drag";
			}
		);
		StarMap.addPanListener(
			function() {
				StarMap.logger.src = StarMap.baseUrl+"/Logger?pan";
			}
		);
}

/******************************************
* CLASS: SMUtils
********************************************/
var SMUtils = {
	
	dbg: function(msg) {
		if (document.getElementById("dbg")) {
			var div = document.getElementById("dbg");
			div.innerHTML = msg + "\n"+ div.innerHTML.substr(0,10000);
		}
	},

 	leftClicked: function(e) {
		if (!e) var e = window.event;
		if (e.which) return (e.which == 1);
		else if (e.button) return (e.button == 0 || e.button == 1);
	},

	getTarget: function(e) {
		var targ;
		if (!e) var e = window.event;
		if (e.target) targ = e.target;
		else if (e.srcElement) targ = e.srcElement;
		if (targ.nodeType == 3) // defeat Safari bug
			targ = targ.parentNode;
		return targ;
	},

	addEvent: function(obj, evType, fn, userCapture) {
		if (obj.addEventListener) {
			obj.addEventListener(evType,fn,userCapture);
			return true;
		} else if (obj.attachEvent) {
			var r = obj.attachEvent('on'+evType,fn);
		} else {
			obj['on'+evType] = fn;
		}
	},
	
	mouseX: function(e) {
		var mouseX = 0;
		if (e.pageX) {
			mouseX = e.pageX;
		} else if (e.clientX) {
			mouseX = e.clientX;
			if (StarMap.isIE) {
				if (document.documentElement && document.documentElement.scrollLeft) {
					mouseX += document.documentElement.scrollLeft;
				} else if (document.body && document.body.scrollLeft) {
					mouseX += document.body.scrollLeft;
				}
			}
		}
		return mouseX;
	},
	
	mouseY: function(e) {
		var mouseY = 0;
		if (e.pageY) {
			mouseY = e.pageY;
		} else if (e.clientY) {
			mouseY = e.clientY;
			if (StarMap.isIE) {
				if (document.documentElement && document.documentElement.scrollTop) {
					mouseY += document.documentElement.scrollTop;
				} else if (document.body && document.body.scrollTop) {
					mouseY += document.body.scrollTop;
				}
			}
		}
		return mouseY;
	},
	
	topLeftX: function(obj) {
		var curLeft = 0;
		if (obj.offsetParent) {
			do {
				curLeft += obj.offsetLeft;
			} while (obj = obj.offsetParent);
		} else if (obj.x) {
			curLeft += obj.x;
		}
		return curLeft;
	},
	
	topLeftY: function(obj) {
		var curTop = 0;
		if (obj.offsetParent) {
			do {
				curTop += obj.offsetTop;
			} while (obj = obj.offsetParent);
		} else if (obj.y) {
			curTop += obj.y;
		}
		return curTop;
	},
	
	stopEvent: function(e) {
		if (e && e.stopPropagation && e.preventDefault) {
			e.stopPropagation();
			e.preventDefault();
		} else if (window.event) {
			window.event.cancelBubble = true;
			window.event.returnValue = false;		
		}
		return false;
	}

}

/*******************************************
* CLASS: StarMap
********************************************/
var StarMap = {

	updatePage: 'dummy.jsp',

	baseUrl: "http://maps.starcus.se/tkartor/",
	domain: "starcus.se",
	
	isIE: !window.opera && navigator.userAgent.indexOf('MSIE') != -1,

	mouseDownX: 0,
	mouseDownY: 0,
	
	mouseUpX: 0,
	mouseUpY: 0,
	
	cx: 0,
	cy: 0,

	dx: 0,
	dy: 0,
	
	zoomLevels: [2,4,10,25,70,200,700,3500],
	zoomIndex: 4,
	zoomListeners: [],
	
	currentZoomLevels: [2,4,10,25,70,200,700,3500],

	clickListeners: [],
	mouseDownListeners: [],
	mouseUpListeners: [],
	panListeners: [],
	dragListeners: [],
	satBoundsListeners: [],
	
	zoomChangeListeners: [],
		
	map: null,
	mapWidth: 0,
	mapHeight: 0,
	wrapper: null,
	
	dragMode: false,
	dragStartX: 0,
	dragStartY: 0,
	
	tiles: [],
	numTilesX: 0,
	numTilesY: 0,
	tileSize: 256,
	
	points: [],
	maxPoints: 20,
	
	panToX: 0,
	panToY: 0,
	isPanning: false,
	
	insideSat: false,

	polylines: [],

	startDrag: function(e) {
		if (SMUtils.leftClicked(e)) {
			StarMap.dragMode = true;
			StarMap.startDragX = SMUtils.mouseX(e);
			StarMap.startDragY = SMUtils.mouseY(e);
			SMUtils.stopEvent(e);
			StarMap.dx = 0;
			StarMap.dy = 0;
			StarMap.mouseDownX = SMUtils.mouseX(e);
			StarMap.mouseDownY = SMUtils.mouseY(e);
			StarMap.callWhenAllLoaded(StarMap.callMouseDownListeners,10);
		}
	},
	
	counter: 0,
	
	drag: function(e) {
		if (StarMap.dragMode) {
			if (StarMap.counter++ % 2 == 0) {
				return;
			}
			var dx = SMUtils.mouseX(e) - StarMap.startDragX;
			var dy = SMUtils.mouseY(e) - StarMap.startDragY;
			StarMap.dx -= dx;
			StarMap.dy += dy;
			StarMap.cx -= dx*StarMap.zoomLevels[StarMap.zoomIndex];
			StarMap.cy += dy*StarMap.zoomLevels[StarMap.zoomIndex];
			StarMap.panTiles(dx,dy);
			StarMap.panPoints(dx,dy);
			StarMap.startDragX = SMUtils.mouseX(e);
			StarMap.startDragY = SMUtils.mouseY(e);
		}
	},

	stopDrag: function(e) {
		if (StarMap.dragMode && (StarMap.dx != 0 || StarMap.dy != 0)) {
			//StarMap.resetTiles();
			StarMap.updateSettings();
			StarMap.callWhenAllLoaded(StarMap.callDragListeners,10);
		}
		StarMap.dragMode = false;
		// Don't stop event because it will break safari
		//SMUtils.stopEvent(e);
		return false;
	},
	
	mouseUp: function(e) {
		StarMap.mouseUpX = SMUtils.mouseX(e);
		StarMap.mouseUpY = SMUtils.mouseY(e);
		StarMap.callWhenAllLoaded(StarMap.callMouseUpListeners,10);
	},

	/*
	resetTiles: function() {
		if (StarMap.isIE) {
			for (var i=0;i<StarMap.tiles.length;i++) {
				StarMap.tiles[i].resetImg();
			}
		}
	},
	*/

	enableFlag: function() {
		SMPoint.flag = true;
	},
	
	disableFlag: function() {
		SMPoint.flag = false;
	},
	
	sourceSatellite: function() {
		StarMap.sat = true;
		StarMap.updateSettings();
		StarMap.repaint();
	},
	
	sourceMap: function() {
		StarMap.sat = false;
		StarMap.updateSettings();
		StarMap.repaint();
	},
	
	pan: function() {

		StarMap.isPanning = true;
		
		if (StarMap.dragMode) {
			StarMap.isPanning = false;
			return;	
		}
		
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		var dx = parseInt((StarMap.panToX-StarMap.cx)/res);
		var dy = parseInt((StarMap.panToY-StarMap.cy)/res);

		if (Math.abs(dx) > (StarMap.mapWidth/2) || Math.abs(dy) > (StarMap.mapHeight/2)) {
			StarMap.cx = StarMap.panToX;
			StarMap.cy = StarMap.panToY;
			StarMap.repaint();
			StarMap.isPanning = false;
			return;
		}
		
		var panX = Math.ceil(dx / 3);
		var panY = Math.ceil(dy / 3);

		if (Math.abs(panX) > 0 || Math.abs(panY) > 0) {
			StarMap.cx += panX*StarMap.zoomLevels[StarMap.zoomIndex];
			StarMap.cy += panY*StarMap.zoomLevels[StarMap.zoomIndex];
			StarMap.panTiles(-panX,panY);
			StarMap.panPoints(-panX,panY);
			setTimeout(StarMap.pan,50);
		} else {
			StarMap.cx = StarMap.panToX;
			StarMap.cy = StarMap.panToY;
			if (StarMap.panCallback) {
				StarMap.panCallback(StarMap.cx,StarMap.cy);	
			}
			for (i=0;i<StarMap.panListeners.length;i++) {
				StarMap.panListeners[i](StarMap.cx,StarMap.cy);
			}
			StarMap.isPanning = false;
			//StarMap.resetTiles();
			StarMap.updateSettings();
		}
	},
	
	panTo: function(x,y,fn) {
		StarMap.panToX = parseInt(x);
		StarMap.panToY = parseInt(y);
		if (!StarMap.isPanning) {
			StarMap.panCallback = fn;
			StarMap.pan();
		}	
		return false;
	},
	
	panAndZoom: function(x,y,zoomLevel) {
		return StarMap.panTo(x,y,new Function("StarMap.zoom("+zoomLevel+")"));		
	},

	panAndZoomIn: function(x,y) {
		SMUtils.dbg("panAndZoomIn("+x+","+y+")");
		return StarMap.panTo(x,y,StarMap.zoomIn);		
	},
	
	panNorth: function() {
		return StarMap.panTo(StarMap.cx,StarMap.cy + (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},
	
	panSouth: function() {
		return StarMap.panTo(StarMap.cx,StarMap.cy - (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},
	
	panWest: function() {
		return StarMap.panTo(StarMap.cx - (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.cy);
	},
	
	panEast: function() {
		return StarMap.panTo(StarMap.cx + (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.cy);
	},
	
	panNorthEast: function() {
		return StarMap.panTo(StarMap.cx + (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.cy + (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},
	
	panSouthEast: function() {
		return StarMap.panTo(StarMap.cx + (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.cy - (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},
	
	panNorthWest: function() {
		return StarMap.panTo(StarMap.cx - (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.cy + (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},
	
	panSouthWest: function() {
		return StarMap.panTo(StarMap.cx - (StarMap.mapWidth/2)*StarMap.zoomLevels[StarMap.zoomIndex],StarMap.panToY = StarMap.cy - (StarMap.mapHeight/2)*StarMap.zoomLevels[StarMap.zoomIndex]);
	},

	callZoomListeners: function() {
		for (i=0;i<StarMap.zoomListeners.length;i++) {
			StarMap.zoomListeners[i](StarMap.zoomIndex);
		}
	},
	
	callClickListeners: function() {
		for (i=0;i<StarMap.clickListeners.length;i++) {
			StarMap.clickListeners[i](StarMap.cx,StarMap.cy);
		}
	},
	
	callMouseDownListeners: function() {
		for (i=0;i<StarMap.mouseDownListeners.length;i++) {
			StarMap.mouseDownListeners[i](StarMap.mouseDownX,StarMap.mouseDownY);
		}
	},
	
	callMouseUpListeners: function() {
		for (i=0;i<StarMap.mouseUpListeners.length;i++) {
			StarMap.mouseUpListeners[i](StarMap.mouseUpX,StarMap.mouseUpY);
		}
	},
	
	callDragListeners: function() {
		for (i=0;i<StarMap.dragListeners.length;i++) {
			StarMap.dragListeners[i](StarMap.cx,StarMap.cy);
		}
	},
	
	callZoomChangeListeners: function() {
		var zoomLevels = StarMap.getCurrentZoomLevels();
		for (i=0;i<StarMap.zoomChangeListeners.length;i++) {
			StarMap.zoomChangeListeners[i](zoomLevels);
		}
	},
	
	callSatBoundsListeners: function() {
		for (i=0;i<StarMap.satBoundsListeners.length;i++) {
			StarMap.satBoundsListeners[i](StarMap.insideSat);
		}
	},
	

	getZoomLevel: function() {
		return StarMap.zoomIndex;
	},
		
	getResolution: function() {
		return StarMap.zoomLevels[StarMap.zoomIndex];
	},

	zoomToFit: function(x1, y1, x2, y2) {
		var minX = Math.min(x1,x2);
		var maxX = Math.max(x1,x2);
		var minY = Math.min(y1,y2);
		var maxY = Math.max(y1,y2);
			
		var width = maxX - minX;
		var height = maxY - minY;

		var resX = width / StarMap.mapWidth;
		var resY = height / StarMap.mapHeight;
		var res = Math.max(resX,resY);
		
		var zoomIndex = StarMap.zoomLevels.length-1;
		for (var i=0;i<StarMap.zoomLevels.length;i++) {
			if (StarMap.zoomLevels[i] >= res) {
				zoomIndex = i;
				break;
			}
		}
		
		//alert(zoomIndex+","+cx+","+cy);
		
		StarMap.cx = minX + width/2;
		StarMap.cy = minY + height/2;
		StarMap.zoom(zoomIndex);
	},

	zoomInToFit: function(topLeftX, topLeftY, width, height) {
		var oldRes = StarMap.zoomLevels[StarMap.zoomIndex];
		var resX = (width*oldRes)/StarMap.mapWidth;
		var resY = (height*oldRes)/StarMap.mapHeight;
		var newRes = Math.max(resX, resY);
		var zoomIndex = StarMap.zoomIndex;
		for (var i=0;i<StarMap.zoomLevels.length;i++) {
			if (StarMap.zoomLevels[i] > newRes) {
				zoomIndex = i;
				break;
			}
		}
		// Always zoom at least one level
		if (zoomIndex == StarMap.zoomIndex && zoomIndex > 0) {
			zoomIndex--;	
		}
		SMUtils.dbg(topLeftX+","+topLeftY);
		
		StarMap.cx = parseInt(StarMap.cx - (StarMap.mapWidth/2)*oldRes + (topLeftX + (width/2))*oldRes);
		StarMap.cy = parseInt(StarMap.cy + (StarMap.mapHeight/2)*oldRes - (topLeftY + (height/2))*oldRes);
		StarMap.zoom(zoomIndex);
	},

	zoom: function(zoomIndex) {
		if (zoomIndex == StarMap.zoomIndex) {
			return;	
		}
		if (zoomIndex > StarMap.zoomIndex && !StarMap.hasZoomLevelsAbove()) {
			return;	
		}
		if (zoomIndex < StarMap.zoomIndex && !StarMap.hasZoomLevelsBelow()) {
			return;
		}
		StarMap.zoomIndex = zoomIndex;
		StarMap.repaint();
		//StarMap.resetTiles();
		StarMap.updateSettings();
		//StarMap.callWhenAllLoaded(StarMap.resetTiles,5);
		//StarMap.callWhenAllLoaded(StarMap.callZoomListeners,10);
		StarMap.callZoomListeners();
		//setTimeout(StarMap.resetTiles,100);
		return false;
	},
	
	zoomIn: function() {
		return StarMap.zoom(StarMap.zoomIndex-1);
	},
	
	zoomOut: function() {
		return StarMap.zoom(StarMap.zoomIndex+1);
	},
	
	zoomMin: function() {
		return StarMap.zoom(StarMap.zoomLevels.length-1);
	},
	
	zoomMax: function() {
		return StarMap.zoom(0);
	},
	
	panTiles: function(dx,dy) {
		for (var i=0;i<StarMap.tiles.length;i++) {
			StarMap.tiles[i].pan(dx,dy);
		}
	},
	
	addPolyline: function(pointstr) {
		var pw = new PolylineWrapper();
		pw.setPoints(pointstr);
		StarMap.polylines.push(pw);
		StarMap.repaint();
	},

	panPoints: function(dx,dy) {
		for (var i=0;i<StarMap.points.length;i++) {
			StarMap.points[i].pan(dx,dy);
		}
	},

	clearPoints: function() {
		var points = StarMap.points;
		var newPoints = [];
		var index = 0;
		for (var i=points.length-1;i>=0;i--) {
			if (points[i].persistent) {
				newPoints[index++] = points[i]; 
			} else {
				points[i].remove(StarMap.wrapper);
			}
		}
		StarMap.points = newPoints;
	},

	clearPolylines: function() {
		StarMap.polylines = [];
		StarMap.repaint();
	},

	addPoint: function(x,y,title,iconURI,zIndex) {
		SMUtils.dbg("Adding point "+title+" at "+x+","+y);
		var point = StarMap.points[StarMap.points.length] = new SMPoint(x,y,title,iconURI,zIndex);
		point.appendTo(StarMap.wrapper);
		return point;
	},
	
	removePoint: function(point) {
		var points = StarMap.points;
		var newPoints = [];
		var index = 0;
		for (var i=points.length-1;i>=0;i--) {
			if (points[i] != point) {
				newPoints[index++] = points[i]; 
			} 
		}
		point.remove(StarMap.wrapper);
		StarMap.points = newPoints;
	},
	
	movePoint: function(point, x, y) {
		StarMap.updatePoint(point,x,y);
	},
	
	updatePoint: function(point, x, y, title, iconURI) {
		point.update(x,y,title,iconURI);
	},

  showPoint: function(point) {
		point.setVisible(true);
  },

  hidePoint: function(point) {
    point.setVisible(false);
  },

	makePersistent: function(point) {
		point.persistent = true;
	},
	
	showToolTip: function(point) {
		point.showToolTip(true);	
	},
	
	hideToolTip: function(point) {
		point.hideToolTip(true);	
	},
	
	addPoints: function(pointstr) {
		var points = pointstr.split(";");
		for (var i=0;i<points.length;i++) {
			var point = points[i].split(",");
			var title = (point.length > 2)?point[2]:(i+1);
			StarMap.addPoint(point[0],point[1],title);
		}
	},
	
	addPanner: function() {
		//SMPanner.init(StarMap.wrapper,10,10);
	},

 	addZoomer: function() {
		//SMZoomer.init(StarMap.wrapper,90,35);
	},

	resize: function() {
			StarMap.mapWidth = parseInt(StarMap.map.offsetWidth);
			StarMap.mapHeight = parseInt(StarMap.map.offsetHeight);
			StarMap.wrapper.parentNode.removeChild(StarMap.wrapper);
			StarMap.buildGrid();
			for (var i=0;i<StarMap.points.length;i++) {
				StarMap.points[i].appendTo(StarMap.wrapper);
			}
			StarMap.scalebar = null;
			StarMap.logo = null;
			StarMap.repaint();
	},

		
	buildGrid: function() {
		StarMap.numTilesX = Math.ceil(StarMap.mapWidth / StarMap.tileSize);
		StarMap.numTilesY = Math.ceil(StarMap.mapHeight / StarMap.tileSize);

		StarMap.numTilesX += (StarMap.numTilesX%2==0)?1:2;
		StarMap.numTilesY += (StarMap.numTilesY%2==0)?1:2;

		SMUtils.dbg("Building grid "+StarMap.numTilesX +"x"+StarMap.numTilesY+" tiles");
		
		var wrapper = StarMap.wrapper = document.createElement("div");
		wrapper.style.position = "relative";
		wrapper.style.overflow = "hidden";
		wrapper.style.textAlign = "left";
		wrapper.style.width = "100%";
		wrapper.style.height = "100%";
		wrapper.style.UserSelect = "none";
		wrapper.style.KhtmlUserSelect = "none";
		wrapper.style.MozUserSelect = "none";
		wrapper.style.cursor = "move";
		StarMap.map.appendChild(wrapper);

		StarMap.tiles = new Array();
		for (var row=0;row<StarMap.numTilesY;row++) {
			for (var col=0;col<StarMap.numTilesX;col++) {
				var tile = StarMap.tiles[StarMap.tiles.length] = new SMTile(row,col,wrapper);
				SMUtils.addEvent(tile.element,'mousedown',StarMap.startDrag,false);
				SMUtils.addEvent(tile.element,'dragstart',SMUtils.stopEvent,false);
				SMUtils.addEvent(tile.element,'selectstart',SMUtils.stopEvent,false);
			}
		}
	},
	
	repaint: function() {
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		var tileSize = (res * 256);
	
		var tileX = Math.floor(StarMap.cx/tileSize)*tileSize;
		var tileY = Math.ceil(StarMap.cy/tileSize)*tileSize;

		var cx = tileX + (res * 128);
		var dx = parseInt((cx - StarMap.cx) / res);

		var cy = tileY - (res * 128);
		var dy = parseInt((StarMap.cy - cy) / res);

		for (var i=0;i<StarMap.tiles.length;i++) {
			StarMap.tiles[i].position(dx,dy);
		}

		for (var i=0;i<StarMap.points.length;i++) {
    			StarMap.points[i].position();
		}

 		StarMap.addScalebar();
 		StarMap.addLogo();

	},

	click: function(e) {
	  if (StarMap.dx == 0 && StarMap.dy == 0) {
			//SMUtils.dbg(SMUtils.mouseX(e)+","+SMUtils.mouseY(e));
      			var dx = SMUtils.mouseX(e) - (SMUtils.topLeftX(StarMap.wrapper)+StarMap.mapWidth/2);
      			var dy = SMUtils.mouseY(e) - (SMUtils.topLeftY(StarMap.wrapper)+StarMap.mapHeight/2);
      			var res = StarMap.zoomLevels[StarMap.zoomIndex];
			if (StarMap.hasZoomLevelsBelow() && StarMap.clickEnabled) {
				StarMap.cx += dx*res;
				StarMap.cy -= dy*res;
				StarMap.zoomIn();
				StarMap.callWhenAllLoaded(StarMap.callClickListeners,10);
			} else {
				var callClickListeners = function() {
					for (i=0;i<StarMap.clickListeners.length;i++) {
						StarMap.clickListeners[i](StarMap.cx+dx*res,StarMap.cy-dy*res);
					}
				};
				
				StarMap.callWhenAllLoaded(callClickListeners,10);
			}

		  }
  	},

		
	keyDown: function(e) {
		switch (e.keyCode) {
			case 12:
				if (StarMap.isSat()) {
					StarMap.sourceMap();
				} else {
					StarMap.sourceSatellite();
				}
				break;
			case 33:
				StarMap.panNorthEast();
				break;
			case 34:
				StarMap.panSouthEast();
				break;
			case 35:
				StarMap.panSouthWest();
				break;
			case 36:
				StarMap.panNorthWest();
				break;
			case 37:
				StarMap.panWest();
				break;
			case 38:
				StarMap.panNorth();
				break;
			case 39:
				StarMap.panEast();
				break;
			case 40:
				StarMap.panSouth();
				break;
			case 61:
			case 107:
				StarMap.zoomIn();
				break;
			case 109:
				StarMap.zoomOut();
				break;
			default:
				//alert(e.keyCode);
				break;
		}
	},

	enableKeyPad: function() {
		SMUtils.addEvent(document,'keydown',StarMap.keyDown,false);
	},
			
	getCenter: function() {
		return new Array(parseInt(StarMap.cx),parseInt(StarMap.cy));
	},

	getBounds: function() {
		var minX = StarMap.cx - (StarMap.mapWidth * StarMap.zoomLevels[StarMap.zoomIndex] / 2);
		var minY = StarMap.cy - (StarMap.mapHeight * StarMap.zoomLevels[StarMap.zoomIndex] / 2);
 		var maxX = StarMap.cx + (StarMap.mapWidth * StarMap.zoomLevels[StarMap.zoomIndex] / 2);
		var maxY = StarMap.cy + (StarMap.mapHeight * StarMap.zoomLevels[StarMap.zoomIndex] / 2);
		return new Array(parseInt(minX),parseInt(minY),parseInt(maxX),parseInt(maxY));
	},
	
	isSat: function() {
		return StarMap.sat;
	},

	getZoomLevels: function() {
		return StarMap.zoomLevels;
	},
	
	getCurrentZoomLevels: function() {
		return StarMap.currentZoomLevels;
	},
	
	setCurrentZoomLevels: function(newZoomLevels) {
		//var oldZoomLevels = StarMap.getCurrentZoomLevels();
		StarMap.currentZoomLevels = newZoomLevels.split(",");
		//if (newZoomLevels != oldZoomLevels) {
			StarMap.callZoomChangeListeners();
		//}
	},
	
	updateSettings: function() {
		/*
		document.domain = StarMap.domain;
		StarMap.updateIFrame.src = StarMap.updatePage+'?x='+StarMap.cx+'&y='+StarMap.cy+'&res='+StarMap.zoomLevels[StarMap.zoomIndex]+'&source='+(StarMap.sat?1:0)+'&width='+StarMap.mapWidth+'&height='+StarMap.mapHeight+'&domain='+StarMap.domain;
		*/
	},

	getInsideSat: function() {
		return StarMap.insideSat;	
	},
	
	setInsideSat: function(insideSat) {
		StarMap.insideSat = insideSat;
		if (StarMap.sat) {
			StarMap.callSatBoundsListeners();
		}
	},
	
	hasZoomLevelsAbove: function() {
		return StarMap.zoomIndex < (StarMap.zoomLevels.length - 1);
	},
	
	hasZoomLevelsBelow: function() {
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		return (StarMap.currentZoomLevels[0] < res);
	},

	allLoaded: function() {
		for (i=0;i<StarMap.tiles.length;i++) {
			if (StarMap.tiles[i].img.loading) {
				return false;
			}
		}
		return true;
	},
	
	callWhenAllLoaded: function(fn,tries) {
		if (tries <= 0 || StarMap.allLoaded()) {
			fn();
		} else {
			setTimeout(function() {StarMap.callWhenAllLoaded(fn,tries-1)},200);	
		}
	},
	
	addLogo: function() {
		if (!StarMap.logo) {
			var logo = StarMap.logo = document.createElement("div");
			logo.style.fontSize = "10px";
			logo.style.fontFamily = "Arial";
			logo.style.margin = "0px";
			logo.style.padding = "2px";
			logo.style.position = "absolute";
			logo.style.left = "0";
			logo.style.bottom = "0";
			logo.style.zIndex = "100";
			StarMap.wrapper.appendChild(logo);
		}

		var logo = StarMap.logo;

		if (StarMap.isSat()) {
			var res = StarMap.zoomLevels[StarMap.zoomIndex];
			switch (res) {
				case 0.2:
				case 0.8:
				case 2:
					logo.innerHTML = "&#169; Lantm�teriverket / respektive kommun";
					break;
				case 4:
					logo.innerHTML = "SPOT &#174;, &#169; CNES, &#169; Metria &#169; Lantm�teriverket / respektive kommun";
					break;
				case 10:
				case 25:
					logo.innerHTML = "SPOT &#174;, &#169; CNES, &#169; Metria";
					break;
				default:
					logo.innerHTML = "";
					break;
			}
		} else {
		logo.innerHTML = "&#169;2008 Starcus &#183; Kartdata &#169;2008 T-Kartor";
		}
	},

	addScalebar: function() {

		if (!StarMap.scalebar) {
			var scalebar = StarMap.scalebar = document.createElement("div");
			scalebar.style.position = "absolute";
			scalebar.style.bottom = "0";
			scalebar.style.right = "0";
			scalebar.style.fontFamily = "Arial";
			scalebar.style.fontSize = "12px";
			scalebar.style.textAlign = "center";
			scalebar.style.border = "solid 2px";
			scalebar.style.borderTop = "none";
			scalebar.style.margin = "2px";
			scalebar.style.zIndex = "99";
			StarMap.wrapper.appendChild(scalebar);
		}
		
		var scalebar = StarMap.scalebar;
		
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		switch (res) {
			case 0.2:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "20 m";
				break;
			case 0.8:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "80 m";
				break;
			case 2:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "200 m";
				break;
			case 4:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "400 m";
				break;
			case 10:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "1 km";
				break;
			case 25:
				scalebar.style.width = "80px";
				scalebar.innerHTML = "2 km";
				break;
			case 70:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "7 km";
				break;
			case 200:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "20 km";
				break;
			case 700:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "70 km";
				break;
			case 3500:
				scalebar.style.width = "100px";
				scalebar.innerHTML = "35 mil";
				break;
                        case 120:
                                scalebar.style.width = "83px";
                                scalebar.innerHTML = "10 km";
                                break;
                        case 600:
                                scalebar.style.width = "100px";
                                scalebar.innerHTML = "60 km";
                                break;
                        case 4800:
                                scalebar.style.width = "101px";
                                scalebar.innerHTML = "50 mil";
                                break;

		}
	},
	
	addPointClickListener: function(point,fn) {
		point.addClickListener(fn);
	},

	addPointMouseOverListener: function(point,fn) {
		point.addMouseOverListener(fn);
	},

	addPointMouseOutListener: function(point,fn) {
		point.addMouseOutListener(fn);
	},

	addZoomListener: function(fn) {
		StarMap.zoomListeners.push(fn);
	},

	addZoomChangeListener: function(fn) {
		StarMap.zoomChangeListeners.push(fn);
		StarMap.callZoomChangeListeners();
	},
	
	addClickListener: function(fn) {
		StarMap.clickListeners.push(fn);
	},
	
	addMouseDownListener: function(fn) {
		StarMap.mouseDownListeners.push(fn);
	},
	
	addMouseUpListener: function(fn) {
		StarMap.mouseUpListeners.push(fn);
	},
	
	addPanListener: function(fn) {
		StarMap.panListeners.push(fn);
	},
	
	addDragEndListener: function(fn) {
		StarMap.dragListeners.push(fn);
	},
	
	addSatBoundsListener: function(fn) {
		StarMap.satBoundsListeners.push(fn);
		StarMap.updateSettings();
	},
	
	init: function(mapDiv, cx, cy, zoomIndex) {
		if (document.getElementById) {
			var map = StarMap.map = document.getElementById(mapDiv);

			SMUtils.dbg("Initiating map at "+cx+","+cy+". res="+StarMap.zoomLevels[zoomIndex]);

			// remove existing children from map div
			while (map.hasChildNodes()) {
				map.removeChild(map.childNodes[0]);
			}

			StarMap.map.style.overflow = "hidden";
			StarMap.cx = parseInt(cx);
			StarMap.cy = parseInt(cy);
			StarMap.zoomIndex = parseInt(zoomIndex);

			StarMap.mapWidth = parseInt(StarMap.map.offsetWidth);
			StarMap.mapHeight = parseInt(StarMap.map.offsetHeight);

			SMUtils.dbg("Map dimensions="+StarMap.mapWidth+"x"+StarMap.mapHeight);
	
			StarMap.buildGrid();
			StarMap.repaint();

			SMUtils.addEvent(document,'mousemove',StarMap.drag,false);
			SMUtils.addEvent(document,'mouseup',StarMap.stopDrag,false);

			SMUtils.addEvent(StarMap.map,'mouseup',StarMap.mouseUp,false);
			SMUtils.addEvent(StarMap.map,'click',StarMap.click,false);


			if (document.getElementsByTagName("body").length > 0) {
				SMUtils.addEvent(document.getElementsByTagName("body")[0],'mouseleave',StarMap.stopDrag,false);
			}
				
			StarMap.updateIFrame = document.createElement("iframe");
			StarMap.updateIFrame.style.visibility = "hidden";

			StarMap.wrapper.appendChild(StarMap.updateIFrame);
			
			StarMap.updateSettings();

			initLogging();

		}
	}
}

/*******************************************
* CLASS: SMToolTip
*******************************************/
var SMToolTip = function(top,left,text,parentElem) {
	
	this.top = top;
	this.left = left;
	
	this.ttElem = document.createElement("div");
	this.ttElem.style.position = "absolute";
	this.ttElem.style.borderTop = "solid 1px #888";
	this.ttElem.style.zIndex = "99";
	this.ttElem.style.visibility = "hidden";

	this.ttBodyElem = document.createElement("div");
	this.ttBodyElem.style.fontFamily ="Verdana";
	this.ttBodyElem.style.textAlign = "left";
	this.ttBodyElem.style.fontSize ="11px";
	this.ttBodyElem.style.padding ="5px";
	this.ttBodyElem.style.borderRight ="solid 1px #000";
	this.ttBodyElem.style.borderLeft = "solid 1px #888";
	this.ttBodyElem.style.backgroundColor ="#fff";
	this.ttElem.appendChild(this.ttBodyElem);
	
	this.ttFooterElem = document.createElement("div");
	this.ttFooterElem.style.padding = "10px";
	this.ttFooterElem.style.background = "transparent url("+StarMap.baseUrl+"/img/pratbubbla.gif) top left no-repeat";
	this.ttElem.appendChild(this.ttFooterElem);
	
	//parentElem.appendChild(this.ttElem);
	var bodyElem = document.getElementsByTagName("body")[0];
	bodyElem.appendChild(this.ttElem);
	
	this.remove = function() {
		var bodyElem = document.getElementsByTagName("body")[0];
		bodyElem.removeChild(this.ttElem);
	};

	this.position = function(top,left) {
		this.top = parseInt(top);
		this.left = parseInt(left);
		this.ttElem.style.left = (SMUtils.topLeftX(StarMap.map) + this.left - 14)+"px";
		this.ttElem.style.top = (SMUtils.topLeftY(StarMap.map) + this.top - this.ttElem.offsetHeight)+"px";
	};
	
	this.pan = function(dx,dy) {
		this.position(this.top+dy, this.left+dx);
	};
	
	this.show = function(sticky) {
		if (sticky || !this.sticky) {
			this.sticky = sticky;
			this.ttElem.style.visibility = "visible";
			this.visible = true;
		}
	};
	
	this.setVisibility = function(visible) {
		this.ttElem.style.visibility = visible;
	};

	this.showDelayed = function(ms) {
		this.timeoutId = setTimeout(this.show,ms);
	};
	
	this.hide = function(sticky) {
		if (sticky || !this.sticky) {
			this.sticky = false;	
			clearTimeout(this.timeoutId);
			this.ttElem.style.visibility = "hidden";
			this.visible = false;
		}
	};

	this.setText = function(text) {
		this.ttBodyElem.innerHTML = text;
	}
	
	this.setText(text);
	this.position(top,left);
}


Function.prototype.bindIt = function(object) {
  var __method = this;
  return function() {
    return __method.call(object);
  }
}

/*******************************************
* CLASS: SMTile
********************************************/
var SMTile = function(row,col,parent) {
	this.row = row;
	this.col = col;
	
	this.div = document.createElement("div");
	this.div.style.position = "absolute";
	this.div.style.width = "256px";
	this.div.style.height = "256px";

	this.img = document.createElement("img");
	this.img.style.position = "absolute";
	this.img.style.visibility = "hidden";
	this.img.style.top = "0";
	this.img.style.left = "0";
	this.img.style.width = "256px";
	this.img.style.height = "256px";
	this.img.onload = function(e) {
		this.loading = false;
		this.tile.src=this.src;	
	}

	document.getElementsByTagName("body")[0].appendChild(this.img);
	//StarMap.wrapper.appendChild(this.img);

	SMUtils.dbg("Creating tile "+row+"x"+col);

	this.element = document.createElement("img");
	this.element.src=StarMap.baseUrl+"/img/loaddragmap.gif";
	this.element.style.width = "256px";
	this.element.style.height = "256px";
	this.element.galleryimg="false";
	this.element.oncontextmenu = function(e){return false};
	this.img.tile = this.element;

	this.div.appendChild(this.element);
	
	parent.appendChild(this.div);

	this.position = function(dx,dy) {
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		this.left = (StarMap.mapWidth >> 1) - ((StarMap.numTilesX << 8) >> 1) + (this.col << 8);
		this.top = (StarMap.mapHeight >> 1) - ((StarMap.numTilesY << 8) >> 1) + (this.row << 8);

		var cTileX = Math.floor(StarMap.cx/(res*256))*(res*256);
		var cTileY = Math.ceil(StarMap.cy/(res*256))*(res*256);

		var x = cTileX - (StarMap.numTilesX >> 1)*(res  *256) + this.col*(res * 256);
		var y = cTileY + (StarMap.numTilesY >> 1)*(res * 256) - this.row*(res * 256);

 		this.sqX = Math.round(x / (res * 256));
 		this.sqY = Math.round(y / (res * 256));

		this.setImg();
		this.left += dx;
		this.top += dy;
		this.div.style.top = this.top + "px";
		this.div.style.left = this.left + "px";
	};

	this.sb = new StringBuffer();

	this.setImg = function() {
		this.img.loading = true;
		this.element.src=StarMap.baseUrl+"/img/loaddragmap.gif";
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		
		var src = this.sb;
		src.reset();
		src.append(StarMap.baseUrl).append("/ImgServlet");
		src.append("?res=").append(res);
		src.append("&source=0");
		src.append("&sqx=").append(this.sqX);
		src.append("&sqy=").append(this.sqY);
		var polylines = StarMap.polylines;
		for (var i=0;i<polylines.length;i++) {
			var x1 = this.sqX * res * 256;
			var y1 = this.sqY * res * 256;
			var x2 = x1 + res*256;
			var y2 = y1 - res*256;
			var bounds = new Rectangle();
			bounds.include(new Point(x1,y1));
			bounds.include(new Point(x2,y2));
			var p = polylines[i].getPolyline(res);
			var path = p.getPath(bounds);
			if (path != null) {
				src.append("&path=").append(path);
			}
		}
		
		this.img.src = src.toString();
	};

	/*
	this.resetImg = function() {
		this.img.loading = true;
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		
		var src = this.sb;
		src.reset();
		src.append(StarMap.baseUrl).append("/ImgServlet");
		src.append("?res=").append(res);
		src.append("&sqx=").append(this.sqX);
		src.append("&sqy=").append(this.sqY);
		var polylines = StarMap.polylines;
		for (var i=0;i<polylines.length;i++) {
			var x1 = this.sqX * res * 256;
			var y1 = this.sqY * res * 256;
			var x2 = x1 + res*256;
			var y2 = y1 - res*256;
			var bounds = new Rectangle();
			bounds.include(new Point(x1,y1));
			bounds.include(new Point(x2,y2));
			var p = polylines[i].getPolyline(res);
			var path = p.getPath(bounds);
			if (path != null) {
				src.append("&path=").append(path);
			}
		}
		this.img.src = src.toString();
	},
	*/


	this.img.onerror = this.setImg.bindIt(this);

	this.pan = function(dx, dy) {
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		this.top += dy;
		this.left += dx;

		if (dx > 0 && this.left > StarMap.mapWidth) {
			this.left -= (StarMap.numTilesX << 8);
			this.sqX -= StarMap.numTilesX;
			this.setImg();
		} else if (dx < 0 && this.left < -StarMap.tileSize) {
			this.left += (StarMap.numTilesX << 8);
			this.sqX += StarMap.numTilesX;
			this.setImg();
		} else if (dy > 0 && this.top > StarMap.mapHeight) {
			this.top -= (StarMap.numTilesY << 8);
			this.sqY += StarMap.numTilesY;
			this.setImg();
		} else if (dy < 0 && this.top < -StarMap.tileSize) {
			this.top += (StarMap.numTilesY << 8);
			this.sqY -= StarMap.numTilesY;
			this.setImg();
		}
		this.div.style.top = this.top + "px";
		this.div.style.left = this.left + "px";
	};
	
}; 

/*******************************************
* CLASS: SMPoint
********************************************/
var SMPoint = function(x,y,title,iconURI,zIndex) {
	
	this.persistent = false;
	this.id = SMPoint.nextId++;
	
	this.x = parseInt(x);
	this.y = parseInt(y);
	this.title = title;
	
	this.top = 0;
	this.left = 0;
	
	if (!iconURI) {
		iconURI = StarMap.baseUrl+"/img/"+(StarMap.points.length+1)+".gif";
	};

	this.setProperties = function() {
		SMUtils.dbg("calling setProperties");
		this.style.marginLeft = -(parseInt(this.width)>>1) +"px";
		this.style.marginTop = -(parseInt(this.height)>>1) +"px";
		this.wrapper.position();
		//this.toolTip.position(this.top+parseInt(this.element.style.marginTop),this.left);
	};

	this.update = function(x, y, title, iconURI) {
		this.x = parseInt(x);
		this.y = parseInt(y);
		if (title) {
			this.title = title;
			this.toolTip.setText(title);
		}	
		if (iconURI) {
			this.element.onload = this.setProperties;
			this.element.src = iconURI;
		}
		this.position();
	}

        this.setVisible = function(visible) {
                if (visible) {
                        this.element.style.display="block";
                } else {
			this.hideToolTip();
                        this.element.style.display="none";
                }
        };
	
	this.appendTo = function(parent) {
		parent.appendChild(this.element);
	};

	this.remove = function(parent) {
		parent.removeChild(this.element);
		this.toolTip.remove();
	};
	
	this.position = function() {
		var res = StarMap.zoomLevels[StarMap.zoomIndex];
		this.left = (this.x - (StarMap.cx - (StarMap.mapWidth >> 1)*res))/res;
		//this.top = ((StarMap.cy + (StarMap.mapHeight >> 1)*res)-this.y)/res;
		this.top = (parseInt(StarMap.cy) + parseInt(StarMap.mapHeight >> 1)*res - parseInt(this.y))/res;
		this.element.style.top = this.top + "px";
		this.element.style.left = this.left + "px";
	
		if (!this.isVisible()) {
			this.toolTip.position(-300,-300);
		} else {
			this.toolTip.position(this.top+parseInt(this.element.style.marginTop),this.left);
			if (this.toolTip.visible) {
				this.toolTip.setVisibility("visible");
			}
		}
	};
	
	this.isVisible = function() {
		var padding = 20;
		return this.top > -padding && this.top < (StarMap.mapHeight+padding) && this.left > -padding && this.left < (StarMap.mapWidth+padding);
	};
	
	this.pan = function(dx, dy) {
		var visibleBefore = this.isVisible();
		this.top += parseInt(dy);
		this.left += parseInt(dx);
		var visibleNow = this.isVisible();
		
		if (visibleBefore || visibleNow) {
			this.element.style.top = this.top + "px";
			this.element.style.left = this.left + "px";
			this.toolTip.position(this.top+parseInt(this.element.style.marginTop),this.left);
			if (this.toolTip.visible) {
				this.toolTip.setVisibility("visible");
			}
		} else {
			this.toolTip.setVisibility("hidden");
			//this.hideToolTip(true);			
		}
		//this.toolTip.pan(dx,dy);
	};
	
	this.addClickListener = function(fn) {
		var point = this;
		SMUtils.addEvent(this.element,'click',function (e) {fn(point)},false);
	};

	this.addMouseOverListener = function(fn) {
		var point = this;
		SMUtils.addEvent(this.element,'mouseover',function (e) {fn(point)},false);
	};

	this.addMouseOutListener = function(fn) {
		var point = this;
		SMUtils.addEvent(this.element,'mouseout',function (e) {fn(point)},false);
	};

	this.showToolTip = function(sticky) {
		if ((sticky || SMPoint.flag) && this.isVisible()) {
			//this.toolTip.position(this.top+parseInt(this.element.style.marginTop),this.left);
			this.toolTip.show(sticky);
		}
	};

	this.hideToolTip = function(sticky) {
		this.toolTip.hide(sticky);
	};

 	this.element = document.createElement("img");
    	this.element.wrapper = this;
    	this.element.style.zIndex = "10";
	if (zIndex) {
		this.element.style.zIndex = zIndex;
	}

 	this.element.style.position = "absolute";
	this.element.oncontextmenu = function(e) {return false};

	this.toolTip = new SMToolTip(0,0,title,StarMap.wrapper);
  this.element.onmouseover = function(e) {SMUtils.getTarget(e).wrapper.showToolTip()};
	this.element.onmouseout = function(e) {SMUtils.getTarget(e).wrapper.hideToolTip()};
	this.element.onmousedown = function(e) {/*StarMap.callMouseDownListeners();*/SMUtils.stopEvent(e);return false;};
	this.element.onmouseup = function(e) {/*StarMap.callMouseUpListeners();*/SMUtils.stopEvent(e);return false;};
	this.element.onclick = function(e) {
	if (StarMap.hasZoomLevelsBelow() && StarMap.clickEnabled) {
		StarMap.cx = SMUtils.getTarget(e).wrapper.x;
		StarMap.cy = SMUtils.getTarget(e).wrapper.y;
		StarMap.zoomIn();
		StarMap.callWhenAllLoaded(StarMap.callClickListeners,10);
		SMUtils.stopEvent(e);
		return false;
	}
      };

      this.element.onload=this.setProperties;
      this.element.src = iconURI;
}

StarMap.sat = false;
SMPoint.flag = true;
SMPoint.nextId = 0;
StarMap.clickEnabled = true;

// Point CLASS

var Point = function(x,y) {
	this.x = parseInt(x);
	this.y = parseInt(y);
	this.marked = false;
};

Point.prototype.toString = function() {
		return this.x+","+this.y;
};

Point.prototype.clone = function() {
	return new Point(this.x,this.y);
};

//Compute the distance to other point
Point.prototype.distanceToPoint = function(otherPoint) {
	return Point.distance(this,otherPoint);
}

//Compute the distance from line segment ab
Point.prototype.distanceToLine = function(a, b) {
	if (a.x == b.x && a.y == b.y) {
		return this.distanceToPoint(a);
	}
	if (Point.dot(a,b,this) > 0) {
		return this.distanceToPoint(b);
	}
	if (Point.dot(b,a,this) > 0) {
		return this.distanceToPoint(a);
	}
	return Math.abs(Point.cross(a,b,this) / Point.distance(a,b));
};

//Compute distance A to B.
Point.distance = function(a, b) {
	var distX = a.x - b.x;
	var distY = a.y - b.y;
	return Math.sqrt(distX*distX+distY*distY);
}

//Compute the dot product AB ⋅ BC
Point.dot = function(a, b, c){
	var ab = [];
  var bc = [];
  ab[0] = b.x - a.x;
  ab[1] = b.y - a.y;
  bc[0] = c.x - b.x;
  bc[1] = c.y - b.y;
  return (ab[0] * bc[0]) + (ab[1] * bc[1]);
};

//Compute the cross product AB x AC
Point.cross = function(a, b, c) {
	var ab = [];
  var ac = [];
  ab[0] = b.x - a.x;
  ab[1] = b.y - a.y;
  ac[0] = c.x - a.x;
  ac[1] = c.y - a.y;
  return (ab[0] * ac[1]) - (ab[1] * ac[0]);
};

Point.parseArray = function(pointstr) {
	var xy = pointstr.split(",");
	var points = [];
	for (var i=0;i<xy.length-1;i+=2) {
		points[points.length] = new Point(xy[i],xy[i+1]);
	}
	return points;
};

// RECTANGLE CLASS
var Rectangle = function() {
	this.topLeft = new Point(-1,-1);
	this.bottomRight = new Point(-1,-1);
};

Rectangle.prototype.getWidth = function() {
	return this.bottomRight.x - this.topLeft.x;
};

Rectangle.prototype.getHeight = function() {
	return this.topLeft.y - this.bottomRight.y;
};

Rectangle.prototype.getCenter = function() {
	return new Point(this.topLeft.x+this.getWidth()/2,this.topLeft.y-this.getHeight()/2);
};

Rectangle.prototype.include = function(point) {
	this.topLeft.x = (this.topLeft.x < 0)?point.x:Math.min(this.topLeft.x,point.x);
	this.topLeft.y = (this.topLeft.y < 0)?point.y:Math.max(this.topLeft.y,point.y);
	this.bottomRight.x = (this.bottomRight.x < 0)?point.x:Math.max(this.bottomRight.x,point.x);
	this.bottomRight.y = (this.bottomRight.y < 0)?point.y:Math.min(this.bottomRight.y,point.y);
};

Rectangle.prototype.includes = function(point) {
	return point.x <= this.bottomRight.x && point.x >= this.topLeft.x && point.y <= this.topLeft.y && point.y >= this.bottomRight.y;
};

Rectangle.prototype.includeRect = function(rect) {
	this.include(rect.topLeft);
	this.include(rect.bottomRight);
};

Rectangle.prototype.intersects = function(rect) {
	return (rect.topLeft.x < this.bottomRight.x) && (rect.bottomRight.x > this.topLeft.x) && (rect.topLeft.y > this.bottomRight.y) && (rect.bottomRight.y < this.topLeft.y);
};

Rectangle.prototype.toString = function() {
	return this.topLeft.toString()+","+this.bottomRight.toString();
};

Rectangle.prototype.getPath = function() {
	var topRight = new Point(this.bottomRight.x, this.topLeft.y);
	var bottomLeft = new Point(this.topLeft.x,this.bottomRight.y);
	var points = [this.topLeft,topRight,this.bottomRight,bottomLeft,this.topLeft];
	return Polyline.encode(points);
}


// POLYLINE CLASS
var MIN_POINTS = 10;

var Polyline = function(points, start, stop) {
	this.points = points;
	this.start = start;
	this.stop = stop;
	this.polylines = [];
		
	var numPoints = (stop-start)+1;
	if (numPoints >= MIN_POINTS*2) {
		var index = parseInt(start + numPoints/2);
		this.polylines[0] = new Polyline(points,start,index);
		this.polylines[1] = new Polyline(points,index,stop);
	}
	
	this.bounds = new Rectangle();
	for (var i=start;i<=stop;i++) {
		this.bounds.include(points[i]);	
	}
	
};

Polyline.prototype.getPoints = function(newRef) {
	var points = this.points;
	var start = this.start;
	var numPoints = this.stop-start+1
	var newPoints = [];
	for (var i=0;i<numPoints;i++) {
		newPoints[i] = newRef?points[start+i].clone():points[start+i];
	}
	return newPoints;
};

Polyline.prototype.clone = function() {
	var points = this.getPoints(true);
	return new Polyline(points,0,points.length);
};
	
Polyline.prototype.intersects = function(rect) {
	if (this.bounds.intersects(rect)) {
		var polys = this.polylines;
		if (polys.length == 0) {
			return true;
		} else {
			for (var i=0;i<polys.length;i++) {
				if (polys[i].intersects(rect)) {
					return true;
				}
			}
		}
	}
	return false;
};

Polyline.prototype.getPath = function(rect) {
	if (rect) {
		return Polyline.encode(this.getIntersection(rect));
	} else {
		var points = this.getPoints();
		return Polyline.encode(points);
	}
};

Polyline.prototype.getIntersection = function(rect) {
	var interval = this.getIntersectionInterval(rect);
	var points = [];
	var numPoints = interval[1]-interval[0]+1;
	for (var i=0;i<numPoints;i++) {
		points[i] = this.points[interval[0]+i];
	}
	return points;
};

Polyline.prototype.getIntersectionInterval = function(rect) {
	var	interval = [this.points.length-1,0];
	if (this.intersects(rect)) {
		var polys = this.polylines;
		if (polys.length == 0) {
			interval = [this.start,this.stop];
		} else {
			for (var i=0;i<polys.length;i++) {
				var interval2 = polys[i].getIntersectionInterval(rect);
				interval[0] = Math.min(interval[0],interval2[0]);
				interval[1] = Math.max(interval[1],interval2[1]);
			}
		}
	}
	return interval;
};
	
Polyline.prototype.toString = function() {
		var str = new StringBuffer();
		var points = this.points;
		for (var i=this.start;i<=this.stop;i++) {
			str.append(points[i].toString());
			if (i < this.stop) {
				str.append(",");
			}
		}
		return str.toString();
};

Polyline.prototype.simplify = function(e) {
		var points = this.getPoints(true);
		
		points[0].marked = true;
		points[points.length-1].marked = true;
		
		Polyline.simplify(points,0,points.length-1,e);
	
		var newPoints = [];
		for (var i=0;i<points.length;i++) {
			if (points[i].marked) {
				newPoints[newPoints.length] = points[i];
			}	
		}
		
		return new Polyline(newPoints,0,newPoints.length-1);
};
	
Polyline.simplify = function(points, start, end, e) {
		if (start >= end-1) {
			return;
		}

		var maxIndex = 0;
		var maxDist = 0;
		
		for (var i=start+1;i<end;i++) {
			var dist = points[i].distanceToLine(points[start],points[end]);
			if (dist > maxDist) {
				maxDist = dist;
				maxIndex = i;
			}
		}
		
		if (maxDist > e) {
			points[maxIndex].marked = true;
			Polyline.simplify(points,start,maxIndex,e);
			Polyline.simplify(points,maxIndex,end,e);
		}		
};

Polyline.parsePolyline = function(pointstr) {
	var points = Point.parseArray(pointstr);
	return new Polyline(points,0,points.length-1);
};

Polyline.encode = function(points) {
	
	if (points.length == 0) {
		return null;
	}
	
	var coords = [];
	var prev = new Point(0,0);
	for (var i=0;i<points.length;i++) {
		coords[2*i] = points[i].x - prev.x;
		coords[(2*i)+1] = points[i].y - prev.y;
		prev = points[i];
	}
	
	var sb = new StringBuffer();
	for (var i=0;i<coords.length;i++) {
		var f = (Math.abs(coords[i]) << 1) - ((coords[i] < 0)?1:0);
		var e;
		do {
			e = f & 31;
			f = f >> 5;
			if (f != 0) {
				e  = e | 32;
			}
			sb.append(String.fromCharCode(e+63));
		} while (f != 0);
	}
	return sb.toString();
};

var PolylineWrapper = function() {
	this.polylines = [];	
	
	this.setPoints = function(pointstr) {
		this.p = Polyline.parsePolyline(pointstr);
		/*
		var res = [0.2,0.8,2,4,10,25,70,200,700,3500];
		for (var i=0;i<res.length;i++) {
			this.polylines[i] = (res[i] > 1)?p.simplify(res[i]):p;
		}
		*/
	};

	this.getPolyline = function(res) {
		if (!this.polylines[res]) {
			this.polylines[res] = (res > 1)?this.p.simplify(res):this.p;
		}
		return this.polylines[res];
	};
}

var StringBuffer = function() { 
	this.buffer = []; 
}

StringBuffer.prototype.append = function(string) {
	this.buffer.push(string);
  return this;
}

StringBuffer.prototype.toString = function() {
  return this.buffer.join("");
}

StringBuffer.prototype.reset = function() {
  return this.buffer = [];
}

function dbg(msg) {
	var div = document.getElementById("dbg");
	if (div) {
		div.innerHTML = msg+"<br/>"+div.innerHTML;
	}
};

/******************************************************************************
* Math (extensions)
******************************************************************************/

// Hyperbolic sine
Math.sinh = function(x) {
	return (Math.exp(x)-Math.exp(-x))/2;
}

// Hyperbolic cosine
Math.cosh = function(x) {
	return (Math.exp(x)+Math.exp(-x))/2;
}

// Hyperbolic tangent
Math.tanh = function(x) {
	return (Math.exp(x)-Math.exp(-x))/(Math.exp(x)+Math.exp(-x));
}

// Hyperbolic arc sine
Math.asinh = function(x) {
	return Math.log(x+Math.sqrt(1+Math.pow(x,2)));
}

// Hyperbolic arc cosine
Math.asinh = function(x) {
	return 2*Math.log(Math.sqrt((x+1)/2)+Math.sqrt((x-1)/2));
}

// Hyperbolic arc tangent
Math.atanh = function(x) {
	return (1/2)*(Math.log(x+1) - Math.log(1-x));
}

// Hyperbolic cotangent
Math.coth = function(x) {
	return (Math.exp(x)+Math.exp(-x))/(Math.exp(x)-Math.exp(-x));
}

// Hyperbolic secant
Math.sech = function(x) {
	return 2/(Math.exp(x)+Math.exp(-x));
}

// Hyperbolic cosecant
Math.csch = function(x) {
	return 2/(Math.exp(x)-Math.exp(-x));
}

// Radians to degrees
Math.toDegrees = function(rad) {
	return rad*360/(2*Math.PI);
};

// Degrees to radians
Math.toRadians = function(deg) {
	return (deg*2*Math.PI)/360;
};

/******************************************************************************
* XY
******************************************************************************/
var XY = function(x,y) {
	this.x = parseFloat(x);
	this.y = parseFloat(y);
};

XY.prototype.toString = function() {
	return this.x+","+this.y;
};

/******************************************************************************
* LatLng
******************************************************************************/
var LatLng = function(lat, lng) {
	this.lat = parseFloat(lat);
	this.lng = parseFloat(lng);
};

LatLng.prototype.toString = function() {
	var lat = LatLng.degree(this.lat);
	var lng = LatLng.degree(this.lng);
	var sb = new StringBuffer();
	sb.append(lat.deg).append("&#176;").append(lat.min).append("&#8242;").append(lat.sec).append("&#8243;");
	sb.append(" ");
	sb.append(lng.deg).append("&#176;").append(lng.min).append("&#8242;").append(lng.sec).append("&#8243;");
	return sb.toString();
}

LatLng.degree = function(dec) {
	var deg = parseInt(dec);
	var min = parseInt((dec-deg)*60);
	var sec = parseInt(((dec-deg)*60-min)*60);
	return {deg:deg,min:min,sec:sec};
};

LatLng.decimal = function(deg, min, sec) {
	return parseFloat(deg + (min/60) + (sec/3600));
};

/******************************************************************************
* Ellipsoid
******************************************************************************/
var Ellipsoid = function(a, f) {
	a = this.a = parseFloat(a);
	f = this.f = parseFloat(f);

	var e2 = this.e2 = f*(2-f);
	var e4 = Math.pow(e2,2);
	var e6 = Math.pow(e2,3);
	var e8 = Math.pow(e2,4);
	
	var n = this.n = f/(2-f);
	var n2 = Math.pow(n,2);
	var n3 = Math.pow(n,3);
	var n4 = Math.pow(n,4);
	
	this.ah = (a/(1+n))*(1+(1/4)*n2+(1/64)*n4);

	// d (delta) are used in (x,y)->(lat,lng)
	var d = this.d = [];
	d[0] = (1/2)*n - (2/3)*n2 + (37/96)*n3 - (1/360)*n4;
	d[1] = (1/48)*n2 + (1/15)*n3 - (437/1440)*n4;
	d[2] = (17/480)*n3 - (37/840)*n4;
	d[3] = (4397/161280)*n4;
	
	// abc coefficients are used in (x,y)->(lat,lng)
	var abc = this.abc = [];
	abc[0] = e2 + e4 + e6 + e8;
	abc[1] = -(1/6)*(7*e4 + 17*e6 + 30*e8);
	abc[2] = (1/120)*(224*e6 + 889*e8);
	abc[3] = -(1/1260)*(4279*e8);

	// b (beta) are used in (lat,lng)->(lat.lng)
	var b = this.b = [];
	b[0] = (1/2)*n - (2/3)*n2 + (5/16)*n3 + (41/180)*n4;
	b[1] = (13/48)*n2 - (3/5)*n3 + (557/1440)*n4;
	b[2] = (61/240)*n3 - (103/140)*n4;
	b[3] = (49561/161280)*n4;
	
	// xyz coefficients are used in (lat,lng)->(x,y)
	var xyz = this.xyz = [];
	xyz[0] = e2;
	xyz[1] = (1/6)*(5*e4-e6);
	xyz[2] = (1/120)*(104*e6-45*e8);
	xyz[3] = (1/1260)*(1237*e8);
	
};

/******************************************************************************
* Transform
******************************************************************************/
var Transform = function() {
	this.e = new Ellipsoid(this.SEMI_MAJOR_AXIS,this.FLATTENING);
};

Transform.prototype = {
	SEMI_MAJOR_AXIS: 6378137,
	FLATTENING: 1/298.257222101,
	FALSE_NORTHING: -667.711,
	FALSE_EASTING: 1500064.274,
	SCALE_REDUCTION: 1.00000561024,
	CENTRAL_MERIDIAN: Math.toRadians(LatLng.decimal(15,48,22.624306))
};


Transform.prototype.toLatLng = function(x,y) {
	x = parseFloat(x);
	y = parseFloat(y);
	
	var xi = (x-this.FALSE_NORTHING)/(this.SCALE_REDUCTION*this.e.ah);
	var eta = (y-this.FALSE_EASTING)/(this.SCALE_REDUCTION*this.e.ah);
		
	var xip = xi;
	var etap = eta;
	var d = this.e.d;
	for (var i=0;i<d.length;i++) {
		xip -= d[i]*Math.sin((2*i+2)*xi)*Math.cosh((2*i+2)*eta);
		etap -= d[i]*Math.cos((2*i+2)*xi)*Math.sinh((2*i+2)*eta);
	}

	var cLat = Math.asin(Math.sin(xip)/Math.cosh(etap));
	var dLng = Math.atan(Math.sinh(etap)/Math.cos(xip));

	var sinCLat = Math.sin(cLat);
	var cosCLat = Math.cos(cLat);
	var abc = this.e.abc;
	
	var lat = Math.toDegrees(cLat + sinCLat*cosCLat*(abc[0]+abc[1]*Math.pow(sinCLat,2)+abc[2]*Math.pow(sinCLat,4)+abc[3]*Math.pow(sinCLat,6)));
	var lng = Math.toDegrees(this.CENTRAL_MERIDIAN + dLng);
	
	return new LatLng(lat,lng);
};

Transform.prototype.toXY = function(lat,lng) {
	lat = Math.toRadians(parseFloat(lat));
	lng = Math.toRadians(parseFloat(lng));
	
	var sinLat = Math.sin(lat);
	var cosLat = Math.cos(lat);
	var xyz = this.e.xyz;
	
	var cLat = lat - sinLat*cosLat*(xyz[0] + xyz[1]*Math.pow(sinLat,2) + xyz[2]*Math.pow(sinLat,4) + xyz[3]*Math.pow(sinLat,6));
	var dLng = lng - this.CENTRAL_MERIDIAN;
	
	var xip = Math.atan(Math.tan(cLat)/Math.cos(dLng));
	var etap = Math.atanh(Math.cos(cLat)*Math.sin(dLng));
	var b = this.e.b;
	
	var x = this.SCALE_REDUCTION*this.e.ah*(xip + 
		b[0]*Math.sin(2*xip)*Math.cosh(2*etap) + 
		b[1]*Math.sin(4*xip)*Math.cosh(4*etap) +
		b[2]*Math.sin(6*xip)*Math.cosh(6*etap) +
		b[3]*Math.sin(8*xip)*Math.cosh(8*etap)) + this.FALSE_NORTHING;
		
	var y = this.SCALE_REDUCTION*this.e.ah*(etap +
		b[0]*Math.cos(2*xip)*Math.sinh(2*etap) +
		b[1]*Math.cos(4*xip)*Math.sinh(4*etap) +
		b[2]*Math.cos(6*xip)*Math.sinh(6*etap) +
		b[3]*Math.cos(8*xip)*Math.sinh(8*etap)) + this.FALSE_EASTING;
		
	return new XY(Math.round(y),Math.round(x));
};

/******************************************************************************
* StarMap extensions
******************************************************************************/
if (StarMap) {
	StarMap.transform = new Transform();
	StarMap.getLatLng = function(x,y) {
		if (!(x && y)) {
			x = StarMap.cx;
			y = StarMap.cy;
		}
		return StarMap.transform.toLatLng(y,x);
	};
	StarMap.getXY = function(lat,lng) {
		if (!(lat && lng)) {
			return StarMap.getCenter();
		}
		return StarMap.transform.toXY(lat,lng);
	};
}
