/*********************************************************************************
		Northern Geomantics IMS Javascript Library
		This library is designed for IE 5 and up and Netscape 7 and up.
		<put in copyright blurb>
*********************************************************************************/

var NGI_INCLUDED = true;

/*********************************************************************************
Generic variables.
*********************************************************************************/

var UPPER_LEFT = 0;
var UPPER_MIDDLE = 1;
var UPPER_RIGHT = 2;
var MIDDLE_LEFT = 3;
var CENTER = 4;
var MIDDLE_RIGHT = 5;
var LOWER_LEFT = 6;
var LOWER_MIDDLE = 7;
var LOWER_RIGHT = 8;

/*********************************************************************************
Common functions.
*********************************************************************************/

function getWindowHeight(){
	if(document){
		if(document.body.clientHeight)
			return document.body.clientHeight;
		else if(window && window.innerHeight)
			return window.innerHeight;
	}
	return 0;
}

function getWindowWidth(){
	if(document){
		if(document.body.clientWidth)
			return document.body.clientWidth;
		else if(window && window.innerWidth)
			return window.innerWidth;
	}
	return 0;
}

function getObject(wnd, id)
{
	try
	{
		var Window = new Object(wnd);
		return Window.document.getElementById(id);
	}
	catch(e)
	{
		alert('There was an error in getObject:\n' + e.message);
	}
	return null;
}

function getLayerHtml(id, l, t, w, h, c, bIncludeEnd)
{
	if(id == null)
		return '<div style="position:absolute; left:' + l + 'px; top:' + t + 'px; width:' + w + 'px; height:' + h + 'px; overflow:hidden; visibility:visible; background-color:' + c + ';"></div>';
	else
		return '<div id = ' + id + ' style="position:absolute; left:' + l + 'px; top:' + t + 'px; width:' + w + 'px; height:' + h + 'px; overflow:hidden; visibility:visible; background-color:' + c + ';"></div>';
}

function startLayer(wnd, id, content)
{
	wnd.document.writeln('<div id = ' + id + '>');
	if(content != null)
		wnd.document.writeln(content);
}

function endLayer(wnd)
{
	wnd.document.writeln('</div>');
}

/*********************************************************************************
RolloverImage.
*********************************************************************************/
function RolloverImage(image, rolloverImage)
{
	this.image = new Image();
	this.image.src = image;
	
	this.rolloverImage = new Image();
	this.rolloverImage.src = rolloverImage;
	
	this.getImage = function(){return this.image;}
	this.getRolloverImage = function(){return this.rolloverImage;}
}

/*********************************************************************************
Point.
*********************************************************************************/

function Point(x,y)
{
	this.x = x;
	this.y = y;
		
	this.getX = function(){return this.x;}
	this.getY = function(){return this.y;}
	
	this.setX = function(x){this.x = x;}
	this.setY = function(y){this.y = y;}
}

/*********************************************************************************
Line.
*********************************************************************************/

function Line(startPoint,endPoint)
{
	this.startPoint = startPoint;
	this.endPoint = endPoint;
		
	this.getStartPoint = function(){return this.startPoint;}
	this.getEndPoint = function(){return this.endPoint;}
	
	this.setStartPoint = function(s){this.startPoint = s;}
	this.setEndPoint = function(e){this.endPoint = e;}
	
	this.convertUnits = function(theDist1,mUnits,sUnits){
		if(theDist1 == 0 || mUnits == null || sUnits == null)
			return 0;
			
		// convert the amounts to new units
		// Note: decimal are not hard coded to allow use with locales using commas instead of points.	
		var theDist = parseFloat(theDist1);
		var mDistance = theDist;
		
		mUnits = mUnits.toUpperCase(mUnits);
		sUnits = sUnits.toUpperCase(sUnits);
	
		if(mUnits == "FEET")
		{
			if (sUnits=="MILES")
				mDistance = theDist / 5280;
			else if (sUnits == "METERS")
				mDistance = theDist * (3048/10000);
			else if (sUnits == "KILOMETERS")
				mDistance = theDist * (3048/10000000);
		}
		else
		{
			if (sUnits=="MILES") 
				mDistance = theDist * (6213711922/10000000000000);
			else if (sUnits == "FEET") 
				mDistance = theDist * (3280839895/1000000000);
			else if (sUnits == "KILOMETERS")
				mDistance = theDist / 1000;
		}
		
		var numDecimals = 2;
		var u = Math.pow(10,numDecimals);
		mDistance = parseInt(mDistance * u + (5/10)) / u;
		return mDistance;
	}
	
	this.getDistance = function(mapUnits, displayUnits){
		if(this.startPoint == null || this.endPoint == null)
			return 0;
		
		mapUnits = mapUnits.toUpperCase(mapUnits);
		displayUnits = displayUnits.toUpperCase(displayUnits);
			
		var distance = 0;
		if(mapUnits =="DEGREES")
		{
			mapUnits = "FEET";
			
			var r = Math.PI / 180;
			var Lon1 = this.startPoint.getX() * r;
			var Lon2 = this.endPoint.getX() * r;
			var Lat1 = this.startPoint.getY() * r;
			var Lat2 = this.endPoint.getY() * r;
			var LonDist = Math.abs(Lon1-Lon2);
			var LatDist = Math.abs(Lat1-Lat2);
		
			var A = Math.pow(Math.sin(LatDist / 2),2) + Math.cos(Lat1) * Math.cos(Lat2) * Math.pow(Math.sin(LonDist /2),2);
			var C = 2 * Math.asin(Math.min(1, Math.sqrt(A)));
			var D = (3963 - 13 * Math.sin((Lat1 + Lat2) / 2)) * C;
			distance = D * 5280;
		}
		else
		{
			var dX = Math.abs(this.endPoint.getX() - this.startPoint.getX());
			var dY = Math.abs(this.endPoint.getY() - this.startPoint.getY());
			distance = Math.sqrt(Math.pow(dX,2) + Math.pow(dY,2));
		}
		
		distance = this.convertUnits(distance,mapUnits,displayUnits);
		var numDecimals = 2;
		var u = Math.pow(10,numDecimals);
		distance = parseInt(distance*u+(5/10))/u;
		return distance;
		
		//return this.convertUnits(distance,mapUnits,displayUnits);
	}
	
	//TODO - this function assumes geometry is measured in decimal degrees.
	//valid values for units are: miles, meters, kilometers.
	this.getDistanceOld = function(units){
		if(this.startPoint == null || this.endPoint == null)
			return 0;
			
		if(units == null)
			units = 'miles';
		units = units.toLowerCase();
		
		var r = Math.PI/180;
		var lat1 = this.startPoint.getX() * r;
		var lon1 = this.startPoint.getY() * r;
		var lat2 = this.endPoint.getX() * r;
		var lon2 = this.endPoint.getY() * r;
		
		var latDistance = Math.abs(lat1-lat2);
		var lonDistance = Math.abs(lon1-lon2);			

		var a = Math.pow(Math.sin(latDistance / 2),2) + Math.cos(lat1) * Math.cos(lat2) * Math.pow(Math.sin(lonDistance/2),2);
		var c = 2 * Math.asin(Math.min(1, Math.sqrt(a)));
		var d = ((3963 - 13 * Math.sin((lat1 + lat2) / 2)) * c);// * 5280;
		var e = Math.pow(10,4);
		
		var scale = 1;
		var mapUnits = 'feet'; //or meters
		
		switch(mapUnits){
			case 'feet':
				switch(units){
					case 'miles':
						scale = 1/5280;
						break;
					case 'meters':
						scale = 3048/10000;
						break;
					case 'kilometers':
						scale = 3048/10000000;
						break;
				}
				break;
			case 'meters':
				switch(units){
					case 'miles':
						scale = 6213711922/10000000000000;
						break;
					case 'feet':
						scale = 3280839895/1000000000;
						break;
					case 'kilometers':
						scale = 1/1000;
						break;
				}
				break;
		}
		d *= scale;
		return parseInt(d * e + (5/10)) / e
	}
}

/*********************************************************************************
Envelope. Designed to hold extent information in map coordinates.
*********************************************************************************/

function Envelope(left, top, right, bottom)
{
	this.left = left;
	this.top = top;
	this.right = right;
	this.bottom = bottom;
	
	this.setLeft = function(l){this.left = l;}
	this.setTop = function(t){this.top = t;}
	this.setRight = function(r){this.right = r;}
	this.setBottom = function(b){this.bottom = b;}
	
	this.getLeft = function(){return this.left;}
	this.getTop = function(){return this.top;}
	this.getRight = function(){return this.right;}
	this.getBottom = function(){return this.bottom;}
	
	this.getWidth = function(){return Math.abs(this.right - this.left);}
	this.getHeight = function(){return Math.abs(this.top - this.bottom);}
	this.toString = function(){return this.left + "," + this.top + "," + this.right + "," + this.bottom;}
	this.clone = function(){return new Envelope(this.left, this.top, this.right, this.bottom);}
	
	this.centerAt = function(x, y){
		var w = this.getWidth() / 2;
		var h = this.getHeight() / 2;
		
		this.setLeft(x - w);
		this.setRight(x + w);
		this.setTop(y - h);
		this.setBottom(y + h);
	}
	
	this.contains = function(x, y){
		return x >= this.getLeft() && x <= this.getLeft() && y >= this.getBottom() && y <= this.getTop();
	}
}

/*********************************************************************************
DisplayTransformation. 	This object is responible for converting image coordinates
						to map coordinates and vice versa.
*********************************************************************************/

function DisplayTransformation()
{	
	this.extent = null;
	this.width = null;
	this.height = null;
	this.left = null;
	this.top = null;
	
	this.setExtent = function(e){this.extent = e;}
	this.setSize = function(w, h){this.width = w;this.height = h;}
	this.setBounds = function(l, t, w, h){
		this.left = l;
		this.top = t;
		this.width = w;
		this.height = h;
	}
	
	this.isValid = function(){
		return this.extent != null && this.left != null && this.top != null && this.width != null && this.height != null;
	}
	
	this.getPixelWidth = function(){
		if(!this.isValid())
			return null;		
		return parseFloat(this.extent.getWidth()) / parseFloat(this.width);
	}
	
	this.getPixelHeight = function(){
		if(!this.isValid())
			return null;		
		return parseFloat(this.extent.getHeight()) / parseFloat(this.height);
	}
	
	this.getImageX = function(x){
		if(!this.isValid())
			return null;
		return parseInt(((x - this.extent.getLeft())/this.getPixelWidth()) + this.left);
	}
	
	this.getImageY = function(y){
		if(!this.isValid())
			return null;
		return parseInt(((parseFloat(this.extent.getTop()) - parseFloat(y))/this.getPixelHeight()) + this.top);
	}
	
	this.getMapX = function(x){
		if(!this.isValid())
			return null;
		x -= this.left;
		return parseFloat((parseFloat(x) * this.getPixelWidth()) + this.extent.getLeft());
	}
	
	this.getMapY = function(y){
		if(!this.isValid())
			return null;
		y -= this.top;
		return parseFloat(-1 * ((parseFloat(y) * this.getPixelHeight()) - this.extent.getTop()));
	}
	
	this.getMapPoint = function(x, y){return new Point(this.getMapX(x), this.getMapY(y));}
	this.getImagePoint = function(x, y){return new Point(this.getImageX(x), this.getImageY(y));}
	
	//returns an envelope from image coorindates.
	this.getEnvelope = function(left, top, right, bottom){
		var l = this.getMapX(left);
		var t = this.getMapY(top);
		var r = this.getMapX(right);
		var b = this.getMapY(bottom);
		return new Envelope(l,t,r,b);
	}
	
	//returns an envelope from a map extent in image coorindates.
	this.getBounds = function(env){
		if(env == null)
			return null;
			
		var l = this.getImageX(env.getLeft());
		var t = this.getImageY(env.getTop());
		var r = this.getImageX(env.getRight());
		var b = this.getImageY(env.getBottom());
		return new Envelope(l,t,r,b);
	}
}

/*********************************************************************************
Label. 	Simple layer that only contains text. Posistion is not updatable.
*********************************************************************************/

function Label(wnd, id, text, cssClass)
{
	this.wnd = wnd;
	this.id = id;
	
	if(cssClass == null)
		this.wnd.document.writeln('<div id = ' + this.id + '>' + text + '</div>');
	else
		this.wnd.document.writeln('<div id = ' + this.id + ' class="' + cssClass + '">' + text + '</div>');
		
	this.setText = function(t){this.wnd.document.getElementById(this.id).innerHTML = t;}
	this.getText = function(){return this.wnd.document.getElementById(this.id).innerHTML;}
}

/*********************************************************************************
LabelLayer. A label that has similar behavior to a layer. Height and width are
			not specified so that the content can auto-expand.
*********************************************************************************/

function LabelLayer(wnd, id)
{
	this.wnd = wnd;
	this.id = id;
	
	var html = '<div id = ' + this.id + ' style="position:absolute; left:0px; top:0px; overflow:hidden; visibility:hidden"></div>';
	this.wnd.document.writeln(html);
	
	this.layer = getObject(this.wnd, this.id);	
	if(this.layer == null)
		this.wnd.status = 'LabelLayer: Failed to load layer with id: ' + this.id;
	
	if(this.layer != null)
		this.layer = this.layer.style;
		
	this.getLayerObject = function(){return this.wnd.document.getElementById(this.id);}
	
	this.valid = function(data){
		return !isNaN(data);
	}
		
	this.visible = false;
	this.setLeft = function(l){if(this.valid(l))this.layer.left = l;}
	this.setTop = function(t){if(this.valid(t))this.layer.top = t;}
	this.setFont = function(f){this.layer.fontFamily = f;}
	this.setFontSize = function(s){this.layer.fontSize = s;}
	this.setFontWeight = function(w){this.layer.fontWeight = w;}
	this.setColor = function(c){this.layer.backgroundColor = c;}
	this.setFontColor = function(c){this.layer.color = c;}
	this.setVisible = function(b){this.visible = b; this.layer.visibility = (b ? 'visible' : 'hidden');}	
	this.setText = function(c){this.getLayerObject().innerHTML = c;}
	
	return false;
}

/*********************************************************************************
Layer. 	This object is responible for dhtml functionality.
*********************************************************************************/

function Layer(wnd, id)
{
	this.wnd = wnd;
	this.id = id;

	var html = '<div id = ' + this.id + ' style="position:absolute; left:0px; top:0px; width:0px; height:0px; overflow:hidden; visibility:hidden"></div>';
	
	//if(this.wnd.document.body.insertAdjacentHTML)
	//	this.wnd.document.body.insertAdjacentHTML("beforeEnd", html);
	//else
		this.wnd.document.writeln(html);
	//this.wnd.document.body.innerHTML += html;
	
	this.layer = getObject(this.wnd, this.id);	
	if(this.layer == null){
		this.wnd.status = 'Layer: Failed to load layer with id: ' + this.id;
	}
	
	if(this.layer != null)
		this.layer = this.layer.style;
		
	this.getLayer = function(){return this.layer;}
	this.getLayerObject = function(){return this.wnd.document.getElementById(this.id);}
		
	this.valid = function(data){
		return !isNaN(data);
	}
	
	this.visible = false;
	this.setLeft = function(l){if(this.valid(l))this.layer.left = l;}
	this.setTop = function(t){if(this.valid(t))this.layer.top = t;}
	this.setWidth = function(w){if(this.valid(w))this.layer.width = w;}
	this.setHeight = function(h){if(this.valid(h))this.layer.height = h;}
	this.setLocation = function(l,t,w,h){
		this.setLeft(l);
		this.setTop(t);
		this.setWidth(w);
		this.setHeight(h);
	}
	this.setColor = function(c){this.layer.backgroundColor = c;}
	this.setVisible = function(b){this.visible = b; this.layer.visibility = (b ? 'visible' : 'hidden');}
	this.setOverflow = function(o){this.layer.overflow = o;}
	this.setContent = function(c){this.getLayerObject().innerHTML = c;}
	this.appendContent = function(c){this.wnd.document.getElementById(this.id).innerHTML += c;}
	
	this.setBorder = function(type, color, width){
		this.layer.borderStyle = type;
		this.layer.borderColor = color;
		this.layer.borderWidth = width;
	}
	
	this.getWidth = function(){return parseInt(this.layer.width);}
	this.getHeight = function(){return parseInt(this.layer.height);}
	this.getLeft = function(){return parseInt(this.layer.left);}
	this.getTop = function(){return parseInt(this.layer.top);}
	this.getRight = function(){return parseInt(this.getLeft() + this.getWidth());}
	this.getBottom = function(){return parseInt(this.getTop() + this.getHeight());}
	this.getCenterX = function(){return parseInt(this.getLeft() + this.getWidth() / 2);}
	this.getCenterY = function(){return parseInt(this.getTop() + this.getHeight() / 2);}
	this.getVisible = function(){return this.visible;}
	this.getOverflow = function(){return this.layer.overflow;}
	
	this.bounds = null;
	this.setBounds = function(env){this.bounds = env;}
	this.updateBounds = function(){
		if(this.bounds == null)
			return false;
			
		var clipLeft = 0;
		var clipTop = 0;
		var clipRight = 0;
		var clipBottom = 0;
		
		if(this.getLeft() < this.bounds.getLeft())
			clipLeft = Math.abs(this.getLeft());
			
		if(this.getTop() < this.bounds.getTop())
			clipTop = Math.abs(this.getTop());
			
		clipRight =  this.bounds.getRight() - this.getLeft();
		clipBottom =  this.bounds.getBottom() - this.getTop();	
		//alert(clipLeft + ' ' + clipTop + ' ' + clipRight + ' ' + clipBottom);
		
		this.clip(clipLeft, clipTop, clipRight, clipBottom);
		return false;
	}
	
	this.clip = function(clipLeft, clipTop, clipRight, clipBottom){
		if(clipLeft < 0)
			clipLeft = 0;
		if(clipTop < 0)
			clipTop = 0;
		if(clipRight < 0)
			clipRight = 0;
		if(clipBottom < 0)
			clipBottom = 0;
			
		this.layer.clip = 'rect(' + clipTop + ' ' +  clipRight + ' ' + clipBottom + ' ' + clipLeft +')';
		return false;
	}
	
	this.clearClip = function(){
		this.layer.clip = '';
		return false;
	}
	
	this.updatePosition = function(position, l, t, w, h){
		var iw = this.getWidth();
		var ih = this.getHeight();
		var il = 0;
		var it = 0;
		
		switch(position)
		{
		case UPPER_LEFT:
			il = l;
			it = t;
			break;
		case UPPER_MIDDLE:
			il = (l + (w/2)) - (iw / 2);
			it = t;
			break;
		case UPPER_RIGHT:
			il = (l + w) - iw;
			it = t;
			break;
		case MIDDLE_LEFT:
			il = l;
			it = (t + (h/2)) - (ih/2);
			break;
		case CENTER:
			il = (l + (w/2)) - (iw / 2);
			it = (t + (h/2)) - (ih/2);
			break;
		case MIDDLE_RIGHT:
			il = (l + w) - iw;
			it = (t + (h/2)) - (ih/2);
			break;
		case LOWER_LEFT:
			il = l;
			it = (t + h) - ih;
			break;
		case LOWER_MIDDLE:
			il = (l + (w/2)) - (iw / 2);
			it = (t + h) - ih;
			break;
		case LOWER_RIGHT:
			il = (l + w) - iw;
			it = (t + h) - ih;
			break;
		}
		this.setLeft(il);
		this.setTop(it);
		return false;
	}
}

/*********************************************************************************
Rectangle. 	This object manages 4 layers to draw rectangles.
*********************************************************************************/

function Rectangle(wnd, id, stroke, color)
{
	this.wnd = wnd;
	this.id = id;
	this.stroke = stroke;
	
	this.clipLeft = 0;
	this.clipTop = 0;
	this.clipRight = 0;
	this.clipBottom = 0;
	this.hasClip = false;
	
	startLayer(this.wnd, this.id);
	
	this.left = new Layer(wnd, id + "_left");
	this.top = new Layer(wnd, id + "_top");
	this.right = new Layer(wnd, id + "_right");
	this.bottom = new Layer(wnd, id + "_bottom");
	
	this.left.setColor(color);
	this.top.setColor(color);
	this.right.setColor(color);
	this.bottom.setColor(color);
	
	endLayer(this.wnd);
	
	this.setColor = function(c){
		this.left.setColor(c);
		this.top.setColor(c);
		this.right.setColor(c);
		this.bottom.setColor(c);
		return false;
	}
	
	this.setVisible = function(b){
		this.left.setVisible(b);
		this.right.setVisible(b);
		this.top.setVisible(b);
		this.bottom.setVisible(b);
		this.updateClip();
	}
	
	this.getLeft = function(){
		return this.left.getLeft();
	}
	
	this.getRight = function(){
		return this.right.getLeft();
	}
	
	this.getTop = function(){
		return this.top.getBottom();
	}
	
	this.getBottom = function(){
		return this.bottom.getTop();
	}
	
	this.getWidth = function(){
		return this.getRight() - this.getLeft();
	}
	
	this.getHeight = function(){
		return this.getBottom() - this.getTop();
	}
	
	this.contains = function(x, y){
		return x >= this.left.getLeft() && x <= this.right.getRight() &&
			   y >= this.top.getTop() && y <= this.bottom.getBottom();
	}
	
	//restricts the rectangle's sides to the input rectangle.
	this.clip = function(clipLeft, clipTop, clipRight, clipBottom){
		this.clipLeft = clipLeft;
		this.clipTop = clipTop;
		this.clipRight = clipRight;
		this.clipBottom = clipBottom;
		this.hasClip = true;
	}
	
	this.outsideOfClip = function(side){
		return (side.getLeft() < this.clipLeft && side.getRight() < this.clipLeft) ||
			   (side.getLeft() > this.clipRight && side.getRight() > this.clipRight) ||
			   (side.getTop() < this.clipTop && side.getBottom() < this.clipTop) ||
			   (side.getTop() > this.clipBottom && side.getBottom() > this.clipBottom);
	}
	
	this.updateClip = function(){
		if(!this.hasClip)
			return;
			
		if(this.outsideOfClip(this.left))
			this.left.setVisible(false);
		else
		{
			if(this.left.getTop() < this.clipTop)
			{
				this.left.setHeight(this.left.getBottom() - this.clipTop);
				this.left.setTop(this.clipTop);
			}
			else if(this.left.getBottom() > this.clipBottom)
				this.left.setHeight(this.clipBottom - this.left.getTop());
		}
		
		if(this.outsideOfClip(this.right))
			this.right.setVisible(false);
		else
		{
			if(this.right.getTop() < this.clipTop)
			{
				this.right.setHeight(this.right.getBottom() - this.clipTop);
				this.right.setTop(this.clipTop);
			}
			else if(this.right.getBottom() > this.clipBottom)
				this.right.setHeight(this.clipBottom - this.right.getTop());
		}
		
		if(this.outsideOfClip(this.top))
			this.top.setVisible(false);
		else
		{
			if(this.top.getLeft() < this.clipLeft)
			{
				this.top.setWidth(this.top.getRight() - this.clipLeft);
				this.top.setLeft(this.clipLeft);
			}
			else if(this.top.getRight() > this.clipRight)
				this.top.setWidth(this.clipRight - this.top.getLeft());
		}
		
		if(this.outsideOfClip(this.bottom))
			this.bottom.setVisible(false);
		else
		{
			if(this.bottom.getLeft() < this.clipLeft)
			{
				this.bottom.setWidth(this.bottom.getRight() - this.clipLeft);
				this.bottom.setLeft(this.clipLeft);
			}
			else if(this.bottom.getRight() > this.clipRight)
				this.bottom.setWidth(this.clipRight - this.bottom.getLeft());
		}
	}
	
	this.updateSide = function(layer, left, top, width, height, bVisible){
		layer.setLeft(left);
		layer.setTop(top);	
		layer.setWidth(width);
		layer.setHeight(height);
		layer.setVisible(bVisible);
	}
	
	this.update = function(left, top, right, bottom)
	{
		this.updateSide(this.left, left, top, this.stroke, Math.abs(bottom - top), true);
		this.updateSide(this.right, right - this.stroke, top, this.stroke, Math.abs(bottom - top), true);
		this.updateSide(this.top, left, top, right - left, this.stroke, true);
		this.updateSide(this.bottom, left, bottom - this.stroke, right - left, this.stroke, true);
	}
	
	this.reset = function()
	{	
		this.updateSide(this.left, 0, 0, 0, 0, false);
		this.updateSide(this.right, 0, 0, 0, 0, false);
		this.updateSide(this.top, 0, 0, 0, 0, false);
		this.updateSide(this.bottom, 0, 0, 0, 0, false);
	}
}

function Rectangle_FROM_BORDER(wnd, id, stroke, color)
{
	this.wnd = wnd;
	this.id = id;
	this.stroke = stroke;
	this.layer = new Layer(wnd, id);
	this.layer.setBorder('solid', color, stroke);
	
	this.setBounds = function(env){this.layer.setBounds(env);}
	
	this.clipLeft = 0;
	this.clipTop = 0;
	this.clipRight = 0;
	this.clipBottom = 0;
	this.hasClip = false;
	
	this.setVisible = function(b){
		this.layer.setVisible(b);
		this.updateClip();
	}
	
	this.getLeft = function(){
		return this.layer.getLeft();
	}
	
	this.getRight = function(){
		return this.layer.getRight();
	}
	
	this.getTop = function(){
		return this.layer.getTop();
	}
	
	this.getBottom = function(){
		return this.layer.getBottom();
	}
	
	this.getWidth = function(){
		return this.layer.getWidth();
	}
	
	this.getHeight = function(){
		return this.layer.getHeight();
	}
	
	this.contains = function(x, y){
		return x >= this.getLeft() && x <= this.getRight() &&
			   y >= this.getTop() && y <= this.getBottom();
	}
	
	//restricts the rectangle's sides to the input rectangle.
	this.clip = function(clipLeft, clipTop, clipRight, clipBottom){
		this.clipLeft = clipLeft;
		this.clipTop = clipTop;
		this.clipRight = clipRight;
		this.clipBottom = clipBottom;
		this.hasClip = true;
		
		this.layer.clip(clipLeft, clipTop, clipRight, clipBottom);
		return false;
		
	}
	
	this.updateClip = function(){
		if(!this.hasClip)
			return;
		return false;
	}
	
	this.update = function(left, top, right, bottom)
	{
		left += this.stroke;
		right -= this.stroke;
		top += this.stroke;
		bottom -= this.stroke;
		
		this.layer.setLeft(left);
		this.layer.setTop(top);	
		this.layer.setWidth(Math.abs(left - right));
		this.layer.setHeight(Math.abs(top - bottom));
		
		this.layer.updateBounds();
		
		this.layer.setVisible(true);
		return false;
	}
	
	this.reset = function()
	{	
		this.layer.setLeft(0);
		this.layer.setTop(0);	
		this.layer.setWidth(0);
		this.layer.setHeight(0);
		this.layer.setVisible(false);
		return false;
	}
}

/*********************************************************************************
RubberRectangle. 	This object facilitates a rectangle being drawn by a tool.
*********************************************************************************/

function RubberRectangle(wnd, id, stroke, color)
{
	this.startX = -1;
	this.startY = -1;
	this.currentX = -1;
	this.currentX = -1;
		
	this.rectangle = new Rectangle(wnd, id , stroke, color);
	
	this.getLeft = function(){return Math.min(this.startX, this.currentX);}
	this.getTop = function(){return Math.min(this.startY, this.currentY);}
	this.getRight = function(){return Math.max(this.startX, this.currentX);}
	this.getBottom = function(){return Math.max(this.startY, this.currentY);}
	
	this.start = function (x, y){
		this.startX = x;
		this.startY = y;
		this.currentX = x;
		this.currentX = y;
	}
	
	this.moveTo = function (x, y){
		this.currentX = x;
		this.currentY = y;
		
		var left = this.getLeft();
		var right = this.getRight();
		var top = this.getTop();
		var bottom = this.getBottom();
		this.rectangle.update(left, top, right, bottom);
	}
	
	this.stop = function (x, y){
		this.currentX = x;
		this.currentY = y;
		this.rectangle.reset();
	}
}

/*********************************************************************************
RubberPolyline. 	This object facilitates a line being drawn by a tool.
*********************************************************************************/

function RubberPolyline(wnd, id, stroke, color, increment, asPolygon, displayTransformation)
{
	this.wnd = wnd;
	this.id = id;
	this.stroke = stroke;
	this.color = color;
	this.increment = increment;
	this.points = new Array();
	this.asPolygon = asPolygon;
	this.displayTransformation = displayTransformation;
	
	startLayer(this.wnd, this.id);
	endLayer(this.wnd);
	
	this.getColor = function(){return this.color;}
	this.setColor = function(c){this.color = c;}
	
	this.getStroke = function(){return this.stroke;}
	this.setStroke = function(s){this.stroke = s;}
	
	this.getIncrement = function(){return this.increment;}
	this.setIncrement = function(i){this.increment = i;}
	
	this.getPoints = function(){return this.points;}
	this.setPoints = function(points){
		if(points != null)this.points = points;
		this.refresh();
	}
	this.clearPoints = function(){
		this.points = new Array();
		getObject(this.wnd, this.id).innerHTML = '';
	}
	
	//This function will always return a point in image coordinates.
	//Useful for drawing.
	this.getPoint = function(index, bInMapCoords){
		var point = this.points[index];
		if(this.displayTransformation != null && !bInMapCoords)
			point = this.displayTransformation.getImagePoint(point.getX(), point.getY());
		return point;
	}
	
	//addPoint expects x and y to be in image coordinates.
	//if this object had a displayTransformation, then it will internally
	//add the points in map coordinates.
	this.addPoint = function(x, y){
		if(this.displayTransformation != null)
			this.points[this.points.length] = this.displayTransformation.getMapPoint(x, y);
		else
			this.points[this.points.length] = new Point(x, y);
		this.refresh();
		return false;
	}
	
	this.getLines = function(bInMapCoords){
		var lines = new Array();
		var count = this.points.length;
		
		if(count > 1){
			var index = 1;
			for(index = 1; index < this.points.length; index++)
				lines[lines.length] = new Line(this.getPoint(index, bInMapCoords), this.getPoint(index-1, bInMapCoords));
				
			if(this.asPolygon && count > 1)
				lines[lines.length] = new Line(this.getPoint(count-1, bInMapCoords), this.getPoint(0, bInMapCoords));
		}
		return lines;
	}
	
			/*var index = 1;
			for(index = 1; index < this.points.length; index++)
				lines[lines.length] = new Line(new Point(this.points[index].getX(), this.points[index].getY()), new Point(this.points[index-1].getX(), this.points[index-1].getY()));
				
			if(this.asPolygon && count > 1)
				lines[lines.length] = new Line(new Point(this.points[count-1].getX(), this.points[count-1].getY()), new Point(this.points[0].getX(), this.points[0].getY()));*/
	
	this.makeLine = function(x1, y1, x2, y2){
		var html = '';
		var dx = x1 - x2;
		var dy = y1 - y2;
		var hyp = Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2));
		var ang = Math.atan2(dx, dy);
		var angX = Math.cos(ang);
		var angY = Math.sin(ang);
	
		for(var i = 0; i < hyp-this.increment; i++){
			var left = x2 + (angY * i);	
			var top = y2 + (i * angX);
			html += getLayerHtml(null, left, top, this.stroke, this.stroke, this.color);
			i+=this.increment;
		}
		return html;
	}
	
	this.refresh = function(){
		var lines = this.getLines();
		var index = 0;
		var html = '';
		var x1, y1, x2, y2;
		for(index = 0; index < lines.length; index++){
			html += this.makeLine(lines[index].getStartPoint().getX(), lines[index].getStartPoint().getY(), lines[index].getEndPoint().getX(), lines[index].getEndPoint().getY());
		}
		getObject(this.wnd, this.id).innerHTML = html;
		return false;
	}
	return false;
}

/*********************************************************************************
ImageLoader.
*********************************************************************************/

function ImageLoader(wnd, id)
{	
	this.wnd = wnd;
	this.id = id;
	this.position = CENTER;
	
	startLayer(this.wnd, this.id);	
	this.imageLayer = new Layer(wnd, id + '_IMAGE_LOADER_LAYER');
	endLayer(this.wnd);
	
	this.visible = false;
	this.setVisible = function(b){
		this.imageLayer.setVisible(b);
		this.visible = b;
	}
	
	this.load = function(url, width, height, position){
		this.position = position;
		
		var content = '<img src = "' + url + '">';
		this.imageLayer.setLeft(0);
		this.imageLayer.setTop(0);
		this.imageLayer.setWidth(width);
		this.imageLayer.setHeight(height);
		this.imageLayer.setContent(content);
		this.setVisible(false);
	}
	
	//updates the posisition of the image loader based on the map's current size and location.
	this.updatePosition = function(l, t, w, h)
	{
		this.imageLayer.updatePosition(this.position, l, t, w, h);
		return false;
	}
}

/*********************************************************************************
DataLoader.
*********************************************************************************/

function DataLoader(wnd, id)
{	
	this.wnd = wnd;
	this.id = id;
	this.position = CENTER;
	
	startLayer(this.wnd, this.id);	
	this.imageLayer = new Layer(wnd, id + '_DATA_LOADER_LAYER');
	endLayer(this.wnd);
	
	this.visible = false;
	this.setVisible = function(b){
		this.imageLayer.setVisible(b);
		this.visible = b;
	}
	
	this.load = function(url, width, height, position){
		this.position = position;
		
		var content = '<img src = "' + url + '">';
		this.imageLayer.setLeft(0);
		this.imageLayer.setTop(0);
		this.imageLayer.setWidth(width);
		this.imageLayer.setHeight(height);
		this.imageLayer.setContent(content);
		this.setVisible(false);
	}
	
	//updates the posisition of the image loader based on the map's current size and location.
	this.updatePosition = function(l, t, w, h)
	{
		this.imageLayer.updatePosition(this.position, l, t, w, h);
		return false;
	}
}

/*********************************************************************************
Legend.
*********************************************************************************/

function Legend(wnd, id)
{	
	this.wnd = wnd;
	this.id = id;
	this.position = UPPER_LEFT;
	
	startLayer(this.wnd, this.id);	
	this.legendLayer = new Layer(wnd, id + '_LEGEND_LAYER');
	endLayer(this.wnd);
	
	this.getLegendLayer = function(){return this.legendLayer;}
	
	this.visible = false;
	this.setVisible = function(b){
		this.legendLayer.setVisible(b);
		this.visible = b;
	}
	
	this.load = function(url, width, height, position){
		this.position = position;
		
		var content = '<img src = "' + url + '">';
		this.legendLayer.setLeft(0);
		this.legendLayer.setTop(0);
		this.legendLayer.setWidth(width);
		this.legendLayer.setHeight(height);
		this.legendLayer.setContent(content);
		this.setVisible(false);
	}
	
	this.updatePosition = function(l, t, w, h)
	{
		this.legendLayer.updatePosition(this.position, l, t, w, h);
		return false;
	}
}

/*********************************************************************************
OverviewMap.
*********************************************************************************/

function OverviewMap(wnd, id)
{	
	this.wnd = wnd;
	this.id = id;
	this.position = UPPER_LEFT;
	this.extent = null;
	this.fullExtent = null;
	this.displayTransformation = new DisplayTransformation();
	this.getDisplayTransformation = function(){return this.displayTransformation;}
	
	startLayer(this.wnd, this.id);	
	this.mapLayer = new Layer(wnd, id + '_MAP_LAYER');
	this.box = new Rectangle(wnd, id + '_box', 2, 'red');
	this.border = new Rectangle(this.wnd, this.id + '_border', 2, 'black');
	this.smallImageLayer = new Layer(wnd, id + '_small_image');
	endLayer(this.wnd);
	
	this.getMapLayer = function(){return this.mapLayer;}
	this.getBox = function(){return this.box;}
	
	this.visible = false;
	this.setVisible = function(b){
		this.border.setVisible(b);
		this.mapLayer.setVisible(b);
		if(b)
		{
			var showSmall = this.showSmallImage();
			this.box.setVisible(!showSmall);
			
			if(showSmall)
				this.smallImageLayer.setVisible(this.border.contains(this.smallImageLayer.getCenterX(), this.smallImageLayer.getCenterY()));
			else{
				this.smallImageLayer.setVisible(false);
				this.box.updateClip();
			}
		}
		else
		{
			this.box.setVisible(false);
			this.smallImageLayer.setVisible(false);
		}
		this.visible = b;
	}
	
	this.showSmallImage = function(){
		return this.box.getWidth() < this.smallImageLayer.getWidth() || this.box.getHeight() < this.smallImageLayer.getHeight();
	}
	
	//this function loads the overview map's image, sets the display transformation's extent and populates the underlying layer.
	this.load = function(url, fullExtent, width, height, position, smallImageUrl, smallImageWidth, smallImageHeight){
		this.position = position;
		this.fullExtent = fullExtent;
		this.displayTransformation.setExtent(this.fullExtent);
		this.displayTransformation.setBounds(0, 0, width, height);
	
		var content = '<img src = "' + url + '">';
		this.mapLayer.setLeft(0);
		this.mapLayer.setTop(0);
		this.mapLayer.setWidth(width);
		this.mapLayer.setHeight(height);
		this.mapLayer.setContent(content);
		
		content = '<img src = "' + smallImageUrl + '">';
		this.smallImageLayer.setLeft(0);
		this.smallImageLayer.setTop(0);
		this.smallImageLayer.setWidth(smallImageWidth);
		this.smallImageLayer.setHeight(smallImageHeight);
		this.smallImageLayer.setContent(content);	
		this.setVisible(this.visible);
	}

	//this function will draw the map's extent on the overview map.	
	this.updateExtent = function(e){
		this.extent = e;
		var left = this.displayTransformation.getImageX(e.getLeft());
		var top = this.displayTransformation.getImageY(e.getTop());
		var right = this.displayTransformation.getImageX(e.getRight());
		var bottom = this.displayTransformation.getImageY(e.getBottom());
		
		//this.box.setBounds(new Envelope(left, top, right, bottom));
		this.box.update(left, top, right, bottom);
		this.box.clip(this.border.getLeft(), this.border.getTop(), this.border.getRight(), this.border.getBottom());		
		
		this.smallImageLayer.setLeft((right - left) / 2 + left);
		this.smallImageLayer.setTop((bottom - top) / 2 + top);
		this.setVisible(this.visible);
	}
	
	this.updatePosition = function(l, t, w, h){
		this.mapLayer.updatePosition(this.position, l, t, w, h);
		this.displayTransformation.setBounds(this.mapLayer.getLeft(), this.mapLayer.getTop(), this.mapLayer.getWidth(), this.mapLayer.getHeight());
		this.border.update(this.mapLayer.getLeft(), this.mapLayer.getTop(), this.mapLayer.getRight(), this.mapLayer.getBottom());
		return false;
	}
	
	//returns an extent centered on the input point.
	this.getExtent = function(x, y){
		var centerX = this.displayTransformation.getMapX(x);
		var centerY = this.displayTransformation.getMapY(y);
		var e = this.extent.clone();
		e.centerAt(centerX, centerY);
		return e;
	}
}

/*********************************************************************************
Map.
*********************************************************************************/

function Map(w, id)
{
	this.wnd = w;
	this.id = id;
	
	this.units = null;
	this.setUnits = function(units){this.units = units;}
	this.getUnits = function(){return this.units;}
	
	this.extents = new Array();
	this.extentIndex = -1;
	this.addExtent = true;
	
	this.updateListeners = new Array();
	this.addUpdateListener = function(obj){this.updateListeners[this.updateListeners.length] = obj;}
	this.notifyUpdateListeners = function(){
		var i = 0;
		for(i = 0; i < this.updateListeners.length; i++)
			if(this.updateListeners[i].onMapUpdate)
				this.updateListeners[i].onMapUpdate();
		return false;
	}
	
	startLayer(this.wnd, this.id);
	
	this.mapLayer = new Layer(w, id + '_MAP_LAYER');
	this.mapLayer.setColor('white');
	this.mapLayer.setVisible(true);
	this.highlightImage = new Layer(w, id + '_highlight_image');
	
	endLayer(this.wnd);	
	
	this.getMapLayer = function(){return this.mapLayer;}
	
	this.displayTransformation = new DisplayTransformation();
	this.getDisplayTransformation = function(){return this.displayTransformation;}
	
	this.extent = null;
	this.getExtent = function(){return this.extent;}
	this.getBounds = function(){return this.displayTransformation.getBounds(this.getExtent());}
	
	this.scale = 0;
	this.getScale = function(){return this.scale;}
	
	this.usingHighlightImage = false;
	this.loadHighlightImage = function(url, width, height){
		this.usingHighlightImage = true;
		
		var content = '<img src = "' + url + '">';
		this.highlightImage.setLeft(0);
		this.highlightImage.setTop(0);
		this.highlightImage.setWidth(width);
		this.highlightImage.setHeight(height);
		this.highlightImage.setContent(content);	
		return false;
	}
	
	//shows the extent using the highlight rectangle.
	//e is an Envelope object in map coordinates.
	this.highlightRectangle = new Rectangle(w, id + '_highlight', 2, 'red');
	this.showExtent = function(e){		
		var left = this.displayTransformation.getImageX(e.getLeft());
		var top = this.displayTransformation.getImageY(e.getTop());
		var right = this.displayTransformation.getImageX(e.getRight());
		var bottom = this.displayTransformation.getImageY(e.getBottom());
		
		var width = Math.abs(right-left);
		var height = Math.abs(top-bottom);
		var bShowRectangle = true;
		
		if(this.usingHighlightImage){
			var bShowImage = (width < this.highlightImage.getWidth() || height < this.highlightImage.getHeight());
			if(bShowImage){
				this.highlightRectangle.reset();
				this.highlightImage.setLeft(width / 2 + left);
				this.highlightImage.setTop(height / 2 + top);
				this.highlightImage.setVisible(true);
				bShowRectangle = false;
			}
		}
		
		if(bShowRectangle){
			var nThreshhold = 10;
			if(width < nThreshhold || height < nThreshhold){
				var centerX = width / 2 + left;
				var centerY = height / 2 + top;
				
				left = centerX - nThreshhold/2;
				top = centerY - nThreshhold/2;
				right = centerX + nThreshhold/2;
				bottom = centerY + nThreshhold/2;
			}
			this.highlightRectangle.update(left, top, right, bottom);
		}
			
		//this.highlightRectangle.clip(0, 0, right, bottom);
		//this.highlightRectangle.setBounds(this.getBounds());
		
		//this.highlightRectangle.update(left, top, right, bottom);
		return false;
	}
	
	//shows an extent buffered by size around the point.
	//x and y are in map coordinates.
	this.showPoint = function(x, y, size){
		var left = (this.displayTransformation.getImageX(x) - size);
		var top = (this.displayTransformation.getImageY(y) - size);
		var right = (this.displayTransformation.getImageX(x) + size);
		var bottom = (this.displayTransformation.getImageY(y) + size);
		
		//this.highlightRectangle.clip(0, 0, right, bottom);
		//this.highlightRectangle.setBounds(this.getBounds());
		this.highlightRectangle.setVisible(true);
		this.highlightRectangle.update(left, top, right, bottom);
		return false;
	}
	
	this.hideExtent = function(){
		this.highlightRectangle.reset();
		this.highlightImage.setVisible(false);
		return false;
	}
	
	this.onBeforeIdentify = null;
	
	this.x = 0;
	this.y = 0;
	this.width = 0;
	this.height = 0;
	
	this.setLocation = function(l, t, w, h){
		this.x = l;
		this.y = t;
		this.width = w;
		this.height = h;
	
		this.mapLayer.setLeft(l);
		this.mapLayer.setTop(t);
		this.mapLayer.setWidth(this.width);
		this.mapLayer.setHeight(this.height);
		this.displayTransformation.setBounds(l,t,w,h);
		return false;
	}
	
	this.setSize = function(w,h){
		this.width = w;
		this.height = h;
		
		this.mapLayer.setWidth(w);
		this.mapLayer.setHeight(h);
		this.displayTransformation.setSize(w,h);
		return false;	
	}
	
	this.getLeft = function(){return this.x;}
	this.getTop = function(){return this.y;}
	this.getRight = function(){return this.getLeft() + this.getWidth();}
	this.getBottom = function(){return this.getTop() + this.getHeight();}
	this.getWidth = function(){return this.mapLayer.getWidth();}
	this.getHeight = function(){return this.mapLayer.getHeight();}
	
	this.overviewMap = null;
	this.getOverviewMap = function(){return this.overviewMap;}
	this.useOverviewMap = false;
	this.setUseOverviewMap = function(b){this.useOverviewMap = b;}
	this.getUseOverviewMap = function(){return this.useOverviewMap;}
	this.loadOverviewMap = function(){
		this.overviewMap = new OverviewMap(w, id + '_overview');
		this.hideOverviewMap();
		return false;
	}
	this.showOverviewMap = function(){
		if(this.overviewMap != null){
			this.overviewMap.updatePosition(this.x, this.y, this.mapLayer.getWidth(), this.mapLayer.getHeight());
			this.overviewMap.setVisible(true);
			this.overviewMap.updateExtent(this.getExtent());
		}
		return false;
	}
	this.hideOverviewMap = function(){
		if(this.overviewMap != null)
			this.overviewMap.setVisible(false);
		return false;
	}
	this.getOverviewMapVisible = function(){
		if(this.useOverviewMap && this.overviewMap != null)
			return this.overviewMap.getMapLayer().getVisible();
		return false;
	}
	
	this.legend = null;
	this.getLegend = function(){return this.legend;}
	this.useLegend = false;
	this.setUseLegend = function(b){this.useLegend = b;}
	this.getUseLegend = function(){return this.useLegend;}
	this.loadLegend = function(){
		this.legend = new Legend(w, id + '_legend');
		this.hideLegend();
	}
	this.showLegend = function(){
		if(this.legend != null){
			this.legend.updatePosition(this.x, this.y, this.mapLayer.getWidth(), this.mapLayer.getHeight());
			this.legend.setVisible(true);
		}
		return false;
	}
	this.hideLegend = function(){
		if(this.legend != null)
			this.legend.setVisible(false);
		return false;
	}
	this.getLegendVisible = function(){
		if(this.useLegend && this.legend != null)
			return this.legend.getLegendLayer().getVisible();
		return false;
	}
	
	this.imageLoader = null;
	this.getImageLoader = function(){return this.imageLoader;}
	this.loadImageLoader = function(){
		this.imageLoader = new ImageLoader(w, id + '_animation');
		this.hideImageLoader();
		return false;
	}
	this.showImageLoader = function(){
		if(this.imageLoader != null){
			this.imageLoader.updatePosition(this.x, this.y, this.mapLayer.getWidth(), this.mapLayer.getHeight());
			this.imageLoader.setVisible(true);
		}
		return false;
	}
	this.hideImageLoader = function(){
		if(this.imageLoader != null)
			this.imageLoader.setVisible(false);
		return false;
	}
	
	this.dataLoader = null;
	this.getDataLoader = function(){return this.dataLoader;}
	this.loadDataLoader = function(){
		this.dataLoader = new DataLoader(w, id + '_animation');
		this.hideDataLoader();
		return false;
	}
	this.showDataLoader = function(){
		if(this.dataLoader != null){
			this.dataLoader.updatePosition(this.x, this.y, this.mapLayer.getWidth(), this.mapLayer.getHeight());
			this.dataLoader.setVisible(true);
		}
		return false;
	}
	this.hideDataLoader = function(){
		if(this.dataLoader != null)
			this.dataLoader.setVisible(false);
		return false;
	}
	
	this.refreshAcetateLayers = function(){
		var bOverview = this.getOverviewMapVisible();
		var bLegend = this.getLegendVisible();
		
		if(bOverview)
			this.showOverviewMap();
			
		if(bLegend)
			this.showLegend();
		return false;
	}
	
	this.navigationForm = null;
	this.setNavigationForm = function(f){this.navigationForm = f;}
	
	this.identifyForm = null;
	this.setIdentifyForm = function(f){this.identifyForm = f;}
	
	this.onBeforeMapRefresh = null;
	this.notifyMapListeners = function(){
		if(this.onBeforeMapRefresh)
			this.onBeforeMapRefresh();
		return false;
	}
	
	this.initialize = function(l, t, w, h){
		if(this.navigationForm == null)
			alert('Map::initialize: Navigation form is null.');
			
		this.navigationForm.COMMAND.value = 'initialize';
		this.navigationForm.MAP_WIDTH.value = w;
		this.navigationForm.MAP_HEIGHT.value = h;
		this.setLocation(l, t, w, h);
		this.showImageLoader();
		this.notifyMapListeners();
		this.navigationForm.submit();	
		return false;
	}
	
	this.changeMapService = function(server, service){
		if(this.navigationForm == null)
			alert('Map::changeMapService: Navigation form is null.');
			
		this.navigationForm.COMMAND.value = 'changeMapService';
		this.navigationForm.MAP_WIDTH.value = this.width;
		this.navigationForm.MAP_HEIGHT.value = this.height;
		this.navigationForm.SERVER.value = server;
		this.navigationForm.SERVICE.value = service;
		
		this.showImageLoader();
		this.notifyMapListeners();
		this.navigationForm.submit();
		return false;
	}
	
	this.mapScaleLabel = null;
	this.mapScaleLabelPrefix = null;
	this.setMapScaleLabel = function(prefix, label){
		this.mapScaleLabelPrefix = prefix;
		this.mapScaleLabel = label;	
		return false;
	}
	this.updateMapScaleLabel = function(scale){
		if(this.mapScaleLabel == null)
			return false;
			
		var label = '';
		if(this.mapScaleLabelPrefix != null)
			label += this.mapScaleLabelPrefix;
		label += parseInt(scale);
		this.mapScaleLabel.setText(label);
		return false;
	}
	
	this.identify = function(left, top, right, bottom){
		if(this.identifyForm == null){
			alert('Map.identify: identify form is null.');
			return false;
		}
		
		if(this.onBeforeIdentify)
			this.onBeforeIdentify();
		
		this.showDataLoader();
		var e = this.displayTransformation.getEnvelope(left, top, right, bottom);
		this.identifyForm.EXTENT.value = e.toString();
		this.identifyForm.submit();
		return false;
	}
	
	this.refresh = function(){
		if(this.extent == null){
			alert('Map.refresh: extent is null.');
			return false;
		}
			
		var left = this.extent.left;
		var right = this.extent.right;
		var top = this.extent.top;
		var bottom = this.extent.bottom;
		this.zoomToExtent(left, top, right, bottom, true);
		return false;	
	}
	
	this.zoomToFullExtent = function(){
		if(this.navigationForm == null)
			alert('Map::zoomToFullExtent: Navigation form is null.');
			
		this.navigationForm.COMMAND.value = 'zoomToFullExtent';
		this.navigationForm.MAP_WIDTH.value = this.width;
		this.navigationForm.MAP_HEIGHT.value = this.height;
		this.showImageLoader();
		this.notifyMapListeners();
		this.navigationForm.submit();
		return false;
	}
	
	this.zoomToExtent = function(left, top, right, bottom, bMapCoords){
		if(this.navigationForm == null)
			alert('Map::zoomToExtent: Navigation form is null.');
		
		var e = null;
		if(bMapCoords)	
			e = new Envelope(left, top, right, bottom);
		else
			e = this.displayTransformation.getEnvelope(left, top, right, bottom);
			
		this.navigationForm.COMMAND.value = 'zoomToExtent';
		this.navigationForm.EXTENT.value = e.toString();
		this.navigationForm.MAP_WIDTH.value = this.width;
		this.navigationForm.MAP_HEIGHT.value = this.height;
		this.showImageLoader();
		this.notifyMapListeners();
		this.navigationForm.submit();
		return false;
	}
	
	this.zoomToScale = function(scale){
		if(this.navigationForm == null)
			alert('Map::zoomToScale: Navigation form is null.');
			
		this.navigationForm.COMMAND.value = 'zoomToScale';
		this.navigationForm.SCALE.value = scale;
		this.navigationForm.MAP_WIDTH.value = this.width;
		this.navigationForm.MAP_HEIGHT.value = this.height;
		this.showImageLoader();
		this.notifyMapListeners();
		this.navigationForm.submit();
		return false;
	}
	
	this.zoomToPoint = function(x, y, size){	
		var left = (this.displayTransformation.getImageX(x) - size);
		var top = (this.displayTransformation.getImageY(y) - size);
		var right = (this.displayTransformation.getImageX(x) + size);
		var bottom = (this.displayTransformation.getImageY(y) + size);
		
		return this.zoomToExtent(left, top, right, bottom, false);
	}
	
	this.goBack = function(){
		if(this.extentIndex > 0)
		{
			var extent = this.extents[--this.extentIndex];
			this.addExtent = false;
			this.zoomToExtent(extent.getLeft(), extent.getTop(), extent.getRight(), extent.getBottom(), true); 
		}
		return false;
	}
	
	this.goForward = function(){
		if(this.extentIndex < (this.extents.length - 1))
		{
			var extent = this.extents[++this.extentIndex];
			this.addExtent = false;
			this.zoomToExtent(extent.getLeft(), extent.getTop(), extent.getRight(), extent.getBottom(), true); 
		}	
		return false;
	}
	
	this.onUpdate = null;
	this.update = function(url, extent, scale){
		this.extent = extent;
		this.displayTransformation.setExtent(extent);
		this.scale = scale;
		
		if(this.addExtent)
		{
			this.extents[this.extents.length] = this.extent;
			this.extentIndex = this.extents.length - 1;
		}
		this.addExtent = true;
		
		//these next two lines are necessary in case the map was panned.
		this.mapLayer.setLeft(this.x);
		this.mapLayer.setTop(this.y);
		this.mapLayer.clip(0, 0, this.getWidth(), this.getHeight());
		
		var img = new Image();	//force the image to download before we perform the switch.
		img.src = url;
		
		var content = '<img src = "' + url + '">';
		this.mapLayer.setContent(content);

		//sometimes the red x displays instead of the image. This "seems" to fix the problem.
		this.mapLayer.setVisible(false);
		this.mapLayer.setVisible(true);
		
		if(this.overviewMap != null)
			this.overviewMap.updateExtent(extent);
			
		this.hideImageLoader();
		this.refreshAcetateLayers();
		this.updateMapScaleLabel(scale);
		
		if(this.onUpdate != null)
			this.onUpdate();
			
		this.notifyUpdateListeners();
		
		return false;
	}
	
	this.moveTo = function(x, y){
		this.mapLayer.setLeft(x);
		this.mapLayer.setTop(y);
		return false;
	}
	
	this.pan = function(xOffset, yOffset, clipLeft, clipTop, clipRight, clipBottom){
		this.mapLayer.clip(clipLeft, clipTop, clipRight, clipBottom);
		this.moveTo(xOffset, yOffset);
		return false;
	}
	
	this.processClick = function(x, y){
		if(this.getOverviewMapVisible()){
			if(x > this.getOverviewMap().getMapLayer().getLeft() && x < this.getOverviewMap().getMapLayer().getRight() &&
			   y < this.getOverviewMap().getMapLayer().getBottom() && y > this.getOverviewMap().getMapLayer().getTop())
			{
				var extent = this.getOverviewMap().getExtent(x, y);
				this.zoomToExtent(extent.getLeft(), extent.getTop(), extent.getRight(), extent.getBottom(), true);
				return true;
			}
		}
		return false;
	}
}

/*********************************************************************************
ZoomInTool.
*********************************************************************************/

function ZoomInTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'zoomIn';}
	this.rubberRectangle = new RubberRectangle(wnd, id + '_rect', 2, 'red');
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.startX = 0;
	this.startY = 0;
	this.currentX = 0;
	this.currentY = 0;
	this.mouseMove = false;
	
	this.onMouseDown = function(x,y){
		if(this.map == null)
			return false;
			
		this.rubberRectangle.start(x, y);	
		this.startX = x;
		this.startY = y;
		return false;
	}
	
	this.onMouseMove = function(x, y){
		if(this.map == null)
			return false;
	
		this.mouseMove = true;		
		this.rubberRectangle.moveTo(x, y);		
		this.currentX = x;
		this.currentY = y;
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
			
		this.rubberRectangle.stop(x, y);	
		this.currentX = x;
		this.currentY = y;
		
		var left = 0;
		var top = 0;
		var right = 0;
		var bottom = 0;
		
		if(this.mouseMove)
		{
			left = Math.min(this.startX, this.currentX);
			top = Math.max(this.startY, this.currentY);
			right = Math.max(this.startX, this.currentX);
			bottom = Math.min(this.startY, this.currentY);
		}
		else
		{
			var width = parseInt(this.map.getWidth() / 4);
			var height = parseInt(this.map.getWidth() / 4);
			
			left = this.currentX - width;
			top = this.currentY + height;
			right = this.currentX + width;
			bottom = this.currentY - height;	
		}
		
		this.startX = 0;
		this.startY = 0;
		this.currentX = 0;
		this.currentY = 0;
		this.mouseMove = false;
		this.map.zoomToExtent(left, top, right, bottom);
		return false;
	}
	
	this.onDoubleClick = function(){
		return false;
	}
}

/*********************************************************************************
ZoomOutTool.
*********************************************************************************/

function ZoomOutTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'zoomOut';}
	this.rubberRectangle = new RubberRectangle(wnd, id + '_rect', 2, 'red');
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.startX = 0;
	this.startY = 0;
	this.currentX = 0;
	this.currentY = 0;
	this.mouseMove = false;
	
	this.onMouseDown = function(x,y){
		if(this.map == null)
			return false;
			
		this.rubberRectangle.start(x, y);
		
		this.startX = x;
		this.startY = y;
		
		return false;
	}
	
	this.onMouseMove = function(x, y){
		if(this.map == null)
			return false;
			
		this.rubberRectangle.moveTo(x, y);
	
		this.mouseMove = true;	
		this.currentX = x;
		this.currentY = y;
		
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
			
		this.rubberRectangle.stop(x, y);
		
		this.currentX = x;
		this.currentY = y;
		
		var left = 0;
		var top = 0;
		var right = 0;
		var bottom = 0;
		var bMapCoordinates = true;
		
		if(this.mouseMove)
		{
			var nMinX = Math.min(this.startX, this.currentX);
			var nMaxX = Math.max(this.startX, this.currentX);
			var nMinY = Math.min(this.startY, this.currentY);
			var nMaxY = Math.max(this.startY, this.currentY);
			
			var dMinX = this.map.getExtent().getLeft();
			var dMaxX = this.map.getExtent().getRight();
			var dMinY = this.map.getExtent().getBottom();
			var dMaxY = this.map.getExtent().getTop();
			
			var dWidth = parseFloat(Math.abs(nMinX - nMaxX));
			var dHeight = parseFloat(Math.abs(nMinY - nMaxY));
			
			var dWidthRatio = parseFloat(parseFloat(this.map.getWidth()) / dWidth);
			var dHeightRatio = parseFloat(parseFloat(this.map.getHeight()) / dHeight);
			
			var dXOffset = dWidthRatio * (this.map.getExtent().getWidth() * .5);
			var dYOffset = dHeightRatio * (this.map.getExtent().getHeight() * .5);
			
			left = dMinX - dXOffset;
			top = dMinY - dYOffset;
			right = dMaxX + dXOffset;
			bottom = dMaxY + dYOffset;
		}
		else
		{
			var mapX = this.map.getDisplayTransformation().getMapX(this.currentX);
			var mapY = this.map.getDisplayTransformation().getMapY(this.currentY);
			var xDistance = this.map.getExtent().getWidth();
			var yDistance = this.map.getExtent().getHeight();
			var zoomFactor = 2;
			
			left = mapX - (xDistance*zoomFactor/2);
			right = mapX + (xDistance*zoomFactor/2);
			top = mapY + (yDistance*zoomFactor/2);
			bottom = mapY - (yDistance*zoomFactor/2);
			bMapCoordinates = true;
		}
		
		this.map.zoomToExtent(left, top, right, bottom, bMapCoordinates);
		
		this.startX = 0;
		this.startY = 0;
		this.currentX = 0;
		this.currentY = 0;
		this.mouseMove = false;
		return false;
	}
	
	this.onDoubleClick = function(){
		return false;
	}
}

/*********************************************************************************
IdentifyTool.
*********************************************************************************/

function IdentifyTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'identify';}
	this.rubberRectangle = new RubberRectangle(wnd, id + '_rect', 2, 'red');
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.startX = 0;
	this.startY = 0;
	this.currentX = 0;
	this.currentY = 0;
	this.mouseMove = false;
	this.allowDrag = true;
	this.setAllowDrag = function(b){
		this.allowDrag = b;
		return false;
	}
	
	this.onMouseDown = function(x,y){
		if(this.map == null || !this.allowDrag)
			return false;
			
		this.rubberRectangle.start(x, y);
		
		this.startX = x;
		this.startY = y;
		
		return false;
	}
	
	this.onMouseMove = function(x, y){
		if(this.map == null || !this.allowDrag)
			return false;
			
		this.rubberRectangle.moveTo(x, y);
	
		this.mouseMove = true;	
		this.currentX = x;
		this.currentY = y;
		
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
		
		if(this.allowDrag)
			this.rubberRectangle.stop(x, y);
		
		this.currentX = x;
		this.currentY = y;
		
		var left = 0;
		var top = 0;
		var right = 0;
		var bottom = 0;
		var bMapCoordinates = true;
		
		if(this.mouseMove)
		{
			left = Math.min(this.startX, this.currentX);
			top = Math.max(this.startY, this.currentY);
			right = Math.max(this.startX, this.currentX);
			bottom = Math.min(this.startY, this.currentY);
		}
		else
		{
			var width = 8;
			var height = 8;
			
			left = this.currentX - width;
			top = this.currentY + height;
			right = this.currentX + width;
			bottom = this.currentY - height;	
		}
		
		this.map.identify(left, top, right, bottom);
		
		this.startX = 0;
		this.startY = 0;
		this.currentX = 0;
		this.currentY = 0;
		this.mouseMove = false;
		return false;
	}
	
	this.onDoubleClick = function(){
		return false;
	}
}

/*********************************************************************************
PanTool.
*********************************************************************************/

function PanTool(wnd, id, map)
{	
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'pan';}
	this.mapLeft = 0;
	this.mapTop = 0;
	this.mapWidth = 0;
	this.mapHeight = 0;
	this.panX = 0;
	this.panY = 0;
	this.startX = 0;
	this.startY = 0;
	this.currentX = 0;
	this.currentY = 0;
	this.mouseMove = false;
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.onMouseDown = function(x,y){
		if(this.map == null)
			return false;
			
		this.startX = x;
		this.startY = y;
		this.mapLeft = this.map.getLeft();
		this.mapTop = this.map.getTop()
		this.mapWidth = this.map.getWidth();
		this.mapHeight = this.map.getHeight();
	
		return false;
	}
	
	this.onMouseMove = function(x, y){
		if(this.map == null)
			return;
			
		this.currentX = x;
		this.currentY = y;
		this.mouseMove = true;
		
		var xOffset = (this.currentX - this.startX) + this.mapLeft;
		var yOffset = (this.currentY - this.startY) + this.mapTop;
		
		var clipLeft = -(this.currentX - this.startX);
		var clipTop = -(this.currentY - this.startY);
		var clipRight = clipLeft + this.mapWidth;
		var clipBottom = clipTop + this.mapHeight;
		
		if(xOffset > 0){
			clipLeft = 0;
			clipRight = (this.mapWidth - xOffset) + this.mapLeft;
			clipLeft = clipRight - this.mapWidth;
		}
		if(yOffset > 0){
			clipTop = 0;
			clipBottom = (this.mapHeight - yOffset) + this.mapTop;
			clipTop = clipBottom - this.mapHeight;
		}
		
		this.panX = clipLeft + this.mapLeft;
		this.panY = clipTop + this.mapTop;	
		this.map.pan(xOffset, yOffset, clipLeft, clipTop, clipRight, clipBottom); 
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
			
		if(this.mouseMove)
		{
			var left = this.panX;
			var right = left + this.mapWidth;
			var top = this.panY;
			var bottom = top + this.mapHeight;
			this.map.zoomToExtent(left, top, right, bottom);		
		}
		
		this.mapLeft = 0;
		this.mapTop = 0;
		this.panX = 0;
		this.panY = 0;
		this.startX = 0;
		this.startY = 0;
		this.currentX = 0;
		this.currentY = 0;
		this.mouseMove = false;
		return false;
	}
	
	this.onDoubleClick = function(){
		return false;
	}
}

/*********************************************************************************
MeasureTool.
*********************************************************************************/

function MeasureTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'measure';}
	this.rubberPolyline = new RubberPolyline(wnd, id + '_measure', 2, 'red', 4, false, this.map.getDisplayTransformation());
	this.getRubberPolyline = function(){return this.rubberPolyline;}
	this.onSketchFinished = null;
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.displayUnits = 'miles';
	this.setDisplayUnits = function(units){
		this.displayUnits = units;
		return false;
	}
	
	this.map.addUpdateListener(this);
	this.onMapUpdate = function(){
		this.rubberPolyline.refresh();
		return false;
	}
	
	//label is a LabelLayer object.
	this.label = null;
	this.setLabel = function(l){this.label = l;}
	
	this.onMouseDown = function(x,y){
		return false;
	}
	
	this.onMouseMove = function(x, y){
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
			
		this.rubberPolyline.addPoint(x,y);
		var lines = this.rubberPolyline.getLines(true);
		var totalDistance = 0;
		var segmentDitance = 0;
		var text = '';
		
		if(lines != null && lines.length > 0){
			var i = 0;
			for(i = 0; i < lines.length; i++)
				totalDistance += lines[i].getDistance(this.map.getUnits(), this.displayUnits);
				
			totalDistance = parseInt(totalDistance * Math.pow(10,2) + (5/10)) / Math.pow(10,2);
			segmentDistance = lines[lines.length-1].getDistance(this.map.getUnits(), this.displayUnits);
			text = 'Segment Distance: ' + segmentDistance + ' ' + this.displayUnits + '. Total Distance: ' + totalDistance + ' ' + this.displayUnits + '.'
			
			if(this.label != null)
				this.label.setText(text);
		}		
		return false;
	}
	
	this.onDoubleClick = function(){
		if(this.onSketchFinished){
			var points = this.rubberPolyline.getPoints();
			var length = points.length;
			
			//Netscape fires an onMouseUp message before the double-click,
			//so we remove the last point if it has the same coordinates.
			if(length > 2)
				if(points[length - 1].getX() == points[length - 2].getX() && points[length - 1].getY() == points[length - 2].getY())
					points.length--;
			
			this.onSketchFinished(points);
		}
		this.rubberPolyline.clearPoints();
		
		if(this.label)
			this.label.setText('');
		return false;
	}
	
	this.onActivate = function(){
		if(this.label)
			this.label.setVisible(true);
		return false;
	}
	
	this.onDeactivate = function(){
		this.rubberPolyline.clearPoints();
		if(this.label){
			this.label.setText('');
			this.label.setVisible(false);
		}
		return false;
	}
}

/*********************************************************************************
PolylineTool.
*********************************************************************************/

function PolylineTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'polyline';}
	this.rubberPolyline = new RubberPolyline(wnd, id + '_polyline', 2, 'red', 4, false, this.map.getDisplayTransformation());
	this.getRubberPolyline = function(){return this.rubberPolyline;}
	this.onSketchFinished = null;
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.map.addUpdateListener(this);
	this.onMapUpdate = function(){
		this.rubberPolyline.refresh();
		return false;
	}
	
	this.onMouseDown = function(x,y){
		return false;
	}
	
	this.onMouseMove = function(x, y){
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
		this.rubberPolyline.addPoint(x, y);
		return false;
	}
	
	this.onDoubleClick = function(){
		if(this.onSketchFinished){
			var points = this.rubberPolyline.getPoints();
			var length = points.length;
			
			//Netscape fires an onMouseUp message before the double-click,
			//so we remove the last point if it has the same coordinates.
			if(length > 2)
				if(points[length - 1].getX() == points[length - 2].getX() && points[length - 1].getY() == points[length - 2].getY())
					points.length--;
			
			this.onSketchFinished(points);
		}
		this.rubberPolyline.clearPoints();
		return false;
	}
}

/*********************************************************************************
PolygonTool.
*********************************************************************************/

function PolygonTool(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'polygon';}
	this.rubberPolyline = new RubberPolyline(wnd, id + '_polygon', 2, 'red', 4, true, this.map.getDisplayTransformation());
	this.getRubberPolyline = function(){return this.rubberPolyline;}
	this.onSketchFinished = null;
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.map.addUpdateListener(this);
	this.onMapUpdate = function(){
		this.rubberPolyline.refresh();
		return false;
	}
	
	this.onMouseDown = function(x,y){
		return false;
	}
	
	this.onMouseMove = function(x, y){
		return false;
	}
	
	this.onMouseUp = function(x, y){
		if(this.map == null)
			return false;
		this.rubberPolyline.addPoint(x, y);
		return false;
	}
	
	this.onDoubleClick = function()
	{
		if(this.onSketchFinished){
			var points = this.rubberPolyline.getPoints();
			var length = points.length;
			if(length > 2){
				if(points[length - 1].getX() == points[length - 2].getX() && points[length - 1].getY() == points[length - 2].getY())
					points[points.length-1] = points[0]; //closes the polygon.
				else
					points[points] = points[0]; //closes the polygon.
			}
			this.onSketchFinished(points);
		}
		this.rubberPolyline.clearPoints();
		return false;
	}
}

/*********************************************************************************
FullExtentCommand.
*********************************************************************************/

function FullExtentCommand(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'fullExtent';}
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}

	
	this.execute = function(){
		if(this.map == null)
			return false;
	
		this.map.zoomToFullExtent();
		return false;
	}
}

/*********************************************************************************
LastExtentCommand.
*********************************************************************************/

function LastExtentCommand(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'lastExtent';}
	
	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.execute = function(){
		if(this.map == null)
			return false;
			
		this.map.goBack();
		return false;
	}
}

/*********************************************************************************
NextExtentCommand.
*********************************************************************************/

function NextExtentCommand(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'nextExtent';}

	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
		
	this.execute = function(){
		if(this.map == null)
			return false;
			
		this.map.goForward();
		return false;
	}
}

/*********************************************************************************
ToggleOverviewCommand.
*********************************************************************************/

function ToggleOverviewCommand(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'toggleOverview';}

	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
		
	this.execute = function(){
		if(this.map == null)
			return false;
			
		if(this.map.getOverviewMapVisible())
			this.map.hideOverviewMap();
		else
			this.map.showOverviewMap();
			
		this.map.hideLegend();
		return false;
	}
}

/*********************************************************************************
ToggleLegendCommand.
*********************************************************************************/

function ToggleLegendCommand(wnd, id, map)
{
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.getName = function(){return 'toggleLegend';}

	this.rolloverImage = null;
	this.setRolloverImage = function(img){this.rolloverImage = img;}
	this.getRolloverImage = function(){return this.rolloverImage;}
	
	this.tooltip = null;
	this.setTooltip = function(t){this.tooltip = t;}
	this.getTooltip = function(){return this.tooltip;}
	
	this.execute = function(){
		if(this.map == null)
			return false;
			
		if(this.map.getLegendVisible())
			this.map.hideLegend();
		else
			this.map.showLegend();
		
		this.map.hideOverviewMap();
		return false;
	}
}

/*********************************************************************************
ScaleZoomControl.	Changes the extent of the map by scale.
					A scale with a value less than 0 will indicate the full extent.
*********************************************************************************/

var scaleZoomControl = null;

//Place holder object for anchor click event
function ScaleZoomControlCommand(a)
{
	a.onclick=function(){
		scaleZoomControl.changeScale(a);
		if(a.blur)
			a.blur();
		return false;
	}
	
	a.onmouseover = function(){
		scaleZoomControl.highlightScaleCommand(a, true);
		return false;
	}
	
	a.onmouseout = function(){
		scaleZoomControl.highlightScaleCommand(a, false);
		return false;	
	}
	return false;
}

function ScaleZoomControl(wnd, id, map)
{
	scaleZoomControl = this;
	this.wnd = wnd;
	this.id = id;
	this.map = map;
	this.map.addUpdateListener(this);	//listen to onMapUpdate to change the scale.
	
	this.anchors = null;
	this.images = null;
	this.selectedImages = null;
	this.unselectedImages = null;
	this.scales = null;
	this.scaleCount = 0;
	this.minScale = -1;
	this.minScaleIndex = -1;
	this.maxScale = -1;
	this.maxScaleIndex = -1;
	this.fullExtentIndex = -1;
	this.selectedAnchor = null;
	this.loaded = false;
	
	/**
	*	anchors: 				An array anchors containing the images for the scales.
	*	images:					An array of images contained by the anchors.
	*	selectedImageUrls: 		An array of strings for the selected image urls.
	*	unselectedImageUrls:	An array of strings for the unselected image urls.
	*	scales:					An array of numbers for the scales to zoom to.
	*/
	
	this.load = function(anchors, images, selectedImageUrls, unselectedImageUrls, scales){
		this.scaleCount = anchors.length;
		this.anchors = anchors;
		this.images = images;
		this.selectedImages = new Array();
		this.unselectedImages = new Array();
		this.scales = scales;
		
		for(var i = 0; i < this.scaleCount; i++){
			var img = new Image();
			img.src = selectedImageUrls[i];
			this.selectedImages[i] = img;
			
			img = new Image();
			img.src = unselectedImageUrls[i];
			this.unselectedImages[i] = img;	
						
			new ScaleZoomControlCommand(this.anchors[i]);
		}
		
		var scale = 0;
		for(var i = 0; i < this.scaleCount; i++){
			if(this.scales[i] < 0)
			{
				this.fullExtentIndex = i;
				continue;
			}
			
			scale = this.scales[i];
			
			if(this.minScale > scale || this.minScale < 0){
				this.minScaleIndex = i;
				this.minScale = scale;
			}
			if(this.maxScale < scale){
				this.maxScaleIndex = i;
				this.maxScale = scale;
			}
		}
		this.loaded = this.minScaleIndex > -1 && this.maxScaleIndex > -1;
		return false;
	}
	
	this.changeScale = function(a){
		//force the selected anchor's image to the unselected one.
		if(this.selectedAnchor != null){
			for(var i = 0; i < this.anchors.length; i++){
				if(this.anchors[i] == this.selectedAnchor){
					this.images[i].src = this.unselectedImages[i].src;	
					break;
				}
			}
		}
		
		//set the selected anchor and refresh the map.
		this.selectedAnchor = a;
		for(var i = 0; i < this.anchors.length; i++){
			if(this.anchors[i] == a){
				this.map.zoomToScale(this.scales[i]);
			}
		}
		return false;
	}
	
	this.highlightScaleCommand = function(a, bHighlight){
		if(a == this.selectedAnchor)
			return false;
			
		for(var i = 0; i < this.anchors.length; i++){
			if(this.anchors[i] == a){
				this.images[i].src = bHighlight ? this.selectedImages[i].src : this.unselectedImages[i].src;	
				break;
			}
		}
		return false;
	}
	
	this.onMapUpdate = function(){	
		if(!this.loaded)
			return false;
		
		for(var i = 0; i < this.scaleCount; i++)
			this.images[i].src = this.unselectedImages[i].src;
			
		var scale = parseInt(this.map.getScale());
		if(this.fullExtentIndex > -1 && scale > this.maxScale){
			this.images[this.fullExtentIndex].src = this.selectedImages[this.fullExtentIndex].src;
			this.selectedAnchor = this.anchors[this.fullExtentIndex];
		}
		else if(scale > this.maxScale){
			this.images[this.maxScaleIndex].src = this.selectedImages[this.maxScaleIndex].src;
			this.selectedAnchor = this.anchors[this.maxScaleIndex];
		}
		else if(scale <= this.minScale){
			this.images[this.minScaleIndex].src = this.selectedImages[this.minScaleIndex].src;
			this.selectedAnchor = this.anchors[this.minScaleIndex];
		}
		else{
			var bHighToLow = this.maxScaleIndex < this.minScaleIndex;
			if(bHighToLow){
				/*
				1000 to 101
				100  to 11
				10
				*/
				for(var i = this.maxScaleIndex; i < this.scaleCount-1; i++){
					var maxScale = this.scales[i];
					var minScale = this.scales[i + 1] + 1;
					if(scale >= minScale && scale <= maxScale){
						this.images[i].src = this.selectedImages[i].src;
						this.selectedAnchor = this.anchors[i];
						break;
					}
				}
			}
			else{
				/*
				10 to 99
				100  to 999
				1000
				*/
				for(var i = this.maxScaleIndex; i < this.scaleCount-1; i++){
					var minScale = this.scales[i];
					var maxScale = this.scales[i + 1] - 1;
					if(scale >= minScale && scale <= maxScale){
						this.images[i].src = this.selectedImages[i].src;
						this.selectedAnchor = this.anchors[i];
						break;
					}
				}
			}
		}
		return false;
	}
}


/*********************************************************************************
Application.	This ties it all together.
*********************************************************************************/

function Application(wnd)
{
	this.wnd = wnd || window;
	this.getWindow = function(){return this.wnd;}
	
	this.tools = new Array();
	this.commands = new Array();
	
/************************************************************************************************
*			This section is very important.
*			It Loads all of the underlying layers into the document.
*************************************************************************************************/

	this.map = new Map(this.wnd, 'map');
	this.getMap = function(){return this.map;}
	
	this.tools[this.tools.length] = new ZoomInTool(this.wnd, 'zoomInTool', this.map);
	this.tools[this.tools.length] = new ZoomOutTool(this.wnd, 'zoomOutTool', this.map);
	this.tools[this.tools.length] = new IdentifyTool(this.wnd, 'identifyTool', this.map);
	this.tools[this.tools.length] = new PanTool(this.wnd, 'panTool', this.map);
	this.tools[this.tools.length] = new PolylineTool(this.wnd, 'polylineTool', this.map);
	this.tools[this.tools.length] = new PolygonTool(this.wnd, 'polygonTool', this.map);
	this.tools[this.tools.length] = new MeasureTool(this.wnd, 'measureTool', this.map);
	
	this.commands[this.commands.length] = new FullExtentCommand(this.wnd, 'fullExtentCommand', this.map);
	this.commands[this.commands.length] = new LastExtentCommand(this.wnd, 'lastExtentCommand', this.map);
	this.commands[this.commands.length] = new NextExtentCommand(this.wnd, 'nextExtentCommand', this.map);
	this.commands[this.commands.length] = new ToggleOverviewCommand(this.wnd, 'toggleOverviewCommand', this.map);
	this.commands[this.commands.length] = new ToggleLegendCommand(this.wnd, 'toggleLegendCommand', this.map);
	
	this.map.loadLegend();
	this.map.loadOverviewMap();
	this.map.loadImageLoader();
	this.map.loadDataLoader();
	
	this.scaleZoomControl = new ScaleZoomControl(this.wnd, 'ScaleZoomControl', this.map);
	this.getScaleZoomControl = function(){return this.scaleZoomControl;}
	
/************************************************************************************************
*			End of critical section.
*************************************************************************************************/

	this.lastTool = null;
	this.getLastTool = function(){return this.lastTool;}
	
	this.currentTool = null;
	this.getCurrentTool = function(){return this.currentTool;}
	this.setCurrentTool = function(t){
		if(t == this.currentTool)
			return false;
		
		this.lastTool = this.currentTool;
		if(this.currentTool != null){
			if(this.currentTool.getRolloverImage())
				this.setImage(this.currentTool, this.currentTool.getRolloverImage().getImage());
			if(this.currentTool.onDeactivate)
				this.currentTool.onDeactivate();
		}
	
		this.currentTool = t;	
		if(this.currentTool != null){
			if(this.currentTool.getRolloverImage())
				this.setImage(this.currentTool, this.currentTool.getRolloverImage().getRolloverImage());
			if(this.currentTool.onActivate)
				this.currentTool.onActivate();
		}
			
		if(this.currentTool != null && this.currentTool.getName() == 'identify')
			if(this.onIdentifyToolSelected)
				this.onIdentifyToolSelected();
		return false;
	}
	
	this.getTool = function(n){
		if(n == null)
			return null;
			
		var i = 0;
		for(i; i < this.tools.length; i++)
		{
			if(this.tools[i].getName().toUpperCase() == n.toUpperCase())
			{
				return this.tools[i];
			}
		}
		return null;
	}
	
	this.getCommand = function(n){
		if(n == null)
			return null;
			
		var i = 0;
		for(i; i < this.commands.length; i++)
		{
			if(this.commands[i].getName().toUpperCase() == n.toUpperCase())
			{
				return this.commands[i];
			}
		}
		return null;
	}
	
	this.tooltipLabel = null;
	this.setTooltipLabel = function(label){
		this.tooltipLabel = label;	
		return false;
	}
	this.updateTooltipLabel = function(tooltip){
		if(this.tooltipLabel == null)
			return false;
			
		if(tooltip == null)
			this.tooltipLabel.setText('');
		else
			this.tooltipLabel.setText(tooltip);
		return false;
	}

	this.images = null;
	this.setImages = function(imgs){this.images = imgs;}
	this.setImage = function (obj, img){
		if(this.images == null || obj == null || img == null)
			return;
	
		var i = 0;
		for(i = 0; i < this.images.length; i++)
		{
			if(this.images[i].name != null && this.images[i].name.toUpperCase() == obj.getName().toUpperCase())
			{
				this.images[i].src = img.src;
				break;
			}
		}
	}
	
	this.highlight = function (name, bHighlight){
		var obj = this.getTool(name);
		if(obj != null)
		{
			if(obj == this.currentTool)
				return false;
		}
		else
			obj = this.getCommand(name);
		
		if(obj == null)
			return false;
			
		if(obj.getRolloverImage() == null)
			return false;
			
		if(bHighlight){
			this.setImage(obj, obj.getRolloverImage().getRolloverImage());
			this.updateTooltipLabel(obj.getTooltip());
		}
		else{
			this.setImage(obj, obj.getRolloverImage().getImage());
			this.checkCommands();
			this.updateTooltipLabel(null);
		}
		return false;
	}
	
	this.highlightCommand = function (name, bHighlight){
		var obj = this.getCommand(name);
		if(obj == null)
			return false;
			
		if(obj.getRolloverImage() == null)
			return false;
			
		if(bHighlight)
			this.setImage(obj, obj.getRolloverImage().getRolloverImage());
		else
			this.setImage(obj, obj.getRolloverImage().getImage());
		return false;
	}
	
	this.checkCommands = function(){	
		this.highlightCommand('toggleoverview', this.map.getOverviewMapVisible());
		this.highlightCommand('togglelegend', this.map.getLegendVisible());
	}
	
	this.getX = function(e){return e.clientX + document.body.scrollLeft;}
	this.getY = function(e){return e.clientY + document.body.scrollTop;}
	
	this.mouseDown = false;
	this.mouseMove = false;
	
	this.onMouseDown = function(e){
		var x = this.getX(e);
		var y = this.getY(e);
			
		if(this.map.processClick(x, y))
			return false;
			
		if(this.currentTool == null)
			return true;
		
		if(this.currentTool != null){
			if(x < this.map.getLeft() || x > this.map.getRight())
				return true;
				
			if(y < this.map.getTop() || y > this.map.getBottom())
				return true;
			
			this.mouseDown = true;
			this.currentTool.onMouseDown(this.getX(e), this.getY(e));
		}
		return false;
	}
	
	this.onIdentifyToolSelected = null;
	
	this.onMouseMove = function(e){
		if(this.map.getExtent() == null)
			return true;
			
		var x = this.getX(e);
		var y = this.getY(e);
		
		var displayX = this.map.getDisplayTransformation().getMapX(x);
		var displayY = this.map.getDisplayTransformation().getMapY(y);
			
		this.wnd.status = displayX + ', ' + displayY;
			
		if(this.currentTool == null)
			return true;
			
		if(this.currentTool != null && this.mouseDown){		
			if(x < this.map.getLeft()) 
				x = this.map.getLeft();
			else if(x > this.map.getRight())
				x = this.map.getRight();
				
			if(y < this.map.getTop())
				y = this.map.getTop();
			else if(y > this.map.getBottom())
				y = this.map.getBottom();
				
			this.mouseMove = true;
			this.currentTool.onMouseMove(x, y);
			return false;
		}
		return false;
	}
	
	this.onMouseUp = function(e){
		if(this.currentTool != null && this.mouseDown){
			this.currentTool.onMouseUp(this.getX(e), this.getY(e));
			this.mouseDown = false;
			this.mouseMove = false;
			return false;
		}
		return true;
	}
	
	this.onDoubleClick = function(e){
		if(this.currentTool != null){
			this.currentTool.onDoubleClick();
			return false;
		}
		return true;
	}
}


/************************************************************************************************
*			Helper Functions.
*************************************************************************************************/

var app = null;
var timeoutID = null;

function loadApplication(window){
	app = new Application(window);
}

//this handles the map refresh.
function handleResize(){
	if(timeoutID)
         window.clearTimeout(timeoutID);
   	timeoutID = window.setTimeout("mapResized()", 300);
	return false;
}

function loadImages(a, selectedImage, unselectedImage, tooltip){
	if(app == null)
		return false;
		
	var name = a.id;
	var tool = app.getTool(name);
	var command = app.getCommand(name);
	var bTool = tool != null;
	var bCommand = command != null;
	
	var img = new RolloverImage(unselectedImage, selectedImage);
	if(bTool){
		tool.setRolloverImage(img);
		tool.setTooltip(tooltip);
	}
	else if(bCommand){
		command.setRolloverImage(img);
		command.setTooltip(tooltip);
	}
	
	if(a)
	{
		a.href = '#';
			
		a.onmouseover = function(){
			app.highlight(name, true);
		}
		
		a.onmouseout = function(){
			app.highlight(name, false);
		}
		
		a.onclick = function(){
			if(bTool)
				app.setCurrentTool(tool);
			else if(bCommand){
				command.execute();
				app.checkCommands();
			}
		}
	}
	return false;
}

//this will initialize the map. If bFillFrame is false, then location information must not be null.
function loadMap(bFillFrame, l, t, w, h){
	if(app == null)
		return false;
		
	var mapLeft = l;
	var mapTop = t;
	var mapWidth = w;
	var mapHeight = h;
	
	if(bFillFrame){
		mapLeft = 0;
		mapTop = 0;
		mapWidth = getWindowWidth();
		mapHeight = getWindowHeight();
	}
	
	//sanity check.
	if(mapWidth == 0){
		alert('Invalid map width specified.');
		return false;
	}
	if(mapHeight == 0){
		alert('Invalid map height specified.');
		return false;
	}
	
	app.getMap().initialize(mapLeft, mapTop, mapWidth, mapHeight);
	return false;
}

function loadEvents(){
	//for now, we are not implementing right-click mouse events...
	window.document.onmousedown = onMouseDown;
	window.document.onmousemove = onMouseMove;
	window.document.onmouseup = onMouseUp;
	window.document.ondblclick=onDoubleClick;
	return false;
}

function isRightClick(e){
	if(window.event)
		return window.event.button == 2;
	return e.button == 2;
}

function onMouseDown(e)
{
	if(isRightClick(e))
		return false;
		
	if(window.event)
		return app.onMouseDown(window.event);
	else
		return app.onMouseDown(e);
}
	
function onMouseMove(e)
{
	if(isRightClick(e))
		return false;
		
	if(window.event)
		return app.onMouseMove(window.event);
	else
		return app.onMouseMove(e);
}

function onMouseUp(e)
{
	if(isRightClick(e))
		return false;
		
	if(window.event)
		return app.onMouseUp(window.event);
	else
		return app.onMouseUp(e);
}

function onDoubleClick(e)
{
	if(window.event)
		return app.onDoubleClick(window.event);
	else
		return app.onDoubleClick(e);
}
	
function refreshMap(){
	//var mapWidth = document.body.clientWidth;
	//var mapHeight = document.body.clientHeight;
	
	//app.getMap().setSize(mapWidth, mapHeight);
	app.getMap().refresh();
	return false;
}

function mapResized(){
	var mapWidth = getWindowWidth();
	var mapHeight = getWindowHeight();
	
	app.getMap().setSize(mapWidth, mapHeight);
	app.getMap().refresh();
	return false;
}

function updateMap(url, left, top, right, bottom, scale)
{
	if(app != null)
		app.getMap().update(url, new Envelope(left, top, right, bottom), scale);
	return false;
}

function initLocationMap(url, left, top, right, bottom, w, h, smallImageUrl, smallImageWidth, smallImageHeight)
{
	if(app == null)
		return false;
		
	app.getMap().setUseOverviewMap(true);
	app.getMap().getOverviewMap().load(url, new Envelope(left, top, right, bottom), w, h, UPPER_LEFT, smallImageUrl, smallImageWidth, smallImageHeight);
	return false;
}

function initMap(url, left, top, right, bottom, scale)
{
	if(app == null)
		return false;
		
	var l = 0;
	var t = 0;
	var mapWidth = getWindowWidth();
	var mapHeight = getWindowHeight();
	var mapExtent = new Envelope(left, top, right, bottom);
	
	app.getMap().setLocation(l,t,mapWidth,mapHeight);
	app.getMap().update(url, mapExtent, scale);
	return false;
}

function initNonSizeableMap(url, left, top, right, bottom, mapWidth, mapHeight, scale)
{
	if(app == null)
		return false;
	
	var l = 0;
	var t = 0;
	var mapExtent = new Envelope(left, top, right, bottom);
	
	app.getMap().setLocation(l,t,mapWidth,mapHeight);
	app.getMap().update(url, mapExtent, scale);
	return false;
}

function showImageLoader()
{
	if(app == null)
		return false;
	app.getMap().showImageLoader();
	return false;
}

function setImages(imgs)
{
	if(app != null)
		app.setImages(imgs);
	return false;
}

function changeMapService(server, service)
{
	if(app != null)
		app.getMap().changeMapService(server, service);
	return false;
}

function zoomToExtent(left, top, right, bottom){
	if(app == null)
		return false;
		
	app.getMap().zoomToExtent(left, top, right, bottom, true);
	return false;
}

function showExtent(left, top, right, bottom){
	if(app == null)
		return false;
	app.getMap().showExtent(new Envelope(left, top, right, bottom));
	return false;
}

function showPoint(x, y, size){
	if(app == null)
		return false;
	app.getMap().showPoint(x, y, size);
	return false;
}

function hideExtent(){
	if(app != null)
		app.getMap().hideExtent();
	return false;
}

function setTool(id){
	app.setCurrentTool(app.getTool(id));
	return false;
}

/*
//wrapper to library function.
function zoomToExtent(left, top, right, bottom){
	app.getMap().zoomToExtent(left, top, right, bottom, true);
	return false;
}

//wrapper to library function.
function showExtent(left, top, right, bottom){
	app.getMap().showExtent(new Envelope(left, top, right, bottom));
	return false;
}

//wrapper to library function.
function hideExtent(){
	app.getMap().hideExtent();
	return false;
}
*/