function CMapFuncs()
{
	// Array of defined quick zooms.
	this.m_quickZooms = new Array();
	// Map dimensions.
	this.height = 0;
	this.width = 0;
	// Set this value to change the size of the zoom-in rectangle
	// This is what the extents gets changed to if the zoom-in
	// is too small.  e.g. we probably don't want to zoom any
	// closer than a city block.  The value's in map units.
	this.minZoomInSize = 1000;

	// The 1x1 pixel image that locates 0,0 of the map image.
	this.anchorImage = null;

	this.m_toolbar = null;
	this.setToolbar =
	function m_setToolbar(toolbar)
	{
		this.m_toolbar = toolbar;
		this.m_toolbar.parent = this;
	}

	// Submit the given form, show the wait dialog if it applies.
	this.submitForm =
	function m_submitForm(form)
	{
		if (form == this.m_form)
			this.showDialogDiv("wait");
		form.submit();
	}

	// Maping functions -->
	this.m_frame = null;  // The frame of the map document.
	this.m_doc = null;  // The document of the map page.
	this.m_form = null;  // The form of the page where the map is displayed.
	this.setFrameDocumentAndForm =
	function m_setFrameDocumentAndForm(frame, document, form)
	{
		this.m_frame = frame;
		this.m_doc = document;
		this.m_form = form;
		this.setBrowser();
	}

	// Adds a predefined zoom - sort of like a bookmark.
	// Alows to quickly zoom to a place of interest.
	// I.e.: "downtown" or "tranquil pines neighborhood"
	this.addQuickZoom =
	function m_addQuickZoom(zoomName, minx, miny, maxx, maxy)
	{
		this.m_quickZooms[zoomName] = new Array(minx, miny, maxx, maxy);
	}

	this.zoomWindow =
	function m_zoomWindow(x1, y1, x2, y2)
	{
		this.m_form.txtParam.value = x1 + "," + y1 + "," + x2 + "," + y2;
		this.submitForm(this.m_form);
	}

	// Zoom the map the a given record.
	this.zoomToRecord =
	function(layerName, tileIndex, shapeIndex, id)
	{
		// Add the query.
		if (id)
		{
			this.m_form.txtSearchType.value = "record";
			this.m_form.txtSearchTarget.value = layerName + "," + tileIndex + "," + shapeIndex;
		}
		
		// The PHP code is smart enough to do the zooming.  So we just fire it off here.
		this.m_form.txtParam.value = layerName + "," + tileIndex + "," + shapeIndex;
		this.doCommand("zoom_rec");
	
		// Send the query.
		if (id)
			this.doQuery("id");  // "id" tells it to use the ID pane.
	}

	this.doQuickZoom =
	function m_doQuickZoom(zoomName)
	{
		if (zoomName == "zoom_all")
			this.doCommand("zoom_all");
		else
		{
			var ext = this.m_quickZooms[zoomName];
			this.zoomTo(ext[0], ext[1], ext[2], ext[3], false);
		}
	}

	this.zoomToExtent =
	function m_zoomToExtent(extentString)
	{
		this.m_form.txtParam.value = extentString;
		this.doCommand("zoom_to");
	}

	// Do a map form command.
	this.doCommand =
	function m_doCommand(command)
	{
		this.doCommand2(this.m_form, command);
	}

	// Do a command on the given form.
	this.doCommand2 =
	function m_doCommand(form, command)
	{
		if (form.cmd2 != null)
			form.cmd2.value = command;
		this.submitForm(form);
	}

	this.doMapSearch =
	function m_doMapSearch(searchForm)
	{
		this.m_form.txtSearchType.value = getComboVal(searchForm.cboSearchType);
		this.m_form.txtSearchTarget.value = searchForm.txtSearchTarget.value;
		this.doCommand("search");
	}

	// This function takes care of any post processing
	// necessary on the client after the map page is loaded.
	// A call to onMapLoad() must be placed in the
	// map page's body_OnLoad() event.
	this.onMapLoad =
	function m_onMapLoad(command)
	{
		// Synch the current map command to the toolbar's.
		if (this.m_toolbar.currentTool != null)
			this.m_form.cmd.value = this.m_toolbar.currentTool.name;

		this.doQuery(command);
	
		this.setToggle(command);
	
		this.hideDiv("wait");
	}

	// Resize the map according to current available space.
	// Allow some fudging for map sizing.
	this.fudgeHeight = 0;
	this.fudgeWidth = 0;

	this.resizeMap =
	function m_resizeMap(command)
	{
		// Only resize if it's the correct command.
		if (command != "size") return;
		
		// Height and Width.
		var h = this.getFrameHeight(this.m_frame);
		var w = this.getFrameWidth(this.m_frame);

		h -= (this.getImgTop(this.anchorImage) + this.fudgeHeight);
		w -= (this.getImgLeft(this.anchorImage) + this.fudgeWidth);

		this.m_form.txtHeight.value = h;
		this.m_form.txtWidth.value = w;
		this.doCommand("resize");
	}

	this.doLayers =
	function m_doLayers()
	{
		// We need to include layers that have already been managed.
		// Without doing this we'd lose state information if the
		// user zoomed to a level on which layers depended upon.
		var currLayers = new Array();
		if (this.m_form.txtLayers.value != "")
		{
			var currLayerPairs = this.m_form.txtLayers.value.split(",");
			for (var i = 0; i < currLayerPairs.length; i++)
			{
				var item = currLayerPairs[i].split("=");
				currLayers[i] = new Array(item[0], item[1]);
			}
		}

		// Turn current layers on / off, or add a new array item.
		var inputs = this.m_doc.getElementsByTagName("input");
		var input, j, vis;
		for (var i = 0; i < inputs.length; i++)
		{
			input = inputs[i];
			if (input.type == "checkbox" || input.type == "radio")
			{
				vis = (input.checked ? "1" : "0");
				j = doLayersHelperFindLayer(currLayers, input.value);
				if (j == -1)
					currLayers[currLayers.length] = new Array(input.value, vis);
				else
					currLayers[j][1] = vis;
			}
		}

		// Create the array string.
		var item;
		var layers = "";
		for (var i = 0; i < currLayers.length; i++)
		{
			item = currLayers[i];
			layers += item[0] + "=";
			layers += item[1];
			layers += ",";
		}
		if (layers.length > 0)
			layers = layers.substr(0, layers.length - 1);
		
		this.m_form.txtLayers.value = layers;
		this.doCommand("layers");
	}

	function doLayersHelperFindLayer(currentLayerArray, layerName)
	{
		// Returns the index of the layer item if found.
		if (currentLayerArray == null)
			return -1;
		
		var i;
		var found = false;
		for (i = 0; i < currentLayerArray.length; i++)
		{
			if (currentLayerArray[i][0] == layerName)
			{
				found = true;
				break;
			}
		}
	
		if (found)
			return i;
		else
			return -1;
	}

	this.toGeoPoint =
	function(x, y, mapHeight, mapWidth, minx, miny, maxx, maxy)
	{	
		var dPixX = mapWidth;
		var dPixY = mapHeight;

		y = dPixY - y;
		
		var dGeoX = Math.abs(maxx - minx);
		var dGeoY = Math.abs(maxy - miny);
		var geosPerPixX = dGeoX / dPixX;
		var geosPerPixY = dGeoY / dPixY;

		var geosX = geosPerPixX * x;
		var geosY = geosPerPixY * y;
	
		var geoX = minx + geosX;
		var geoY = miny + geosY;

		var pnt = new Array(geoX, geoY);

		return pnt;
	}
	// <-- Mapping functions

	// Searching functions -->
	this.postOnId = true;		// Should the ID query be automatically posted
							// to the server when an ID is performed?

	this.m_rForm = null;  // The form of the page where search results are displayed.
	this.setResultsForm =
	function m_setResultsForm(form)
	{
		this.m_rForm = form;
	}

	this.m_iForm = null;  // The form of the page where ID results are displayed.
						// May be the same as resutls form above.
	this.setIdForm =
	function m_setIdForm(form)
	{
		this.m_iForm = form;
	}

	this.doQuery =
	function m_doQuery(command)
	{
		// Get the correct form to use.
		var form = null;
		if (command == "id")
			form = this.m_iForm;
		else if (command == "search")
			form = this.m_rForm;
		
		if (form != null)
		{
			// Copy all the necessary variables over and submit.
			form.txtSearchType.value = this.m_form.txtSearchType.value;
			form.txtSearchTarget.value = this.m_form.txtSearchTarget.value;
			form.txtLayers.value = this.m_form.txtLayers.value;
			form.txtPage.value = "1";
			form.txtHeight.value = this.m_form.txtHeight.value;
			form.txtWidth.value = this.m_form.txtWidth.value;
			this.submitForm(form);
		}
	}

	this.gotoResultPage =
	function m_gotoResultPage(page)
	{
		this.m_rForm.txtPage.value = page;
		this.submitForm(this.m_rForm);
	}
	
	// Clear the map and result pages from the current search.
	this.clear =
	function m_clear()
	{
		// Clear any measure stuff for the map form.
		this.m_form.txtPoints.value = "";

		this.clearSearchForm(this.m_form);
		this.clearSearchForm(this.m_rForm);
		this.clearSearchForm(this.m_iForm);
	}

	// Clear the given form's search.
	this.clearSearchForm =
	function m_clearSearchForm(form)
	{
		if (form != null)
		{
			form.txtSearchType.value = "";
			form.txtSearchTarget.value = "";
			this.doCommand2(form, "clear");
		}
	}
	// <-- Searching functions

	// Sets the correct toggle button.
	this.setToggle =
	function m_setToggle(command)
	{
		if (command == "search" || command == "id" || command == "measure")
			this.m_toolbar.setToggle("clear");
	}

	this.doPrint =
	function m_doPrint()
	{
		// Create the query string.
		var qry = "txtExtent=" + escape(this.m_form.txtExtent.value);
		qry += "&txtSearchType=" + escape(this.m_form.txtSearchType.value);
		qry += "&txtSearchTarget=" + escape(this.m_form.txtSearchTarget.value);
		qry += "&txtLayers=" + escape(this.m_form.txtLayers.value);
		qry += "&txtRefMap=" + escape(this.m_form.txtRefMap.value);
		qry += "&txtHeight=" + escape(this.m_form.txtHeight.value);
		qry += "&txtWidth=" + escape(this.m_form.txtWidth.value);
		// Turn on all the choices.
		qry += "&chkMap=on&chkLegend=on&chkResults=on";
		window.open("print.php?" + qry);
	}

	this.doHelp =
	function m_doHelp()
	{
		window.open("help.html");
	}

	// There's not a more direct way to do button
	//clicks, so we take care of them all here.
	this.doButtonClick =
	function m_doButtonClick(button)
	{
		var actioned = false;
		switch (button.name)
		{
			// Clear the current search.
			case "clear":
				if (this.m_toolbar.isToggle(button))
				{
					if (button.toggled)
					{
						this.clear();
						actioned = true;
					}
				}
				break;
			
			// Toggle the reference map.
			case "ref_map":
				if (this.m_toolbar.isToggle(button))
				{
					// The "toggled" value hasn't been applied yet.
					// So we actually want the complement .toggled
					this.m_form.txtRefMap.value = (button.toggled ? "false" : "true");
					this.doCommand("ref_map");
					actioned = true;
				}
				break;

			// Open print page.		
			case "print":
				this.doPrint();
				actioned = true;
				break;

			// Open help page.
			case "help":
				this.doHelp();
				actioned = true;
				break;

			// Zoom all.
			case "zoom_all":
				this.doQuickZoom("zoom_all");
				break;
		}
		return actioned;
	}

	// Div functions -->
	this.getDiv =
	function m_getDiv(divName)
	{
		var retVal;
		if (this.m_browser == 0)
			retVal = eval("this.m_doc.all." + divName);
		else if (this.m_browser == 1)
			retVal = this.m_doc.getElementById(divName);
		else if (this.m_browser == 2)
			retVal = eval("this.m_doc." + divName);
	
		return retVal;
	}

	this.setDivVisibility =
	function m_setDivVisibility(div, visible)
	{
		if (this.m_browser == 0 || this.m_browser == 1)
			div.style.visibility = (visible ? "visible" : "hidden");
		else if (this.m_browser == 2)
			div.visibility = (visible ? "show" : "hidden");
	}

	this.setDivTop =
	function m_setDivTop(div, top)
	{
		if (this.m_browser == 0)
			div.style.pixelTop = top;
		else if (this.m_browser == 1)
			div.style.top = top + 'px';
		else if (this.m_browser == 2)
			div.top = top;
	}

	this.setDivLeft =
	function m_setDivLeft(div, left)
	{
		if (this.m_browser == 0)
			div.style.pixelLeft = left;
		else if (this.m_browser == 1)
			div.style.left = left + 'px';
		else if (this.m_browser == 2)
			div.left = left;
	}

	this.setDivWidth =
	function m_setDivHeight(div, width)
	{
		if (this.m_browser == 0 || this.m_browser == 1)
			div.style.width = width;
		else if (this.m_browser == 2)
			div.clip.width = width;
	}

	this.setDivHeight =
	function m_setDivHeight(div, height)
	{
		if (this.m_browser == 0 || this.m_browser == 1)
			div.style.height = height;
		else if (this.m_browser == 2)
			div.clip.height = height;
	}

	this.getDivTop =
	function m_getDivTop(div)
	{
		if (this.m_browser == 0)
			return div.style.pixelTop;
		else if (this.m_browser == 1)
			return parseInt(div.style.top);
		else if (this.m_browser == 2)
			return div.top;
	}

	this.getDivLeft =
	function m_getDivLeft(div)
	{
		if (this.m_browser == 0)
			return div.style.pixelLeft;
		else if (this.m_browser == 1)
			return parseInt(div.style.left);
		else if (this.m_browser == 2)
			return div.left;
	}

	this.getDivHeight =
	function m_getDivHeight(div)
	{
		if (this.m_browser == 0 || this.m_browser == 1)
			return div.offsetHeight;
		else if (this.m_browser == 2)
			return div.clip.height;
	}

	this.getDivWidth =
	function m_getDivWidth(div)
	{
		if (this.m_browser == 0 || this.m_browser == 1)
			return div.offsetWidth;
		else if (this.m_browser == 2)
			return div.clip.width;
	}

	// Show and center a dialog div.
	this.showDialogDiv =
	function m_showDialogDiv(divName)
	{
		// First we center it, then we show it.

		var div = this.getDiv(divName);
		this.setDivTop(div, (this.getFrameHeight(this.m_frame) - this.getDivHeight(div)) / 2);
		this.setDivLeft(div, (this.getFrameWidth(this.m_frame) - this.getDivWidth(div)) / 2); 
		this.setDivVisibility(div, true);
	}
	
	this.hideDiv =
	function m_hideDiv(divName)
	{
		this.setDivVisibility(this.getDiv(divName), false);
	}
	// <-- Div functions

	// This is the type of browser we're dealing with.
	this.m_browser = -1;
	this.setBrowser =
	function m_setBrowser()
	{
		if (this.m_doc.all)
			this.m_browser = 0;  // IE.
		else if (this.m_doc.getElementById)
			this.m_browser = 1;  // Mozilla.
		else if (this.m_doc.layers)
			this.m_browser = 2;  // Old Netscape?
	}

	this.getImgLeft =
	function m_getImgLeft(imageId)
	{
		var img = this.m_doc.images[imageId];
		if (img.x)
			return img.x;
			
		var left = img.offsetLeft;
		var par = img.offsetParent;
		while (par != null)
		{
			left += par.offsetLeft;
			par = par.offsetParent;
		}
		return left;
	}
	
	this.getImgTop =
	function m_getImgTop(imageId)
	{
		var img = this.m_doc.images[imageId];
		if (img.y)
			return img.y;
			
		var top = img.offsetTop;
		var par = img.offsetParent;
		while (par != null)
		{
			top += par.offsetTop;
			par = par.offsetParent;
		}
		return top;
	}

	this.getFrameHeight =
	function m_getFrameHeight(frame)
	{
		if (this.m_browser == 0)
			return frame.document.body.clientHeight;
		else
			return frame.innerHeight;
	}
	
	this.getFrameWidth =
	function m_getFrameWidth(frame)
	{
		if (this.m_browser == 0)
			return frame.document.body.clientWidth;
		else
			return frame.innerWidth;
	}

	// Rubberbanding -->
	this.rubberCanDoZoomWinFunc = null;
	this.connectEvents = null;  // Func that's fired to connect mousing events.
	this.disconnectEvents = null;  // Func fired when mousing events are disconnected.
	this.m_rubberTop = 0;
	this.m_rubberLeft = 0;
	this.m_rubberMargin = 5;  // Default to 5 pixels.
	this.isInBounds = false;  // Was the click in the map bounds?

	// This locates where rubberbanding should
	// occur according to an anchor image.
	// The image is assumed at the top-left.
	this.setupRubberbanding =
	function setupRubberbanding()
	{
		this.m_rubberTop = this.getImgTop(this.anchorImage) + 1;  // Account for the pixel gif.
		this.m_rubberLeft = this.getImgLeft(this.anchorImage);
	}

	// Return the current X & Y of the mouse.
	this.getXY =
	function getXY(e)
	{
		var x, y;
		switch (this.m_browser)
		{
			case 0:
				x = e.x;
				y = e.y;
				break;
			
			case 1:
				x = e.clientX;
				y = e.clientY;
				break;
			
			case 2:
				break;
		}

		// Set the inBounds flag.
		this.isInBounds = this.inBounds(x, y);

		return new Array(x, y);
	}

	this.getRubberbandDiv =
	function m_getRubberbandDiv()
	{
		return this.getDiv("rubberBand");
	}

	this.startRubber =
	function m_startRubber(e)
	{
		// Only Zooming and IDing allowed.
		var tool = this.m_toolbar.currentTool.name;
		if (!(tool == "zoom_win" || tool == "id")) return;

		var pnt = this.getXY(e);

		// Bail if we went out of bounds.
		if (!this.isInBounds) return;

		// Set various div dimensions and properties.
		var div = this.getRubberbandDiv();
		this.setDivWidth(div, 0);
		this.setDivHeight(div, 0);
		this.setDivLeft(div, pnt[0]);
		this.setDivTop(div, pnt[1]);

		this.setDivVisibility(div, true);

		if (this.m_browser == 2)
		{
			this.m_doc.captureEvents(Event.MOUSEMOVE);
			this.m_doc.captureEvents(Event.MOUSEUP);
		}

		this.connectEvents();
	}
	
	this.moveRubber =
	function m_moveRubber(e)
	{
		var pnt = this.getXY(e);

		// Set various div dimensions and properties.
		var div = this.getRubberbandDiv();
		var w = this.adjustDim(pnt[0] - this.getDivLeft(div));
		var h = this.adjustDim(pnt[1] - this.getDivTop(div));
		if (this.inX(pnt[0]))
			this.setDivWidth(div, w);
		if (this.inY(pnt[1]))
			this.setDivHeight(div, h);
		
		if (this.m_browser == 2)
		{
			div.document.open();
			div.document.write('<table width="' + this.getDivWidth(div) + '" height="' + this.getDivHeight(div) + '" border="1"><tr><td><\/td><\/tr><\/table>');
			div.document.close();
		}
	}

	this.stopRubber =
	function stopRubber()
	{
		// Make it go away.
		this.setDivVisibility(this.getRubberbandDiv(), false);

		if (this.m_browser == 2)
		{
			this.m_doc.releaseEvents(Event.MOUSEMOVE);
			this.m_doc.releaseEvents(Event.MOUSEUP);
		}
	
		this.m_doc.onmousemove = null;
		this.m_doc.onmouseup = null;

		var tool = this.m_toolbar.currentTool.name;
		if (tool == "zoom_win")
			this.zoomWindow(this.currX1(), this.currY1(), this.currX2(), this.currY2());
		else if (tool == "id" && this.postOnId)
		{
			// Set the parameters for the ID query and submit.
			this.m_form.txtParam.value = this.currX1() + "," + this.currY1() + "," + this.currX2() + "," + this.currY2();
			this.submitForm(this.m_form);
		}

		if (this.disconnectEvents != null)
			this.disconnectEvents();
	}

	this.inBounds =
	function m_inBounds(x, y)
	{
		return (this.inX(x) && this.inY(y));
	}

	this.inX =
	function m_inX(x)
	{
		return (x >= (this.m_rubberLeft + this.m_rubberMargin) && x <= (this.m_rubberLeft + this.width - this.m_rubberMargin));
	}

	this.inY =
	function m_inY(y)
	{
		return (y >= (this.m_rubberTop + this.m_rubberMargin) && y <= (this.m_rubberTop + this.height - this.m_rubberMargin));
	}

	this.adjustDim =
	function m_adjustDim(dim)
	{
		if (dim < 0)
			return 0;
		else
			return dim;
	}

	// The X coordinate in reference to the frame.
	this.absoluteX =
	function()
	{
		return this.getDivLeft(this.getRubberbandDiv());
	}
	
	// The Y coordinate in reference to the frame.
	this.absoluteY =
	function()
	{
		return this.getDivTop(this.getRubberbandDiv());
	}

	// Takes a page X pixel coordinate and translates it to a map X pixel coordinate.
	this.getMapX =
	function(pageX)
	{
		return (pageX - this.m_rubberLeft);
	}

	// Takes a page Y pixel coordinate and translates it to a map Y pixel coordinate.
	this.getMapY =
	function(pageY)
	{
		return (pageY - this.m_rubberTop);
	}

	// The X1 coordinate in reference to the map image.
	this.currX1 =
	function()
	{
		return this.getMapX(this.absoluteX());
	}
	
	// The Y1 coordinate in reference to the map image.
	this.currY1 =
	function()
	{
		return this.getMapY(this.absoluteY());
	}

	// The X2 coordinate in reference to the map image.
	this.currX2 =
	function()
	{
		return (this.currX1() + this.getDivWidth(this.getRubberbandDiv()));
	}

	// The Y2 coordinate in reference to the map image.
	this.currY2 =
	function()
	{
		return (this.currY1() + this.getDivHeight(this.getRubberbandDiv()));
	}
	// <-- Rubberbanding
}